changeset 3567:2b002b042499

translations: merge translations from weblate
author Fadi Abbud <fadi.abbud@intevation.de>
date Mon, 03 Jun 2019 10:19:18 +0200
parents 4c585b5d4fe8 (diff) a363d1529cf8 (current diff)
children c646bb821b69
files
diffstat 215 files changed, 21279 insertions(+), 11012 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Wed May 29 10:58:45 2019 +0200
+++ b/.hgignore	Mon Jun 03 10:19:18 2019 +0200
@@ -108,4 +108,5 @@
 
 # Import data
 schema/geonames-import/data/*
-translations.json
\ No newline at end of file
+translations.json
+pub-config.json
--- a/client/.env	Wed May 29 10:58:45 2019 +0200
+++ b/client/.env	Mon Jun 03 10:19:18 2019 +0200
@@ -11,3 +11,7 @@
 #Logos to be potentially loaded by the SPA. Can be left blank.
 VUE_APP_SECONDARY_LOGO_URL=
 VUE_APP_LOGO_FOR_PDF_URL=
+VUE_APP_SILENCE_TRANSLATIONWARNINGS =
+
+#Url of user manual
+VUE_APP_USER_MANUAL_URL=
\ No newline at end of file
--- a/client/package.json	Wed May 29 10:58:45 2019 +0200
+++ b/client/package.json	Mon Jun 03 10:19:18 2019 +0200
@@ -37,6 +37,7 @@
     "d3-line-chunked": "^1.4.1",
     "date-fns": "^1.30.1",
     "debounce": "^1.2.0",
+    "file-saver": "^2.0.2",
     "glob-all": "^3.1.0",
     "jspdf": "^1.5.3",
     "locale2": "^2.2.0",
--- a/client/public/index.html	Wed May 29 10:58:45 2019 +0200
+++ b/client/public/index.html	Mon Jun 03 10:19:18 2019 +0200
@@ -5,7 +5,7 @@
   SPDX-License-Identifier: AGPL-3.0-or-later
   License-Filename: LICENSES/AGPL-3.0.txt
 
-  Copyright (C) 2018 by via donau
+  Copyright (C) 2018, 2019 by via donau
    – Österreichische Wasserstraßen-Gesellschaft mbH
   Software engineering by Intevation GmbH
 -->
--- a/client/src/assets/application.scss	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/assets/application.scss	Mon Jun 03 10:19:18 2019 +0200
@@ -105,6 +105,22 @@
   font-weight: bold;
 }
 
+.box-control {
+  display: flex;
+  color: #888;
+  padding: 4px 7px;
+  border-radius: 0.25rem;
+  cursor: pointer;
+  transition: background-color 0.3s, color 0.3s;
+  &.small {
+    padding: 4px;
+  }
+  &:hover {
+    color: #666;
+    background-color: #eee;
+  }
+}
+
 .expanded {
   max-height: 999px;
   max-width: 999px;
@@ -183,16 +199,12 @@
 
 select.form-control-sm.small {
   padding: 0.25rem 0.1rem;
-  font-size: 80%;
+  font-size: 0.75rem;
 }
 
 input.form-control-sm.small {
   padding: 0.25rem 0.2rem;
-  font-size: 80%;
-}
-
-.empty {
-  margin-right: 1.25rem;
+  font-size: 0.75rem;
 }
 
 .truncate {
@@ -201,15 +213,12 @@
   text-overflow: ellipsis;
 }
 
-.loading {
-  background: rgba(255, 255, 255, 0.9);
-  position: absolute;
-  z-index: 99;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  display: flex;
-  align-items: center;
-  justify-content: center;
+.wh-100 {
+  width: 100% !important;
+  height: 100% !important;
 }
+
+.wh-50 {
+  width: 50% !important;
+  height: 50% !important;
+}
Binary file client/src/assets/da-diagram.png has changed
Binary file client/src/assets/distancemarks-axis.png has changed
Binary file client/src/assets/fa-diagram.png has changed
Binary file client/src/assets/marker-gauge-blue.png has changed
Binary file client/src/assets/marker-gauge-brown.png has changed
Binary file client/src/assets/marker-gauge-red.png has changed
Binary file client/src/assets/marker-gauge-white.png has changed
Binary file client/src/assets/marker-gauge.png has changed
--- a/client/src/assets/tooltip.scss	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/assets/tooltip.scss	Mon Jun 03 10:19:18 2019 +0200
@@ -17,10 +17,12 @@
 }
 
 .tooltip .tooltip-inner {
-  background: black;
-  color: white;
-  border-radius: 16px;
+  background: white;
+  box-shadow: 0 0.1rem 0.5rem rgba(0, 0, 0, 0.2);
+  color: #666;
+  border-radius: 0.25rem;
   padding: 5px 10px 4px;
+  font-size: 0.8rem;
 }
 
 .tooltip .tooltip-arrow {
@@ -29,7 +31,7 @@
   border-style: solid;
   position: absolute;
   margin: 5px;
-  border-color: black;
+  border-color: white;
   z-index: 1;
 }
 
@@ -95,7 +97,7 @@
 
 .tooltip.popover .popover-inner {
   background: #f9f9f9;
-  color: black;
+  color: white;
   padding: 24px;
   border-radius: 5px;
   box-shadow: 0 5px 30px rgba(black, 0.1);
--- a/client/src/components/App.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/App.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -3,7 +3,7 @@
     <div v-if="isAuthenticated" class="d-flex flex-column userinterface">
       <div class="boxes d-flex p-2">
         <div class="mr-auto d-flex">
-          <Sidebar :routeName="routeName" />
+          <Sidebar />
           <div :class="searchContainer">
             <Search v-if="isMapVisible" />
             <Contextbox v-if="isMapVisible" />
@@ -14,6 +14,7 @@
             <Profiles v-if="isMapVisible" />
             <Gauges v-if="isMapVisible" />
             <Pdftool v-if="isMapVisible" />
+            <AvailableFairwayDepthDialogue v-if="isMapVisible" />
           </div>
           <div class="d-flex flex-column align-items-end">
             <Identify v-if="isMapVisible" />
@@ -22,13 +23,12 @@
           <Toolbar v-if="isMapVisible" />
         </div>
       </div>
-      <Zoom v-if="isMapVisible" />
-      <Splitscreen v-if="isMapVisible" />
-      <MinimizedSplitscreens v-if="isMapVisible" />
+      <MapPopup />
     </div>
     <router-view />
     <vue-snotify />
     <Popup />
+    <KeyboardHandler />
   </div>
 </template>
 
@@ -79,14 +79,10 @@
     ...mapState("user", ["isAuthenticated"]),
     ...mapState("application", ["contextBoxContent", "showSearchbar"]),
     isMapVisible() {
-      return /importoverview|stretches|review|bottlenecks|mainview/.test(
-        this.routeName
+      return /importconfiguration|importoverview|stretches|sections|review|bottlenecks|mainview/.test(
+        this.$route.name
       );
     },
-    routeName() {
-      const routeName = this.$route.name;
-      return routeName;
-    },
     searchContainer() {
       return [
         "ml-2",
@@ -100,7 +96,6 @@
     Profiles: () => import("./fairway/Profiles"),
     Gauges: () => import("./gauge/Gauges"),
     Pdftool: () => import("./Pdftool"),
-    Zoom: () => import("./Zoom"),
     Identify: () => import("./identify/Identify"),
     Layers: () => import("./layers/Layers"),
     Sidebar: () => import("./Sidebar"),
@@ -108,8 +103,10 @@
     Contextbox: () => import("./Contextbox"),
     Toolbar: () => import("./toolbar/Toolbar"),
     Popup: () => import("./Popup"),
-    Splitscreen: () => import("./splitscreen/Splitscreen"),
-    MinimizedSplitscreens: () => import("./splitscreen/MinimizedSplitscreens")
+    AvailableFairwayDepthDialogue: () =>
+      import("./fairway/AvailableFairwayDepthDialogue.vue"),
+    MapPopup: () => import("./map/MapPopup"),
+    KeyboardHandler: () => import("./KeyboardHandler")
   }
 };
 </script>
--- a/client/src/components/Bottlenecks.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/Bottlenecks.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -7,71 +7,55 @@
     />
     <UITableHeader
       :columns="[
-        { id: 'properties.name', title: `${nameLabel}`, class: 'col-4' },
+        { id: 'properties.name', title: `${nameLabel}`, width: '230px' },
+        {
+          id: 'properties.responsible_country',
+          title: `${countryLabel}`,
+          width: '100px'
+        },
         {
           id: 'properties.current',
           title: `${latestmeasurementLabel}`,
-          class: 'col-3'
+          width: '150px'
         },
-        { id: 'properties.from', title: `${chainageLabel}`, class: 'col-3' }
+        { id: 'properties.from', title: `${chainageLabel}`, width: '135px' }
       ]"
     />
     <UITableBody
       :data="filteredBottlenecks() | sortTable(sortColumn, sortDirection)"
-      :maxHeight="(showSplitscreen ? 18 : 35) + 'rem'"
-      :active="openBottleneck"
-      v-slot="{ item: bottleneck }"
+      maxHeight="35rem"
+      :isActive="item => item === this.openBottleneck"
     >
-      <div class="col-4 py-2 text-left">
-        <a href="#" @click="selectBottleneck(bottleneck)">{{
-          bottleneck.properties.name
-        }}</a>
-      </div>
-      <div class="col-3 py-2">
-        {{ bottleneck.properties.current | surveyDate }}
-      </div>
-      <div class="col-3 py-2">
-        {{
-          displayCurrentChainage(
-            bottleneck.properties.from,
-            bottleneck.properties.to
-          )
-        }}
-      </div>
-      <div class="col-2 py-2 pr-0 text-right d-flex flex-column">
-        <a
-          class="text-info mt-auto mb-auto mr-2"
-          @click="loadSurveys(bottleneck)"
-          v-if="bottleneck.properties.current"
-        >
-          <font-awesome-icon
-            class="pointer"
-            icon="spinner"
-            fixed-width
-            spin
-            v-if="loading === bottleneck"
-          ></font-awesome-icon>
-          <font-awesome-icon
-            class="pointer"
-            icon="angle-down"
-            fixed-width
-            v-if="loading !== bottleneck && openBottleneck !== bottleneck"
-          ></font-awesome-icon>
-          <font-awesome-icon
-            class="pointer"
-            icon="angle-up"
-            fixed-width
-            v-if="loading !== bottleneck && openBottleneck === bottleneck"
-          ></font-awesome-icon>
-        </a>
-      </div>
-      <div
-        :class="[
-          'col-12 p-0',
-          'surveys',
-          { open: openBottleneck === bottleneck }
-        ]"
-      >
+      <template v-slot:row="{ item: bottleneck }">
+        <div class="table-cell truncate text-left" style="width: 230px">
+          <a href="#" @click="selectBottleneck(bottleneck)">{{
+            bottleneck.properties.name
+          }}</a>
+        </div>
+        <div class="table-cell text-center" style="width: 100px">
+          {{ bottleneck.properties.responsible_country }}
+        </div>
+        <div class="table-cell" style="width: 150px">
+          {{ bottleneck.properties.current | surveyDate }}
+        </div>
+        <div class="table-cell" style="width: 135px">
+          {{
+            displayCurrentChainage(
+              bottleneck.properties.from,
+              bottleneck.properties.to
+            )
+          }}
+        </div>
+        <div class="table-cell center" style="flex-grow: 1">
+          <UISpinnerButton
+            @click="loadSurveys(bottleneck)"
+            :loading="loading === bottleneck"
+            :state="bottleneck === openBottleneck"
+            v-if="bottleneck.properties.current"
+          />
+        </div>
+      </template>
+      <template v-slot:expand="{ item: bottleneck }">
         <a
           href="#"
           class="d-inline-block px-3 py-2"
@@ -81,32 +65,11 @@
         >
           {{ survey.date_info | surveyDate }}
         </a>
-      </div>
+      </template>
     </UITableBody>
   </div>
 </template>
 
-<style lang="sass" scoped>
-.table-body
-  .row
-    > div:not(:last-child)
-      transition: background-color 0.3s, color 0.3s
-    &.active
-      > div:not(:last-child)
-        background-color: $color-info
-        color: #fff
-        a
-          color: #fff !important
-      .surveys
-        border-bottom: solid 1px $color-info
-    .surveys
-      overflow: hidden
-      max-height: 0
-      &.open
-        overflow-y: auto
-        max-height: 5rem
-</style>
-
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -138,15 +101,14 @@
     };
   },
   computed: {
-    ...mapState("application", [
-      "searchQuery",
-      "showSearchbarLastState",
-      "showSplitscreen"
-    ]),
+    ...mapState("application", ["searchQuery", "showSearchbarLastState"]),
     ...mapState("bottlenecks", ["bottlenecksList"]),
     bottlenecksLabel() {
       return this.$gettext("Bottlenecks");
     },
+    countryLabel() {
+      return this.$gettext("Country");
+    },
     nameLabel() {
       return this.$gettext("Name");
     },
@@ -180,7 +142,7 @@
           this.$store.commit("bottlenecks/selectedSurvey", survey);
         })
         .then(() => {
-          this.$store.commit("map/moveToExtent", {
+          this.$store.dispatch("map/moveToFeauture", {
             feature: bottleneck,
             zoom: 17,
             preventZoomOut: true
@@ -194,10 +156,7 @@
           bottleneck.properties.name
         )
         .then(() => {
-          this.$store.commit("bottlenecks/setFirstSurveySelected");
-        })
-        .then(() => {
-          this.$store.commit("map/moveToExtent", {
+          this.$store.dispatch("map/moveToFeauture", {
             feature: bottleneck,
             zoom: 17,
             preventZoomOut: true
--- a/client/src/components/Contextbox.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/Contextbox.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,11 +1,11 @@
 <template>
   <div :class="style">
-    <Bottlenecks v-if="contextBoxContent === 'bottlenecks'"></Bottlenecks>
-    <Staging v-if="contextBoxContent === 'staging'"></Staging>
-    <Stretches v-if="contextBoxContent === 'stretches'"></Stretches>
-    <ImportOverview
-      v-if="contextBoxContent === 'importoverview'"
-    ></ImportOverview>
+    <Bottlenecks v-if="contextBoxContent === 'bottlenecks'" />
+    <Staging v-if="contextBoxContent === 'staging'" />
+    <Stretches v-if="contextBoxContent === 'stretches'" />
+    <Sections v-if="contextBoxContent === 'sections'" />
+    <ImportOverview v-if="contextBoxContent === 'importoverview'" />
+    <ImportConfiguration v-if="contextBoxContent === 'importconfiguration'" />
   </div>
 </template>
 
@@ -29,9 +29,10 @@
   name: "contextbox",
   components: {
     Bottlenecks: () => import("@/components/Bottlenecks"),
-    Stretches: () => import("@/components/ImportStretches.vue"),
-    ImportOverview: () =>
-      import("@/components/importoverview/ImportOverview.vue")
+    Stretches: () => import("@/components/stretches/Stretches"),
+    Sections: () => import("@/components/sections/Sections"),
+    ImportOverview: () => import("@/components/importoverview/ImportOverview"),
+    ImportConfiguration: () => import("@/components/importconfiguration/Import")
   },
   computed: {
     ...mapState("application", [
@@ -53,6 +54,7 @@
   },
   methods: {
     close() {
+      this.$store.commit("map/mapPopupEnabled", true);
       this.$store.commit("application/searchQuery", "");
       this.$store.commit("application/showContextBox", false);
       this.$store.commit(
@@ -75,7 +77,7 @@
   background: #fff;
 }
 .contextbox > div:last-child {
-  width: 660px;
+  width: 668px;
 }
 
 .contextboxcollapsed {
@@ -84,7 +86,7 @@
 }
 
 .contextboxextended {
-  max-width: 660px;
+  max-width: 668px;
 }
 
 .close-contextbox {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/DiagramLegend.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,75 @@
+<template>
+  <div :class="['diagram-legend', { collapsed }]">
+    <span class="box-control toggle" @click="collapsed = !collapsed">
+      <font-awesome-icon icon="angle-left" fixed-width />
+    </span>
+    <div
+      class="d-flex align-items-center justify-content-center w-100"
+      style="overflow: hidden"
+    >
+      <div class="text-left px-3">
+        <slot />
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="sass">
+.diagram-legend
+  position: relative
+  width: 180px
+  font-size: 0.8rem
+  display: flex
+  border-right: solid 1px #dee2e6
+  .toggle
+    margin-left: 0
+    position: absolute
+    top: 0.25rem
+    left: 0.25rem
+    svg
+      transition: transform 0.3s
+  &.collapsed
+    width: 0
+    border-right: none
+    .toggle
+      left: 0.25rem
+      svg
+        transform: rotateY(180deg)
+  .legend
+    margin: 10px 0
+    span
+      vertical-align: middle
+      display: inline-block
+      width: 12px
+      height: 12px
+      border-radius: 50%
+</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>
+ */
+
+export default {
+  data() {
+    return {
+      collapsed: false
+    };
+  },
+  watch: {
+    collapsed() {
+      this.$nextTick(this.$parent.drawDiagram);
+    }
+  }
+};
+</script>
--- a/client/src/components/ImportApprovedGaugeMeasurement.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-<template>
-  <div class="d-flex flex-row">
-    <Spacer></Spacer>
-    <div class="card sysconfig mt-2 shadow-xs w-100 h-100 mr-2">
-      <UIBoxHeader icon="upload" :title="importGaugmeasurmentLabel" />
-      <div class="card-body stretches-card">
-        <div class="w-95 ml-auto mr-auto mt-4 mb-4">
-          <div class="d-flex flex-column text-left w-25">
-            <label class="text-nowrap" for="originator">
-              <small class="text-muted"
-                >{{ $options.ORIGINATOR }} / {{ $options.FROM }}</small
-              >
-            </label>
-            <input
-              type="text"
-              v-model="originator"
-              class="form-control"
-              id="originator"
-            />
-            <span class="text-left text-danger">
-              <small v-if="!originator">
-                <translate>Please enter an originator</translate>
-              </small>
-            </span>
-          </div>
-          <div class="mt-4 flex-column w-100">
-            <div class="custom-file">
-              <input
-                accept=".csv"
-                type="file"
-                @change="fileSelected"
-                class="custom-file-input"
-                id="uploadFile"
-              />
-              <label class="pointer custom-file-label" for="uploadFile">
-                {{ uploadLabel }}
-              </label>
-            </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>
-</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";
-import { displayError, displayInfo } from "@/lib/errors.js";
-import app from "@/main";
-
-export default {
-  name: "importapprovedgaugemeasurements",
-  data() {
-    return {
-      disableUploadButton: false,
-      uploadLabel: this.$gettext("choose file to upload"),
-      uploadFile: null,
-      originator: "viadonau"
-    };
-  },
-  computed: {
-    importGaugmeasurmentLabel() {
-      return this.$gettext("Import approved gaugemeasurements");
-    }
-  },
-  methods: {
-    initialState() {
-      this.uploadLabel = this.$gettext("choose file to upload");
-      this.uploadFile = null;
-      this.originator = "viadonau";
-    },
-    fileSelected(e) {
-      const files = e.target.files || e.dataTransfer.files;
-      if (!files) return;
-      this.uploadLabel = files[0].name;
-      this.uploadFile = files[0];
-    },
-    submit() {
-      if (!this.originator || !this.uploadFile) return;
-      let formData = new FormData();
-      formData.append("agm", this.uploadFile);
-      formData.append("originator", this.originator);
-      HTTP.post("/imports/agm", formData, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-Type": "multipart/form-data"
-        }
-      })
-        .then(() => {
-          displayInfo({
-            title: this.$gettext("Import"),
-            message: this.$gettext(
-              "Starting import of Approved Gauge Measurements"
-            )
-          });
-          this.initialState();
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    }
-  },
-  components: {
-    Spacer: () => import("./Spacer")
-  },
-  ORIGINATOR: app.$gettext("originator"),
-  FROM: app.$gettext("from")
-};
-</script>
-
-<style lang="scss" scoped></style>
--- a/client/src/components/ImportSoundingresults.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,358 +0,0 @@
-<template>
-  <div class="main d-flex flex-column">
-    <div class="d-flex flex-row">
-      <Spacer></Spacer>
-      <div class="card shadow-xs mt-2 mr-2 w-100 h-100">
-        <UIBoxHeader icon="upload" :title="importSoundingresultsLabel" />
-        <div v-if="editState">
-          <div
-            v-for="(message, index) in messages"
-            :key="index"
-            class="alert alert-warning small rounded-0"
-          >
-            {{ message }}
-          </div>
-          <div class="container">
-            <div class="row">
-              <div class="col-5">
-                <small class="text-muted">
-                  <translate>Bottleneck</translate>
-                </small>
-                <select v-model="bottleneck" class="custom-select">
-                  <option
-                    v-for="bottleneck in availableBottlenecks"
-                    :value="bottleneck"
-                    :key="bottleneck.properties.objnam"
-                  >
-                    {{ bottleneck.properties.objnam }}
-                  </option>
-                </select>
-                <span class="text-danger">
-                  <small v-if="!bottleneck">
-                    <translate>Please select a bottleneck</translate>
-                  </small>
-                </span>
-              </div>
-              <div class="col-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 class="col-2">
-                <small class="text-muted">
-                  <translate>Depthreference</translate>
-                </small>
-                <select
-                  v-model="depthReference"
-                  class="custom-select"
-                  id="depthreference"
-                >
-                  <option
-                    v-for="option in this.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="col-3">
-                <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 class="row"></div>
-          </div>
-        </div>
-        <div class="container py-5">
-          <div v-if="uploadState" class="input-group">
-            <div class="custom-file">
-              <input
-                accept=".zip"
-                type="file"
-                @change="fileSelected"
-                class="custom-file-input"
-                id="uploadFile"
-              />
-              <label class="pointer custom-file-label" for="uploadFile">
-                {{ uploadLabel }}
-              </label>
-            </div>
-          </div>
-          <div class="d-flex justify-content-between" v-if="editState">
-            <a
-              download="meta.json"
-              :href="dataLink"
-              :class="[
-                'btn btn-outline-info',
-                { disabled: !bottleneck || !importDate || !depthReference }
-              ]"
-            >
-              <translate>Download Meta.json</translate>
-            </a>
-            <span>
-              <button
-                @click="deleteTempData"
-                class="btn btn-danger"
-                type="button"
-              >
-                <translate>Cancel Upload</translate>
-              </button>
-              <button
-                :disabled="disableUploadButton"
-                @click="confirm"
-                class="btn btn-info ml-2"
-                type="button"
-              >
-                <translate>Confirm</translate>
-              </button>
-            </span>
-          </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 = null;
-      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];
-      this.upload();
-    },
-    deleteTempData() {
-      HTTP.delete("/imports/sr-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}`
-          });
-        });
-    },
-    upload() {
-      let formData = new FormData();
-      formData.append("soundingresult", this.uploadFile);
-      HTTP.post("/imports/sr-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 = this.bottlenecks.find(
-              bn => bn.properties.objnam === 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.properties.objnam);
-      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/sr", 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.properties.objnam
-          });
-          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"]),
-    importSoundingresultsLabel() {
-      return this.$gettext("Import Soundingresults");
-    },
-    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;
-    },
-    editState() {
-      return this.importState === IMPORTSTATE.EDIT;
-    },
-    uploadState() {
-      return this.importState === IMPORTSTATE.UPLOAD;
-    },
-    Upload() {
-      return this.$gettext("Upload");
-    },
-    Confirm() {
-      return this.$gettext("Confirm");
-    },
-    dataLink() {
-      if (this.bottleneck && this.depthReference && this.import) {
-        return (
-          "data:text/json;charset=utf-8," +
-          encodeURIComponent(
-            JSON.stringify({
-              depthReference: this.depthReference,
-              bottleneck: this.bottleneck.properties.objnam,
-              date: this.importDate
-            })
-          )
-        );
-      }
-    },
-    depthReferenceOptions() {
-      if (
-        this.bottleneck &&
-        this.bottleneck.properties.reference_water_levels
-      ) {
-        return Object.keys(
-          JSON.parse(this.bottleneck.properties.reference_water_levels)
-        );
-      }
-      return [];
-    }
-  }
-};
-</script>
--- a/client/src/components/ImportStretches.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,594 +0,0 @@
-<template>
-  <div class="d-flex flex-column mb-3">
-    <UIBoxHeader
-      icon="road"
-      :title="defineStretchesLabel"
-      :closeCallback="$parent.close"
-    />
-    <div v-if="!edit" class="mb-3">
-      <UITableHeader
-        :columns="[
-          { id: 'properties.name', title: `${nameLabel}`, class: 'col-4' },
-          { id: 'properties.date_info', title: `${dateLabel}`, class: 'col-2' },
-          {
-            id: 'properties.source_organization',
-            title: `${sourceorganizationLabel}`,
-            class: 'col-3'
-          }
-        ]"
-      />
-      <UITableBody
-        :data="filteredStretches() | sortTable(sortColumn, sortDirection)"
-        v-slot="{ item: stretch }"
-      >
-        <div class="py-1 col-4 ">
-          <a
-            class="linkto text-info"
-            v-if="isInStaging(stretch.properties.name)"
-            @click="gotoStaging(getStagingLink(stretch.properties.name))"
-          >
-            {{ stretch.properties.name
-            }}<font-awesome-icon
-              class="ml-1 text-danger"
-              icon="exclamation-triangle"
-              fixed-width
-            ></font-awesome-icon
-            ><small class="ml-1">review</small>
-          </a>
-          <a v-else @click="moveMapToStretch(stretch)" href="#">{{
-            stretch.properties.name
-          }}</a>
-        </div>
-        <div class="py-1 col-2">
-          {{ stretch.properties.date_info | surveyDate }}
-        </div>
-        <div class="py-1 col-3">
-          {{ stretch.properties.source_organization }}
-        </div>
-        <div class="py-1 col text-right">
-          <button
-            class="btn btn-xs btn-dark mr-1"
-            @click="editStretch(stretch)"
-          >
-            <font-awesome-icon icon="pencil-alt" fixed-width />
-          </button>
-          <button class="btn btn-xs btn-dark" @click="deleteStretch(stretch)">
-            <font-awesome-icon icon="trash" fixed-width />
-          </button>
-        </div>
-      </UITableBody>
-    </div>
-    <div v-if="edit">
-      <div class="ml-3 mr-3">
-        <div class="d-flex flex-row justify-content-between">
-          <div class="mt-2 w-50 mr-2 text-left">
-            <small class="text-muted"> <translate>ID</translate> </small>
-            <input
-              id="id"
-              type="text"
-              class="form-control"
-              placeholder="AT_Section_12"
-              aria-label="id"
-              v-model="id"
-              :disabled="editExistingStretch"
-            />
-            <span class="text-left text-danger">
-              <small v-if="idError && !id">
-                <translate>Please enter an id</translate>
-              </small>
-            </span>
-          </div>
-          <div class="mt-2 w-50 ml-2 text-left">
-            <div>
-              <small class="text-muted">
-                <translate>Countrycode</translate>
-              </small>
-              <input
-                id="countryCode"
-                type="text"
-                class="form-control"
-                placeholder="AT"
-                aria-label="id"
-                v-model="countryCode"
-              />
-              <span class="text-left text-danger">
-                <small v-if="countryCodeError && !countryCode">
-                  <translate>Please enter a countrycode </translate>
-                </small>
-              </span>
-            </div>
-            <div class="w-50 ml-2"></div>
-          </div>
-        </div>
-        <div class="d-flex flex-column  justify-content-between">
-          <div class="mt-2 text-left">
-            <small class="text-muted"> <translate>Start rhm</translate> </small>
-            <div class="d-flex flex-row">
-              <input
-                id="startrhm"
-                type="text"
-                class="form-control"
-                placeholder="e.g. ATXXX000010000019900"
-                aria-label="startrhm"
-                v-model="startrhm"
-              />
-              <span class="input-group-text">
-                <font-awesome-icon
-                  @click="togglePipette('start')"
-                  :class="{ 'text-info': pipetteStart }"
-                  icon="bullseye"
-                ></font-awesome-icon>
-              </span>
-            </div>
-            <span class="text-left text-danger">
-              <small v-if="startrhmError && !startrhm">
-                <translate>Please enter a start point</translate>
-              </small>
-            </span>
-          </div>
-          <div class="mt-2 text-left">
-            <small class="text-muted"> <translate>End rhm</translate> </small>
-            <div class="d-flex flex-row">
-              <input
-                id="endrhm"
-                type="text"
-                class="form-control"
-                placeholder="e.g. ATXXX000010000019900"
-                aria-label="endrhm"
-                v-model="endrhm"
-              />
-              <span class="input-group-text">
-                <font-awesome-icon
-                  @click="togglePipette('end')"
-                  :class="{ 'text-info': pipetteEnd }"
-                  icon="bullseye"
-                ></font-awesome-icon>
-              </span>
-            </div>
-            <span class="text-left text-danger">
-              <small v-if="endrhmError && !endrhm">
-                <translate>Please enter an end point</translate>
-              </small>
-            </span>
-          </div>
-        </div>
-        <div
-          v-if="!editExistingStretch"
-          class="d-flex flex-row justify-content-between"
-        >
-          <div class="mt-2  mr-2 w-50  text-left">
-            <small class="text-muted">
-              <translate>Tolerance for snapping of waterway axis [m]</translate>
-            </small>
-            <input
-              class="form-control"
-              v-model.number="tolerance"
-              placeholder=""
-              type="number"
-              min="0"
-              step="any"
-              aria-label="tolerance"
-              id="tolerance"
-            />
-            <span class="text-left text-danger">
-              <small v-if="toleranceError && !tolerance">
-                <translate>Please enter a tolerance value</translate>
-              </small>
-            </span>
-          </div>
-        </div>
-        <div class="d-flex flex-row justify-content-between">
-          <div class="mt-2  mr-2 w-50  text-left">
-            <small class="text-muted">
-              <translate>Object name</translate>
-            </small>
-            <input
-              id="objbn"
-              type="text"
-              class="form-control"
-              placeholder=""
-              aria-label="objbn"
-              v-model="objbn"
-            />
-            <span class="text-left text-danger">
-              <small v-if="objbnError && !objbn">
-                <translate>Please enter an objectname</translate>
-              </small>
-            </span>
-          </div>
-          <div class="mt-2  ml-2 w-50  text-left">
-            <small class="text-muted">
-              <translate>National Object name</translate>
-            </small>
-            <input
-              id="nobjbn"
-              type="text"
-              class="form-control"
-              placeholder=""
-              aria-label="nobjbn"
-              v-model="nobjbn"
-            />
-          </div>
-        </div>
-        <div class="d-flex flex-row justify-content-between">
-          <div class="mt-2 mr-2 w-50 text-left">
-            <small class="text-muted"> <translate>Date info</translate> </small>
-            <input
-              id="date_info"
-              type="date"
-              class="form-control"
-              placeholder="date_info"
-              aria-label="date_info"
-              v-model="date_info"
-            />
-            <span class="text-left text-danger">
-              <small v-if="date_infoError && !date_info">
-                <translate>Please enter a date</translate>
-              </small>
-            </span>
-          </div>
-          <div class="mt-2 ml-2 w-50 text-left">
-            <small class="text-muted"> <translate>Source</translate> </small>
-            <input
-              id="source"
-              type="text"
-              class="form-control"
-              placeholder="source"
-              aria-label="source"
-              v-model="source"
-            />
-            <span class="text-left text-danger">
-              <small v-if="sourceError && !source">
-                <translate>Please enter a source</translate>
-              </small>
-            </span>
-          </div>
-        </div>
-      </div>
-      <div class="text-right mt-2 mr-3 mb-3">
-        <button @click="edit = false" class="btn btn-warning mr-2">Back</button>
-        <button
-          @click="save"
-          type="submit"
-          class="shadow-sm btn btn-info submit-button"
-        >
-          <translate>Submit</translate>
-        </button>
-      </div>
-    </div>
-    <div class="text-right mr-3">
-      <button v-if="!edit" @click="startEdit()" class="btn btn-info">
-        <translate>New stretch</translate>
-      </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, 2019 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- * Tom Gottfried <tom.gottfried@intevation.de>
- */
-import { mapState, mapGetters } from "vuex";
-import { displayError, displayInfo } from "@/lib/errors";
-import { LAYERS } from "@/store/map";
-import { HTTP } from "@/lib/http";
-import { sortTable } from "@/lib/mixins";
-
-export default {
-  name: "importstretches",
-  mixins: [sortTable],
-  data() {
-    return {
-      staging: [],
-      edit: false,
-      editExistingStretch: false,
-      id: "",
-      funktion: "",
-      startrhm: "",
-      endrhm: "",
-      tolerance: 5,
-      objbn: "",
-      nobjbn: "",
-      countryCode: "",
-      date_info: new Date().toISOString().split("T")[0],
-      source: "",
-      pipetteStart: false,
-      pipetteEnd: false,
-      idError: false,
-      funktionError: false,
-      startrhmError: false,
-      endrhmError: false,
-      toleranceError: false,
-      objbnError: false,
-      nobjbnError: false,
-      date_infoError: false,
-      sourceError: false,
-      countryCodeError: false
-    };
-  },
-  computed: {
-    ...mapState("application", ["searchQuery"]),
-    ...mapState("map", ["identifiedFeatures", "currentMeasurement"]),
-    ...mapGetters("user", ["isSysAdmin"]),
-    ...mapState("imports", ["stretches"]),
-    defineStretchesLabel() {
-      return this.$gettext("Define Stretches");
-    },
-    nameLabel() {
-      return this.$gettext("Name");
-    },
-    dateLabel() {
-      return this.$gettext("Date");
-    },
-    sourceorganizationLabel() {
-      return this.$gettext("Source organization");
-    },
-    stretchesInStaging() {
-      const result = [];
-      for (let stretch of this.stretches) {
-        for (let s of this.staging) {
-          if (s.kind == "st" && s.summary.stretch == stretch.properties.name) {
-            result.push({ name: s.summary.stretch, id: s.id });
-          }
-        }
-      }
-      return result;
-    }
-  },
-  watch: {
-    identifiedFeatures() {
-      const filterDistanceMarks = x => {
-        return /^distance_marks/.test(x["id_"]);
-      };
-      const distanceMark = this.identifiedFeatures.filter(filterDistanceMarks);
-      if (distanceMark.length > 0) {
-        const value = distanceMark[0].getProperties()["location"];
-        this.startrhm = this.pipetteStart ? value : this.startrhm;
-        this.endrhm = this.pipetteEnd ? value : this.endrhm;
-        this.pipetteStart = false;
-        this.pipetteEnd = false;
-      }
-    }
-  },
-  methods: {
-    filteredStretches() {
-      return this.stretches.filter(s => {
-        return (s.properties.name + s.properties.source_organization)
-          .toLowerCase()
-          .includes(this.searchQuery.toLowerCase());
-      });
-    },
-    gotoStaging(id) {
-      this.$router.push("/review/" + id);
-    },
-    isInStaging(stretchname) {
-      for (let s of this.stretchesInStaging) {
-        if (s.name == stretchname) return true;
-      }
-      return false;
-    },
-    getStagingLink(stretchname) {
-      for (let s of this.stretchesInStaging) {
-        if (s.name == stretchname) return s.id;
-      }
-    },
-    loadStagingData() {
-      return new Promise((resolve, reject) => {
-        HTTP.get("/imports?states=pending", {
-          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-        })
-          .then(response => {
-            const { imports } = response.data;
-            this.staging = imports;
-            resolve(response);
-          })
-          .catch(error => {
-            reject(error);
-          });
-      });
-    },
-    editStretch(stretch) {
-      const properties = stretch.properties;
-      this.date_info = properties.date_info.split("T")[0];
-      this.id = properties.name;
-      this.nobjbn = properties.nobjnam;
-      this.objbn = properties.objnam;
-      this.countryCode = properties.countries;
-      this.source = properties["source_organization"];
-      this.edit = true;
-      this.startrhm = properties.lower;
-      this.endrhm = properties.upper;
-      this.editExistingStretch = true;
-    },
-    deleteStretch(stretch) {
-      this.$store.commit("application/popup", {
-        icon: "trash",
-        title: this.$gettext("Delete Stretch"),
-        content:
-          this.$gettext("Do you really want to delete this stretch:") +
-          `<br>
-        <b>${stretch.properties.name}, ${
-            stretch.properties.source_organization
-          } (${stretch.properties.countries})</b>`,
-        confirm: {
-          label: this.$gettext("Delete"),
-          icon: "trash",
-          callback: () => {
-            displayInfo({
-              title: this.$gettext("Not implemented"),
-              message: this.$gettext("Deleting ") + stretch.id
-            });
-          }
-        },
-        cancel: {
-          label: this.$gettext("Cancel"),
-          icon: "times"
-        }
-      });
-    },
-    moveMapToStretch(stretch) {
-      this.$store.commit("map/setLayerVisible", LAYERS.STRETCHES);
-      this.$store.commit("map/moveToExtent", {
-        feature: stretch,
-        zoom: 17,
-        preventZoomOut: true
-      });
-    },
-    loadStretches() {
-      return new Promise((resolve, reject) => {
-        this.$store
-          .dispatch("imports/loadStretches")
-          .then(response => {
-            resolve(response);
-          })
-          .catch(error => {
-            reject(error);
-          });
-      });
-    },
-    clean() {
-      this.id = "";
-      this.edit = false;
-      this.editExistingStretch = false;
-      this.funktion = "";
-      this.startrhm = "";
-      this.tolerance = 5;
-      this.endrhm = "";
-      this.objbn = "";
-      this.nobjbn = "";
-      this.countryCode = "";
-      this.date_info = new Date().toISOString().split("T")[0];
-      this.source = "";
-      this.pipetteStart = false;
-      this.pipetteEnd = false;
-      this.idError = false;
-      this.funktionError = false;
-      this.startrhmError = false;
-      this.endrhmError = false;
-      this.toleranceError = false;
-      this.objbnError = false;
-      this.nobjbnError = false;
-      this.date_infoError = false;
-      this.sourceError = false;
-      this.countryCodeError = false;
-    },
-    startEdit() {
-      this.clean();
-      this.edit = true;
-    },
-    togglePipette(t) {
-      this.$store.commit("map/setLayerVisible", LAYERS.DISTANCEMARKSAXIS);
-      if (t === "start") {
-        this.pipetteStart = !this.pipetteStart;
-        this.pipetteEnd = false;
-      } else {
-        this.pipetteEnd = !this.pipetteEnd;
-        this.pipetteStart = false;
-      }
-    },
-    validate() {
-      const fields = [
-        "id",
-        "funktion",
-        "startrhm",
-        "tolerance",
-        "endrhm",
-        "objbn",
-        "nobjbn",
-        "countryCode",
-        "date_info",
-        "source"
-      ];
-      fields.forEach(field => {
-        if (!this[field]) {
-          this[field + "Error"] = true;
-        } else {
-          this[field + "Error"] = false;
-        }
-      });
-    },
-    save() {
-      this.validate();
-      if (
-        !this.id ||
-        !this.startrhm ||
-        !this.endrhm ||
-        (!this.tolerance && this.editExistingStretch) ||
-        !this.source ||
-        !this.date_info ||
-        !this.objbn ||
-        !this.countryCode
-      )
-        return;
-      const data = {
-        name: this.id,
-        from: this.startrhm,
-        to: this.endrhm,
-        "source-organization": this.source,
-        "date-info": this.date_info,
-        objnam: this.objbn,
-        nobjnam: this.nobjbn,
-        countries: this.countryCode.split(",").map(x => {
-          return x.trim();
-        })
-      };
-      if (!this.editExistingStretch) {
-        data["tolerance"] = this.tolerance;
-      }
-      this.$store
-        .dispatch("imports/saveStretch", data)
-        .then(() => {
-          displayInfo({
-            title: this.$gettext("Import"),
-            message: this.$gettext("Starting import of stretch")
-          });
-          this.clean();
-          this.loadStretches().then(() => {
-            this.edit = false;
-          });
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    }
-  },
-  mounted() {
-    this.edit = false;
-    this.loadStretches().catch(error => {
-      const { status, data } = error.response;
-      displayError({
-        title: this.$gettext("Backend Error"),
-        message: `${status}: ${data.message || data}`
-      });
-    });
-    this.loadStagingData().catch(error => {
-      const { status, data } = error.response;
-      displayError({
-        title: this.$gettext("Backend Error"),
-        message: `${status}: ${data.message || data}`
-      });
-    });
-  }
-};
-</script>
-
-<style lang="scss" scoped>
-.linkto {
-  cursor: pointer;
-}
-</style>
--- a/client/src/components/ImportWaterwayProfiles.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,186 +0,0 @@
-<template>
-  <div class="d-flex flex-row">
-    <Spacer></Spacer>
-    <div class="card sysconfig mt-2 shadow-xs w-100 h-100 mr-2">
-      <UIBoxHeader icon="upload" :title="importWaterwayProfilesLabel" />
-      <div class="card-body stretches-card">
-        <div class="w-95 ml-auto mr-auto mt-4 mb-4">
-          <div class="mb-4">
-            <div class="d-flex flex-row">
-              <div class="flex-column w-100">
-                <div class="flex-row text-left">
-                  <small class="text-muted"> <translate>URL</translate> </small>
-                </div>
-                <div class="w-100">
-                  <input class="form-control" type="url" v-model="url" />
-                </div>
-              </div>
-            </div>
-            <div v-if="!url" class="d-flex flex-row">
-              <small
-                ><translate class="text-danger"
-                  >Please enter a URL</translate
-                ></small
-              >
-            </div>
-            <div class="d-flex flex-row">
-              <div class="flex-column mt-3 mr-3 w-50">
-                <div class="flex-row text-left">
-                  <small class="text-muted">
-                    <translate>Featuretype</translate>
-                  </small>
-                </div>
-                <div class="w-100">
-                  <input
-                    class="form-control"
-                    type="text"
-                    v-model="featureType"
-                  />
-                </div>
-                <div v-if="!featureType" class="d-flex flex-row">
-                  <small
-                    ><translate class="text-danger"
-                      >Please enter a Featuretype</translate
-                    ></small
-                  >
-                </div>
-              </div>
-              <div class="flex-column mt-3 w-50">
-                <div class="flex-row text-left">
-                  <small class="text-muted">
-                    <translate>SortBy</translate>
-                  </small>
-                </div>
-                <div class="w-100">
-                  <input class="form-control" type="text" v-model="sortBy" />
-                </div>
-                <div v-if="!sortBy" class="d-flex flex-row">
-                  <small
-                    ><translate class="text-danger"
-                      >Please enter SortBy</translate
-                    ></small
-                  >
-                </div>
-              </div>
-            </div>
-          </div>
-          <div class="d-flex flex-row text-left">
-            <div class="mt-3 mb-3 flex-column w-100">
-              <div class="custom-file">
-                <input
-                  accept=".csv"
-                  type="file"
-                  @change="fileSelected"
-                  class="custom-file-input"
-                  id="uploadFile"
-                />
-                <label class="pointer custom-file-label" for="uploadFile">
-                  {{ uploadLabel }}
-                </label>
-              </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 { displayError, displayInfo } from "@/lib/errors.js";
-import { HTTP } from "@/lib/http";
-
-export default {
-  name: "importwaterwayprofiles",
-  data() {
-    return {
-      url: "https://service.d4d-portal.info/wamos/wfs/",
-      sortBy: "hydro_scamin",
-      featureType: "ws-wamos:ienc_wtwprf",
-      disableUploadButton: false,
-      uploadLabel: this.$gettext("choose file to upload"),
-      uploadFile: null
-    };
-  },
-  computed: {
-    importWaterwayProfilesLabel() {
-      return this.$gettext("Import Waterway Profiles");
-    }
-  },
-  methods: {
-    fileSelected(e) {
-      const files = e.target.files || e.dataTransfer.files;
-      if (!files) return;
-      this.uploadLabel = files[0].name;
-      this.uploadFile = files[0];
-    },
-    submit() {
-      if (!this.url || !this.featureType || !this.sortBy || !this.uploadFile)
-        return;
-      let formData = new FormData();
-      formData.append("wp", this.uploadFile);
-      formData.append("url", this.url);
-      formData.append("feature-type", this.featureType);
-      formData.append("sort-by", this.sortBy);
-      HTTP.post("/imports/wp", formData, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-Type": "multipart/form-data"
-        }
-      })
-        .then(() => {
-          displayInfo({
-            title: this.$gettext("Import"),
-            message:
-              this.uploadLabel + this.$gettext(" was successfully uploaded.")
-          });
-          this.url = "https://service.d4d-portal.info/wamos/wfs/";
-          this.uploadFile = null;
-          this.uploadLabel = this.$gettext("choose file to upload");
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          const messages = data.messages ? data.messages.join(", ") : "";
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${messages}`
-          });
-        });
-    }
-  },
-  components: {
-    Spacer: () => import("./Spacer")
-  }
-};
-</script>
-
-<style lang="scss" scoped></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/KeyboardHandler.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,98 @@
+<template>
+  <transition name="fade">
+    <div class="notice" v-if="showNotice">
+      <span>{{ noticeText }}</span>
+    </div>
+  </transition>
+</template>
+
+<style lang="sass" scoped>
+.notice
+  position: absolute
+  top: 0
+  width: 100%
+  text-align: center
+  z-index: 1
+  font-size: 11px
+  line-height: 11px
+  > span
+    opacity: 0.5
+    background: white
+    display: inline-block
+    border-bottom-right-radius: 0.25rem
+    border-bottom-left-radius: 0.25rem
+    padding: 3px 5px
+</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 {
+  computed: {
+    ...mapState("application", ["paneSetup"]),
+    ...mapState("map", [
+      "openLayersMaps",
+      "lineToolEnabled",
+      "polygonToolEnabled",
+      "cutToolEnabled"
+    ]),
+    showNotice() {
+      return (
+        this.lineToolEnabled ||
+        this.polygonToolEnabled ||
+        this.cutToolEnabled ||
+        this.paneSetup.includes("COMPARESURVEYS")
+      );
+    },
+    noticeText() {
+      if (
+        this.lineToolEnabled ||
+        this.polygonToolEnabled ||
+        this.cutToolEnabled
+      ) {
+        return this.$gettext("Press ESC to stop drawing.");
+      } else if (this.paneSetup.includes("COMPARESURVEYS")) {
+        return this.$gettext("Press ESC to close compare view.");
+      }
+    }
+  },
+  mounted() {
+    window.addEventListener("keydown", e => {
+      // Escape
+      if (e.keyCode === 27) {
+        if (
+          this.lineToolEnabled ||
+          this.polygonToolEnabled ||
+          this.cutToolEnabled
+        ) {
+          this.$store.commit("map/lineToolEnabled", false);
+          this.$store.commit("map/polygonToolEnabled", false);
+          this.$store.commit("map/cutToolEnabled", false);
+          this.$store.commit("map/setCurrentMeasurement", null);
+          this.openLayersMaps.forEach(m => {
+            m.getLayer("DRAWTOOL")
+              .getSource()
+              .clear();
+          });
+        } else if (this.paneSetup.includes("COMPARESURVEYS")) {
+          this.$store.commit("fairwayprofile/additionalSurvey", null);
+        }
+      }
+    });
+  }
+};
+</script>
--- a/client/src/components/Login.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/Login.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,4 +1,4 @@
-(<template>
+<template>
   <div class="d-flex flex-column login bg-white shadow">
     <div class="m-5">
       <!-- logo section -->
@@ -84,8 +84,8 @@
         <img :src="secondaryLogo" />
       </div>
     </div>
-  </div> </template
->)
+  </div>
+</template>
 
 <style lang="scss" scoped>
 .login {
@@ -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";
+import { displayError } from "@/lib/errors";
 
 const UNAUTHORIZED = 401;
 
@@ -193,7 +193,8 @@
         .dispatch("user/login", { user, password })
         .then(() => {
           this.loginFailed = false;
-          this.$router.push("/");
+          this.$router.push(localStorage.getItem("tempRoute") || "/");
+          localStorage.removeItem("tempRoute");
         })
         .catch(error => {
           this.loginFailed = true;
--- a/client/src/components/Logs.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/Logs.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,7 +1,7 @@
 <template>
   <div class="main d-flex flex-column">
     <div class="d-flex flex-row">
-      <Spacer></Spacer>
+      <Spacer />
       <div class="card logs shadow-xs mt-2 mr-2">
         <UIBoxHeader icon="book" title="Logs" />
         <div class="logoutput text-left bg-white">
@@ -14,8 +14,11 @@
             <ul class="nav nav-pills">
               <li class="nav-item">
                 <a
+                  id="accesslog"
                   :class="accesslogStyle"
-                  @click="fetch('system/log/apache2/access.log', 'accesslog')"
+                  @click.prevent="
+                    fetch('system/log/apache2/access.log', 'accesslog')
+                  "
                   href="#"
                 >
                   <translate>Accesslog</translate>
@@ -23,8 +26,11 @@
               </li>
               <li class="nav-item">
                 <a
+                  id="errorlog"
                   :class="errorlogStyle"
-                  @click="fetch('system/log/apache2/error.log', 'errorlog')"
+                  @click.prevent="
+                    fetch('system/log/apache2/error.log', 'errorlog')
+                  "
                   href="#"
                 >
                   <translate>Errorlog</translate>
@@ -65,6 +71,7 @@
 
 .logs {
   height: 85vh;
+  width: 100vw;
 }
 
 #code {
@@ -113,10 +120,11 @@
  * Thomas Junk <thomas.junk@intevation.de>
  */
 import { mapState } from "vuex";
-import { HTTP } from "@/lib/http.js";
+import { HTTP } from "@/lib/http";
 import "../../node_modules/highlight.js/styles/paraiso-dark.css";
 import Vue from "vue";
 import VueHighlightJS from "vue-highlightjs";
+import { displayError } from "@/lib/errors";
 Vue.use(VueHighlightJS);
 
 const ACCESSLOG = "accesslog";
@@ -149,7 +157,13 @@
           this.refreshed = new Date().toLocaleString();
           this.currentFile = file;
         })
-        .catch();
+        .catch(e => {
+          const { status, data } = e.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status} ${data.message || data}`
+          });
+        });
     },
     disallow(e) {
       e.target.blur();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Main.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,130 @@
+<template>
+  <div id="panes" :class="'d-flex position-absolute rotate' + paneRotate">
+    <Pane :pane="panes[0]" :key="panes[0].id" :class="paneClasses[0]" />
+    <Pane
+      :pane="panes[1]"
+      :key="panes[1].id"
+      :class="paneClasses[1]"
+      v-if="panes.length >= 2"
+    />
+    <Pane
+      :pane="panes[2]"
+      :key="panes[2].id"
+      :class="paneClasses[2]"
+      v-if="panes.length >= 3"
+    />
+    <Pane
+      :pane="panes[3]"
+      :key="panes[3].id"
+      :class="paneClasses[3]"
+      v-if="panes.length === 4"
+    />
+  </div>
+</template>
+
+<style lang="sass">
+#panes
+  top: -1px
+  right: -1px
+  bottom: -1px
+  left: -1px
+  z-index: 1
+  &.rotate1
+    flex-wrap: wrap
+    flex-direction: row
+  &.rotate2
+    flex-wrap: wrap-reverse
+    flex-direction: column
+  &.rotate3
+    flex-wrap: wrap-reverse
+    flex-direction: row-reverse
+  &.rotate4
+    flex-wrap: wrap
+    flex-direction: column-reverse
+  .pane
+    border: solid 1px #dee2e6
+    background: #fff
+</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>
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+
+import { mapState } from "vuex";
+import * as paneSetups from "./paneSetups";
+
+export default {
+  components: {
+    Pane: () => import("./Pane")
+  },
+  computed: {
+    ...mapState("application", ["paneSetup", "paneRotate"]),
+    panes() {
+      return Object.values(paneSetups[this.paneSetup]);
+    },
+    paneClasses() {
+      if (this.paneSetup === "DEFAULT") {
+        return ["wh-100"];
+      }
+
+      if (this.paneSetup === "COMPARESURVEYS") {
+        return [2, 4].includes(this.paneRotate)
+          ? ["w-100 h-50", "w-100 h-50"]
+          : ["w-50 h-100", "w-50 h-100"];
+      }
+
+      if (this.paneSetup === "FAIRWAYPROFILE") {
+        return [1, 3].includes(this.paneRotate)
+          ? ["w-100 h-50", "w-100 h-50"]
+          : ["w-50 h-100", "w-50 h-100"];
+      }
+
+      if (this.paneSetup === "AVAILABLEFAIRWAYDEPTH") {
+        return [1, 3].includes(this.paneRotate)
+          ? ["w-100 h-50", "w-100 h-50"]
+          : ["w-50 h-100", "w-50 h-100"];
+      }
+
+      if (this.paneSetup === "AVAILABLEFAIRWAYDEPTHLNWL") {
+        return [1, 3].includes(this.paneRotate)
+          ? ["w-100 h-50", "w-100 h-50"]
+          : ["w-50 h-100", "w-50 h-100"];
+      }
+
+      if (this.paneSetup === "COMPARESURVEYS_FAIRWAYPROFILE") {
+        return [1, 3].includes(this.paneRotate)
+          ? ["wh-50", "wh-50", "w-100 h-50"]
+          : ["wh-50", "wh-50", "w-50 h-100"];
+      }
+
+      if (
+        ["GAUGE_WATERLEVEL", "GAUGE_HYDROLOGICALCONDITIONS"].includes(
+          this.paneSetup
+        )
+      ) {
+        return [1, 3].includes(this.paneRotate)
+          ? ["w-100 h-50", "w-100 h-50"]
+          : ["w-50 h-100", "w-50 h-100"];
+      }
+
+      if (this.paneSetup === "GAUGE_WATERLEVEL_HYDROLOGICALCONDITIONS") {
+        return [1, 3].includes(this.paneRotate)
+          ? ["w-100 h-50", "wh-50", "wh-50"]
+          : ["h-100 w-50", "wh-50", "wh-50"];
+      }
+    }
+  }
+};
+</script>
--- a/client/src/components/Maplayer.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,439 +0,0 @@
-<template>
-  <div
-    id="map"
-    :class="{
-      splitscreen: this.splitscreen,
-      nocursor: this.hasActiveInteractions
-    }"
-  ></div>
-</template>
-
-<style lang="sass" scoped>
-#map
-  height: 100vh
-
-  &.splitscreen
-    height: 50vh
-
-  &.nocursor
-    cursor: 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, 2019 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 { equalTo } from "ol/format/filter.js";
-import { Stroke, Style, Fill } from "ol/style.js";
-import { displayError } from "@/lib/errors.js";
-import { LAYERS } from "@/store/map.js";
-
-/* for the sake of debugging */
-/* eslint-disable no-console */
-export default {
-  name: "maplayer",
-  data() {
-    return {
-      projection: "EPSG:3857",
-      splitscreen: false
-    };
-  },
-  computed: {
-    ...mapGetters("map", ["getLayerByName", "getVSourceByName"]),
-    ...mapState("map", [
-      "initialLoad",
-      "extent",
-      "layers",
-      "openLayersMap",
-      "lineTool",
-      "polygonTool",
-      "cutTool"
-    ]),
-    ...mapState("bottlenecks", ["selectedSurvey"]),
-    ...mapState("application", ["showSplitscreen"]),
-    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(LAYERS.BOTTLENECKISOLINE);
-      const wmsSrc = layer.data.getSource();
-      const exists = bottleneck_id != "does_not_exist";
-
-      if (exists) {
-        wmsSrc.updateParams({
-          cql_filter:
-            "date_info='" +
-            datestr +
-            "' AND bottleneck_id='" +
-            bottleneck_id +
-            "'"
-        });
-      }
-      layer.isVisible = exists;
-      layer.data.setVisible(exists);
-    }
-  },
-  watch: {
-    showSplitscreen(show) {
-      if (show) {
-        setTimeout(() => {
-          this.splitscreen = true;
-        }, 350);
-      } else {
-        this.splitscreen = false;
-      }
-    },
-    splitscreen() {
-      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],
-        minZoom: 5, // restrict zooming out to ~size of Europe for width 1000px
-        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);
-
-    if (this.initialLoad) {
-      this.$store.commit("map/initialLoad", false);
-      var currentUser = this.$store.state.user.user;
-      HTTP.get("/users/" + currentUser, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-type": "text/xml; charset=UTF-8"
-        }
-      })
-        .then(response => {
-          this.$store.commit("map/moveToBoundingBox", {
-            boundingBox: [
-              response.data.extent.x1,
-              response.data.extent.y1,
-              response.data.extent.x2,
-              response.data.extent.y2
-            ],
-            zoom: 17,
-            preventZoomOut: true
-          });
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    }
-
-    // TODO make display of layers more dynamic, e.g. from a list
-
-    // load different fairway dimension layers (level of service)
-    [
-      LAYERS.FAIRWAYDIMENSIONSLOS1,
-      LAYERS.FAIRWAYDIMENSIONSLOS2,
-      LAYERS.FAIRWAYDIMENSIONSLOS3
-    ].forEach((los, i) => {
-      // loading the full WFS layer without bboxStrategy
-      var source = this.getVSourceByName(los);
-      /*eslint-disable no-unused-vars */
-      var loader = function(extent, resolution, projection) {
-        var featureRequest = new WFS().writeGetFeature({
-          srsName: "EPSG:3857",
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["fairway_dimensions"],
-          outputFormat: "application/json",
-          filter: equalTo("level_of_service", i + 1)
-        });
-
-        featureRequest["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 => {
-          source.addFeatures(
-            new GeoJSON().readFeatures(JSON.stringify(response.data))
-          );
-          // would scale to the extend of all resulting features
-          // this.openLayersMap.getView().fit(vectorSrc.getExtent());
-        });
-      };
-
-      layer = this.getLayerByName(los);
-      layer.data.getSource().setLoader(loader);
-      layer.data.setVisible(layer.isVisible);
-    });
-
-    // load following layers with bboxStrategy (using our request builder)
-    var layer = null;
-
-    layer = this.getLayerByName(LAYERS.WATERWAYAREA);
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["waterway_area"],
-          geometryName: "area"
-        },
-        "/internal/wfs",
-        layer.data.getSource()
-      )
-    );
-    layer.data.setVisible(layer.isVisible);
-
-    layer = this.getLayerByName(LAYERS.WATERWAYAXIS);
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["waterway_axis"],
-          geometryName: "wtwaxs"
-        },
-        "/internal/wfs",
-        layer.data.getSource()
-      )
-    );
-    layer.data.setVisible(layer.isVisible);
-
-    layer = this.getLayerByName(LAYERS.WATERWAYPROFILES);
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["waterway_profiles"],
-          geometryName: "geom"
-        },
-        "/internal/wfs",
-        layer.data.getSource()
-      )
-    );
-    layer.data.setVisible(layer.isVisible);
-
-    layer = this.getLayerByName(LAYERS.DISTANCEMARKS);
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["distance_marks_ashore_geoserver"],
-          geometryName: "geom"
-        },
-        "/internal/wfs",
-        layer.data.getSource()
-      )
-    );
-    layer.data.setVisible(layer.isVisible);
-
-    layer = this.getLayerByName(LAYERS.DISTANCEMARKSAXIS);
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["distance_marks_geoserver"],
-          geometryName: "geom"
-        },
-        "/internal/wfs",
-        layer.data.getSource()
-      )
-    );
-    layer.data.setVisible(layer.isVisible);
-
-    layer = this.getLayerByName(LAYERS.GAUGES);
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["gauges_geoserver"],
-          geometryName: "geom"
-        },
-        "/internal/wfs",
-        layer.data.getSource()
-      )
-    );
-    layer.data.setVisible(layer.isVisible);
-
-    layer = this.getLayerByName(LAYERS.STRETCHES);
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["stretches_geoserver"],
-          geometryName: "area"
-        },
-        "/internal/wfs",
-        layer.data.getSource()
-      )
-    );
-    layer.data.setVisible(layer.isVisible);
-
-    layer = this.getLayerByName(LAYERS.BOTTLENECKSTATUS);
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["bottlenecks_geoserver"],
-          geometryName: "area"
-        },
-        "/internal/wfs",
-        layer.data.getSource()
-      )
-    );
-
-    layer = this.getLayerByName(LAYERS.BOTTLENECKS);
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["bottlenecks_geoserver"],
-          geometryName: "area"
-        },
-        "/internal/wfs",
-        layer.data.getSource()
-      )
-    );
-    layer.data.setVisible(layer.isVisible);
-    HTTP.get("/system/style/Bottlenecks/stroke", {
-      headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-    })
-      .then(response => {
-        let btlnStrokeC = response.data.code;
-        HTTP.get("/system/style/Bottlenecks/fill", {
-          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-        })
-          .then(response => {
-            let btlnFillC = response.data.code;
-            var newStyle = new Style({
-              stroke: new Stroke({
-                color: btlnStrokeC,
-                width: 4
-              }),
-              fill: new Fill({
-                color: btlnFillC
-              })
-            });
-            layer.data.setStyle(newStyle);
-          })
-          .catch(error => {
-            console.log(error);
-          });
-      })
-      .catch(error => {
-        console.log(error);
-      });
-
-    // so none is shown
-    this.updateBottleneckFilter("does_not_exist", "1999-10-01");
-    this.$store.dispatch("map/disableIdentifyTool");
-    this.$store.dispatch("map/enableIdentifyTool");
-  }
-};
-</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/PageNotFound.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,22 @@
+<template>
+  <div class="main d-flex flex-row" style="position: relative;">
+    <Spacer />
+    <div class="my-auto mx-auto">
+      <h1>
+        <font-awesome-icon icon="frown-open" fixed-width />
+        We are sorry. The ressource you requested could not be found.
+      </h1>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "pagenotfound",
+  components: {
+    Spacer: () => import("@/components/Spacer")
+  }
+};
+</script>
+
+<style></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Pane.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,35 @@
+<template>
+  <div :id="pane.id" class="pane d-flex position-relative">
+    <component :is="pane.component" :key="pane.id" />
+  </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>
+ */
+
+export default {
+  props: ["pane"],
+  components: {
+    // all components that are supposed to be displayed in a pane must be registered here
+    Map: () => import("./map/Map"),
+    Fairwayprofile: () => import("./fairway/Fairwayprofile"),
+    AvailableFairwayDepth: () => import("./fairway/AvailableFairwayDepth"),
+    AvailableFairwayDepthLNWL: () =>
+      import("./fairway/AvailableFairwayDepthLNWL"),
+    Waterlevel: () => import("./gauge/Waterlevel"),
+    HydrologicalConditions: () => import("./gauge/HydrologicalConditions")
+  }
+};
+</script>
--- a/client/src/components/Pdftool.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/Pdftool.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -77,6 +77,13 @@
   </div>
 </template>
 
+<style lang="scss" scoped>
+input,
+select {
+  font-size: 0.8em;
+}
+</style>
+
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -93,14 +100,13 @@
  * * Bernhard E. Reiter <bernhard@intevation.de>
  * * Fadi Abbud <fadi.abbud@intevation.de>
  */
-import { mapGetters, mapState } from "vuex";
+import { mapState, mapGetters } from "vuex";
 import jsPDF from "jspdf";
-import "@/lib/font-linbiolinum.js";
-import { getPointResolution } from "ol/proj.js";
-import locale2 from "locale2";
-import { HTTP } from "../lib/http";
-import { displayError } from "@/lib/errors.js";
-import { LAYERS } from "@/store/map.js";
+import "@/lib/font-linbiolinum";
+import { getPointResolution } from "ol/proj";
+import { HTTP } from "@/lib/http";
+import { displayError } from "@/lib/errors";
+import { pdfgen } from "@/lib/mixins";
 
 var paperSizes = {
   // in millimeter, landscape [width, height]
@@ -109,6 +115,7 @@
 };
 
 export default {
+  mixins: [pdfgen],
   name: "pdftool",
   data() {
     return {
@@ -166,9 +173,8 @@
   computed: {
     ...mapState("application", ["showPdfTool", "logoForPDF"]),
     ...mapState("bottlenecks", ["selectedBottleneck", "selectedSurvey"]),
-    ...mapState("map", ["openLayersMap", "isolinesLegendImgDataURL"]),
-    ...mapGetters("map", ["getLayerByName"]),
-    ...mapState("user", ["user"]),
+    ...mapState("map", ["isolinesLegendImgDataURL"]),
+    ...mapGetters("map", ["openLayersMap"]),
     generatePdfLable() {
       return this.$gettext("Generate PDF");
     },
@@ -205,12 +211,18 @@
     // applied to the rest of the form.
     applyTemplateToForm() {
       if (this.form.template) {
-        HTTP.get("/templates/print/" + this.form.template.name, {
-          headers: {
-            "X-Gemma-Auth": localStorage.getItem("token"),
-            "Content-type": "text/xml; charset=UTF-8"
+        HTTP.get(
+          "/templates/" +
+            this.form.template.type +
+            "/" +
+            this.form.template.name,
+          {
+            headers: {
+              "X-Gemma-Auth": localStorage.getItem("token"),
+              "Content-type": "text/xml; charset=UTF-8"
+            }
           }
-        })
+        )
           .then(response => {
             this.templateData = response.data.template_data;
             this.form.format = this.templateData.properties.format;
@@ -265,7 +277,7 @@
       //    which will generate the pdf and resets the map view
       // Step 2 which starts rendering a map with the necessary image size
 
-      var map = this.openLayersMap;
+      var map = this.openLayersMap();
       this.mapSize = map.getSize(); // size in pixels of the map in the DOM
       // Calculate the extent for the current view state and the passed size.
       // The size is the pixel dimensions of the box into which the calculated
@@ -437,12 +449,14 @@
       map.getView().fit(this.mapExtent, { size: mapSizeForPrint });
     },
     cancel() {
-      this.openLayersMap.un(
+      this.openLayersMap().un(
         this.rendercompleteListener.type,
         this.rendercompleteListener.listener
       );
-      this.openLayersMap.setSize(this.mapSize);
-      this.openLayersMap.getView().fit(this.mapExtent, { size: this.mapSize });
+      this.openLayersMap().setSize(this.mapSize);
+      this.openLayersMap()
+        .getView()
+        .fit(this.mapExtent, { size: this.mapSize });
       this.readyToGenerate = true;
     },
     // add the used map scale and papersize
@@ -459,125 +473,6 @@
         ")";
       this.addText(position, offset, width, fontSize, color, str);
     },
-    addRoundedBox(x, y, w, h, color, rounding, brcolor) {
-      // draws a rounded background box at (x,y) width x height
-      // using jsPDF units
-      this.pdf.doc.setDrawColor(brcolor);
-      this.pdf.doc.setFillColor(color);
-      this.pdf.doc.roundedRect(x, y, w, h, rounding, rounding, "FD");
-    },
-    // add some text at specific coordinates and determine how many wrolds in single line
-    addText(position, offset, width, fontSize, color, text) {
-      text = this.replacePlaceholders(text);
-
-      // split the incoming string to an array, each element is a string of
-      // words in a single line
-      this.pdf.doc.setTextColor(color);
-      this.pdf.doc.setFontSize(fontSize);
-      var textLines = this.pdf.doc.splitTextToSize(text, width);
-
-      // x/y defaults to offset for topleft corner (normal x/y coordinates)
-      let x = offset.x;
-      let y = offset.y;
-
-      // if position is on the right, x needs to be calculate with pdf width and
-      // the size of the element
-      if (["topright", "bottomright"].indexOf(position) !== -1) {
-        x = this.pdf.width - offset.x - width;
-      }
-      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
-        y = this.pdf.height - offset.y - this.getTextHeight(textLines.length);
-      }
-
-      this.pdf.doc.text(textLines, x, y, { baseline: "hanging" });
-    },
-    addBox(position, offset, width, height, rounding, color, brcolor) {
-      // x/y defaults to offset for topleft corner (normal x/y coordinates)
-      let x = offset.x;
-      let y = offset.y;
-
-      // if position is on the right, x needs to be calculate with pdf width and
-      // the size of the element
-      if (["topright", "bottomright"].indexOf(position) !== -1) {
-        x = this.pdf.width - offset.x - width;
-      }
-      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
-        y = this.pdf.height - offset.y - height;
-      }
-
-      this.addRoundedBox(x, y, width, height, color, rounding, brcolor);
-    },
-    // add some text at specific coordinates with a background box
-    addTextBox(
-      position,
-      offset,
-      width,
-      height,
-      rounding,
-      padding,
-      fontSize,
-      color,
-      background,
-      text,
-      brcolor
-    ) {
-      this.pdf.doc.setFontSize(fontSize);
-      text = this.replacePlaceholders(text);
-
-      if (!width) {
-        width = this.pdf.doc.getTextWidth(text) + 2 * padding;
-      }
-      let textWidth = width - 2 * padding;
-      if (!height) {
-        let textLines = this.pdf.doc.splitTextToSize(text, textWidth);
-        height = this.getTextHeight(textLines.length) + 2 * padding;
-      }
-
-      this.addBox(
-        position,
-        offset,
-        width,
-        height,
-        rounding,
-        background,
-        brcolor
-      );
-      this.addText(
-        position,
-        { x: offset.x + padding, y: offset.y + padding },
-        textWidth,
-        fontSize,
-        color,
-        text
-      );
-    },
-    addImage(url, format, position, offset, width, height) {
-      // x/y defaults to offset for topleft corner (normal x/y coordinates)
-      let x = offset.x;
-      let y = offset.y;
-
-      // if position is on the right, x needs to be calculate with pdf width and
-      // the size of the element
-      if (["topright", "bottomright"].indexOf(position) !== -1) {
-        x = this.pdf.width - offset.x - width;
-      }
-      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
-        y = this.pdf.height - offset.y - height;
-      }
-
-      let image = new Image();
-      if (url) {
-        image.src = url;
-      } else {
-        if (this.logoForPDF) {
-          image.src = this.logoForPDF;
-        } else {
-          image.src = "/img/gemma-logo-for-pdf.png";
-        }
-      }
-
-      this.pdf.doc.addImage(image, x, y, width, height);
-    },
     addScaleBar(scaleDenominator, position, offset, rounding, brcolor) {
       // scaleDenominator is the x in 1:x of the map scale
 
@@ -754,7 +649,9 @@
       if (
         this.selectedBottleneck &&
         this.selectedSurvey &&
-        this.getLayerByName(LAYERS.BOTTLENECKISOLINE).isVisible
+        this.openLayersMap()
+          .getLayer("BOTTLENECKISOLINE")
+          .getVisible()
       ) {
         // transforming into an HTMLImageElement only to find out
         // the width x height of the legend image
@@ -793,7 +690,9 @@
       if (
         this.selectedBottleneck &&
         this.selectedSurvey &&
-        this.getLayerByName(LAYERS.BOTTLENECKISOLINE).isVisible
+        this.openLayersMap()
+          .getLayer("BOTTLENECKISOLINE")
+          .getVisible()
       ) {
         let survey = this.selectedSurvey;
 
@@ -896,28 +795,6 @@
         );
       }
     },
-    replacePlaceholders(text) {
-      if (text.includes("{date}")) {
-        text = text.replace("{date}", new Date().toLocaleString(locale2));
-      }
-      //get only day,month and year from the Date object
-      if (text.includes("{date-minor}")) {
-        var date = new Date();
-        var dt =
-          (date.getDate() < 10 ? "0" : "") +
-          date.getDate() +
-          "." +
-          (date.getMonth() + 1 < 10 ? "0" : "") +
-          (date.getMonth() + 1) +
-          "." +
-          date.getFullYear();
-        text = text.replace("{date-minor}", dt.toLocaleString(locale2));
-      }
-      if (text.includes("{user}")) {
-        text = text.replace("{user}", this.user);
-      }
-      return text;
-    },
     getTextHeight(numberOfLines) {
       return (
         numberOfLines *
@@ -929,7 +806,7 @@
   mounted() {
     this.form.template = this.templates[0];
     this.templateData = this.form.template;
-    HTTP.get("/templates/print", {
+    HTTP.get("/templates/map", {
       headers: {
         "X-Gemma-Auth": localStorage.getItem("token"),
         "Content-type": "text/xml; charset=UTF-8"
--- a/client/src/components/Search.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/Search.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -2,7 +2,7 @@
   <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>
+        <font-awesome-icon icon="search" />
       </span>
     </div>
     <div
@@ -64,6 +64,12 @@
             class="mr-1"
             fixed-width
           />
+          <font-awesome-icon
+            icon="object-group"
+            v-if="entry.type === 'section'"
+            class="mr-1"
+            fixed-width
+          />
           {{ entry.name }}
         </a>
       </div>
@@ -74,6 +80,7 @@
 <style lang="scss" scoped>
 .searchcontainer {
   opacity: 0.96;
+  width: 668px;
 }
 
 .searchcontainer .searchbar {
@@ -82,8 +89,7 @@
 }
 
 .searchgroup {
-  margin-left: -3px;
-  width: 630px;
+  width: 635px;
   overflow: hidden;
 }
 
@@ -158,7 +164,7 @@
 import debounce from "lodash.debounce";
 import { mapState, mapGetters } from "vuex";
 
-import { displayError } from "@/lib/errors.js";
+import { displayError } from "@/lib/errors";
 import { HTTP } from "@/lib/http";
 import { format } from "date-fns";
 
@@ -200,7 +206,7 @@
     },
     searchbarContainerStyle() {
       return [
-        "input-group searchcontainer shadow-xs",
+        "input-group searchcontainer shadow-xs rounded",
         {
           "d-flex": this.contextBoxContent !== "imports",
           "d-none": this.contextBoxContent === "imports" && this.showContextBox,
@@ -299,7 +305,7 @@
         if (resultEntry.type === "rhm") zoom = 15;
         if (resultEntry.type === "city") zoom = 13;
         if (resultEntry.type === "gauge") zoom = 15;
-        this.$store.commit("map/moveMap", {
+        this.$store.dispatch("map/moveMap", {
           coordinates: resultEntry.geom.coordinates,
           zoom,
           preventZoomOut: true
--- a/client/src/components/Sidebar.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/Sidebar.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -8,127 +8,73 @@
         @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 class="fa-fw" icon="bars"></font-awesome-icon>
+        <font-awesome-icon class="fa-fw" icon="bars" />
       </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>
+          <font-awesome-icon class="mr-2" fixed-width icon="map-marked-alt" />
           <span class="fix-trans-space" v-translate>Map</span>
         </router-link>
         <router-link to="/bottlenecks">
-          <font-awesome-icon
-            class="fa-fw mr-2"
-            fixed-width
-            icon="ship"
-          ></font-awesome-icon>
+          <font-awesome-icon class="mr-2" fixed-width icon="ship" />
           <span class="fix-trans-space" v-translate>Bottlenecks</span>
         </router-link>
         <div v-if="isWaterwayAdmin">
           <router-link to="/imports/overview" class="position-relative">
             <font-awesome-icon
-              class="fa-fw mr-2"
+              class="mr-2"
               fixed-width
               icon="clipboard-check"
-            ></font-awesome-icon>
+            />
             <span class="fix-trans-space" v-translate>Import review</span>
             <span class="indicator" v-if="showSidebar && stagingNotifications">
               {{ stagingNotifications }}
             </span>
           </router-link>
+          <router-link to="/imports/configuration">
+            <font-awesome-icon class="mr-2" fixed-width icon="clock" />
+            <translate class="fix-trans-space">Imports</translate>
+          </router-link>
         </div>
         <div v-if="isSysAdmin">
           <router-link to="/stretches">
-            <font-awesome-icon
-              class="fa-fw mr-2"
-              fixed-width
-              icon="road"
-            ></font-awesome-icon>
+            <font-awesome-icon class="mr-2" fixed-width icon="road" />
             <span class="fix-trans-space" v-translate>Define stretches</span>
           </router-link>
         </div>
         <div v-if="isWaterwayAdmin">
-          <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>Soundingresults</span>
-          </router-link>
-          <router-link to="/importapprovedgaugemeasurement">
-            <font-awesome-icon
-              class="fa-fw mr-2"
-              fixed-width
-              icon="upload"
-            ></font-awesome-icon>
-            <span class="fix-trans-space" v-translate
-              >Approved Gaugemeasurements</span
-            >
+          <router-link to="/sections">
+            <font-awesome-icon class="mr-2" fixed-width icon="road" />
+            <span class="fix-trans-space" v-translate>Define sections</span>
           </router-link>
-          <router-link to="/importwaterwayprofiles">
-            <font-awesome-icon
-              class="fa-fw mr-2"
-              fixed-width
-              icon="upload"
-            ></font-awesome-icon>
-            <span class="fix-trans-space" v-translate>Waterway Profiles</span>
-          </router-link>
-          <router-link to="/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" />
         </div>
+        <small
+          class="text-muted pl-2 pb-1 d-block border-bottom"
+          v-if="isSysAdmin"
+        >
+          <translate>Systemadministration</translate>
+        </small>
         <div v-if="isSysAdmin">
           <router-link to="/usermanagement">
-            <font-awesome-icon
-              class="fa-fw mr-2"
-              fixed-width
-              icon="users-cog"
-            ></font-awesome-icon>
+            <font-awesome-icon class="mr-2" fixed-width icon="users-cog" />
             <span class="fix-trans-space" v-translate>Users</span>
           </router-link>
         </div>
         <div v-if="isWaterwayAdmin">
           <router-link to="/systemconfiguration">
-            <font-awesome-icon
-              class="fa-fw mr-2"
-              fixed-width
-              icon="wrench"
-            ></font-awesome-icon>
+            <font-awesome-icon class="mr-2" fixed-width icon="wrench" />
             <span class="fix-trans-space" v-translate>Configuration</span>
           </router-link>
         </div>
         <div v-if="isSysAdmin">
           <router-link to="/logs">
-            <font-awesome-icon
-              class="fa-fw mr-2"
-              fixed-width
-              icon="book"
-            ></font-awesome-icon>
+            <font-awesome-icon class="mr-2" fixed-width icon="book" />
             <span class="fix-trans-space" v-translate>Logs</span>
           </router-link>
         </div>
         <hr class="m-0" />
         <a @click="logoff" href="#" class="logout">
-          <font-awesome-icon
-            class="fa-fw mr-2"
-            fixed-width
-            icon="power-off"
-          ></font-awesome-icon>
+          <font-awesome-icon class="mr-2" fixed-width icon="power-off" />
           <span class="fix-trans-space" v-translate>Logout</span> {{ user }}
         </a>
       </div>
@@ -152,13 +98,12 @@
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
 import { mapGetters, mapState } from "vuex";
-import { logOff } from "@/lib/session.js";
+import { logOff } from "@/lib/session";
 import { displayError } from "@/lib/errors";
 import { HTTP } from "@/lib/http";
 
 export default {
   name: "sidebar",
-  props: ["routeName"],
   data() {
     return {
       stagingNotifications: null
@@ -198,7 +143,7 @@
       return (
         this.showContextBox &&
         this.contextBoxContent === item &&
-        this.routeName == "mainview"
+        this.$route.name == "mainview"
       );
     }
   },
--- a/client/src/components/Zoom.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-<template>
-  <div :class="['zoom-buttons shadow-xs', { splitscreen: showSplitscreen }]">
-    <button
-      class="zoom-button border-0 bg-white rounded-left ui-element"
-      @click="zoomOut"
-    >
-      <font-awesome-icon icon="minus"></font-awesome-icon>
-    </button>
-    <button
-      class="zoom-button border-0 bg-white ui-element border-right"
-      @click="refreshMap"
-    >
-      <font-awesome-icon icon="redo"></font-awesome-icon>
-    </button>
-    <button
-      class="zoom-button 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="sass" scoped>
-.zoom-buttons
-  position: absolute
-  bottom: $small-offset
-  left: 50%
-  margin-left: -($icon-width * 1.5)
-  margin-bottom: 0
-  transition: margin-bottom 0.3s
-  &.splitscreen
-    margin-bottom: 50vh
-
-  .zoom-button
-    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";
-import { Vector as VectorLayer } from "ol/layer.js";
-
-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;
-    },
-    refreshMap() {
-      let layers = this.openLayersMap.getLayers().getArray();
-      for (let i = 0; i < layers.length; i++) {
-        let layer = layers[i];
-        if (
-          layer instanceof VectorLayer &&
-          layer.get("source").loader_.name != "VOID"
-        ) {
-          layer.getSource().clear(true);
-          layer.getSource().refresh({ force: true });
-        }
-      }
-    }
-  }
-};
-</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/fairway/AvailableFairwayDepth.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,659 @@
+<template>
+  <div class="d-flex flex-column flex-fill">
+    <UIBoxHeader icon="chart-area" :title="title" :closeCallback="close" />
+    <UISpinnerOverlay v-if="loading" />
+    <div class="d-flex flex-fill">
+      <DiagramLegend>
+        <div v-for="(entry, index) in legend" :key="index" class="legend">
+          <span
+            :style="
+              `${legendStyle(
+                index
+              )}; border-radius: 0.25rem; width: 40px; height: 20px;`
+            "
+          ></span>
+          {{ entry }}
+        </div>
+        <div>
+          <select
+            @change="applyChange"
+            v-model="form.template"
+            class="form-control d-block custom-select-sm w-100 mt-1"
+          >
+            <option
+              v-for="template in templates"
+              :value="template"
+              :key="template.name"
+            >
+              {{ template.name }}
+            </option>
+          </select>
+          <button
+            @click="downloadPDF"
+            type="button"
+            class="btn btn-sm btn-info d-block w-100 mt-2"
+          >
+            <translate>Export to PDF</translate>
+          </button>
+          <a
+            :href="dataLink"
+            :download="csvFileName"
+            class="mt-2 btn btn-sm btn-info w-100"
+            >Download CSV</a
+          >
+        </div>
+      </DiagramLegend>
+      <div
+        ref="diagramContainer"
+        :id="containerId"
+        class="mx-auto my-auto diagram-container"
+      ></div>
+    </div>
+  </div>
+</template>
+
+<style></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>
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ * Fadi Abbud <fadi.abbud@intevation.de>
+ */
+import * as d3 from "d3";
+import app from "@/main";
+import debounce from "debounce";
+import { diagram } from "@/lib/mixins";
+import { mapState } from "vuex";
+import filters from "@/lib/filters.js";
+import jsPDF from "jspdf";
+import canvg from "canvg";
+import { pdfgen } from "@/lib/mixins";
+import { HTTP } from "@/lib/http";
+import { displayError } from "@/lib/errors";
+import { FREQUENCIES } from "@/store/fairwayavailability";
+
+const hoursInDays = x => x / 24;
+
+export default {
+  mixins: [diagram, pdfgen],
+  components: {
+    DiagramLegend: () => import("@/components/DiagramLegend")
+  },
+  data() {
+    return {
+      containerId: "availablefairwaydepth",
+      loading: false,
+      width: 1000,
+      height: 600,
+      paddingRight: 100,
+      spaceBetween: 80,
+      labelPaddingTop: 15,
+      scalePaddingLeft: 50,
+      paddingTop: 10,
+      diagram: null,
+      yScale: null,
+      barsWidth: 60,
+      dimensions: null,
+      ldcoffset: 3,
+      pdf: {
+        doc: null,
+        width: null,
+        height: null
+      },
+      form: {
+        template: null
+      },
+      templateData: null,
+      templates: [],
+      defaultTemplate: {
+        name: "Default",
+        properties: {
+          paperSize: "a4"
+        },
+        elements: [
+          {
+            type: "diagram",
+            position: "topleft",
+            offset: { x: 20, y: 60 },
+            width: 290,
+            height: 100
+          },
+          {
+            type: "diagramtitle",
+            position: "topleft",
+            offset: { x: 70, y: 20 },
+            fontsize: 20,
+            color: "steelblue"
+          },
+          {
+            type: "diagramlegend",
+            position: "topleft",
+            offset: { x: 30, y: 160 },
+            color: "black"
+          }
+        ]
+      }
+    };
+  },
+  created() {
+    window.addEventListener("resize", debounce(this.drawDiagram), 200);
+  },
+  mounted() {
+    this.drawDiagram();
+    this.templates[0] = this.defaultTemplate;
+    this.form.template = this.templates[0];
+    this.templateData = this.form.template;
+    HTTP.get("/templates/diagram", {
+      headers: {
+        "X-Gemma-Auth": localStorage.getItem("token"),
+        "Content-type": "text/xml; charset=UTF-8"
+      }
+    })
+      .then(response => {
+        if (response.data.length) {
+          this.templates = response.data;
+          this.form.template = this.templates[0];
+          this.templates[this.templates.length] = this.defaultTemplate;
+          this.applyChange();
+        }
+      })
+      .catch(e => {
+        const { status, data } = e.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      });
+  },
+  computed: {
+    ...mapState("fairwayavailability", [
+      "selectedFairwayAvailabilityFeature",
+      "fwData",
+      "from",
+      "to",
+      "frequency",
+      "csv",
+      "depthlimit1",
+      "depthlimit2",
+      "widthlimit1",
+      "widthlimit2"
+    ]),
+    legend() {
+      const d = [this.depthlimit1, this.depthlimit2].sort();
+      const w = [this.widthlimit1, this.widthlimit2].sort();
+      const lowerBound = [d[0], w[0]].filter(x => x).join(", ");
+      const upperBound = [d[1], w[1]].filter(x => x).join(", ");
+      return [
+        `> LDC`,
+        `< ${lowerBound}`,
+        `< ${upperBound}`,
+        `>= ${upperBound}`
+      ];
+    },
+    dataLink() {
+      return `data:text/csv;charset=utf-8, ${encodeURIComponent(this.csv)}`;
+    },
+    csvFileName() {
+      return `${this.$gettext("fairwayavailability")}-${
+        this.featureName
+      }-${filters.surveyDate(this.fromDate)}-${filters.surveyDate(
+        this.toDate
+      )}-${this.$gettext(this.frequency)}-.csv`;
+    },
+    frequencyToRange() {
+      const frequencies = {
+        [FREQUENCIES.MONTHLY]: [-33, 33],
+        [FREQUENCIES.QUARTERLY]: [-93, 93],
+        [FREQUENCIES.YEARLY]: [-370, 370]
+      };
+      return frequencies[this.frequency];
+    },
+    fromDate() {
+      return this.from;
+    },
+    toDate() {
+      return this.to;
+    },
+    availability() {
+      return this.plainAvailability;
+    },
+    title() {
+      return `Available Fairway Depth: ${
+        this.featureName
+      } (${filters.surveyDate(this.fromDate)} - ${filters.surveyDate(
+        this.toDate
+      )}) ${this.$gettext(this.frequency)}`;
+    },
+    featureName() {
+      if (this.selectedFairwayAvailabilityFeature == null) return "";
+      return this.selectedFairwayAvailabilityFeature.properties.name;
+    }
+  },
+  methods: {
+    applyChange() {
+      if (this.form.template.hasOwnProperty("properties")) {
+        this.templateData = this.defaultTemplate;
+        return;
+      }
+      if (this.form.template) {
+        HTTP.get("/templates/diagram/" + this.form.template.name, {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token"),
+            "Content-type": "text/xml; charset=UTF-8"
+          }
+        })
+          .then(response => {
+            this.templateData = response.data.template_data;
+          })
+          .catch(e => {
+            const { status, data } = e.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          });
+      }
+    },
+    downloadPDF() {
+      this.pdf.doc = new jsPDF(
+        "l",
+        "mm",
+        this.templateData.properties.paperSize
+      );
+      // pdf width and height in millimeter (landscape)
+      this.pdf.width =
+        this.templateData.properties.paperSize === "a3" ? 420 : 297;
+      this.pdf.height =
+        this.templateData.properties.paperSize === "a3" ? 297 : 210;
+      if (this.templateData) {
+        // default values if some are missing in template
+        let defaultFontSize = 11,
+          defaultColor = "black",
+          defaultWidth = 70,
+          defaultTextColor = "black",
+          defaultBorderColor = "white",
+          defaultBgColor = "white",
+          defaultRounding = 2,
+          defaultPadding = 2,
+          defaultOffset = { x: 0, y: 0 };
+        this.templateData.elements.forEach(e => {
+          switch (e.type) {
+            case "diagram": {
+              this.addDiagram(
+                e.position,
+                e.offset || defaultOffset,
+                e.width,
+                e.height
+              );
+              break;
+            }
+            case "diagramtitle": {
+              let title = `Available Fairway Depth: ${this.featureName}`;
+              this.addDiagramTitle(
+                e.position,
+                e.offset || defaultOffset,
+                e.fontsize || defaultFontSize,
+                e.color || defaultColor,
+                title
+              );
+              break;
+            }
+            case "diagramlegend": {
+              this.addDiagramLegend(
+                e.position,
+                e.offset || defaultOffset,
+                e.color || defaultColor
+              );
+              break;
+            }
+            case "text": {
+              this.addText(
+                e.position,
+                e.offset || defaultOffset,
+                e.width || defaultWidth,
+                e.fontsize || defaultFontSize,
+                e.color || defaultTextColor,
+                e.text || ""
+              );
+              break;
+            }
+            case "image": {
+              this.addImage(
+                e.url,
+                e.format || "",
+                e.position,
+                e.offset || defaultOffset,
+                e.width || 90,
+                e.height || 60
+              );
+              break;
+            }
+            case "box": {
+              this.addBox(
+                e.position,
+                e.offset || defaultOffset,
+                e.width || 90,
+                e.height || 60,
+                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
+                e.color || defaultBgColor,
+                e.brcolor || defaultBorderColor
+              );
+              break;
+            }
+            case "textbox": {
+              this.addTextBox(
+                e.position,
+                e.offset || defaultOffset,
+                e.width,
+                e.height,
+                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
+                e.padding || defaultPadding,
+                e.fontsize || defaultFontSize,
+                e.color || defaultTextColor,
+                e.background || defaultBgColor,
+                e.text || "",
+                e.brcolor || defaultBorderColor
+              );
+              break;
+            }
+          }
+        });
+      }
+      this.pdf.doc.save(`Available Fairway Depth: ${this.featureName}`);
+    },
+    addDiagram(position, offset, width, height) {
+      let x = offset.x,
+        y = offset.y;
+      var svg = this.$refs.diagramContainer.innerHTML;
+      if (svg) {
+        svg = svg.replace(/\r?\n|\r/g, "").trim();
+      }
+      // use default width,height if they are missing in the template definition
+      if (!width) {
+        width = this.templateData.properties.paperSize === "a3" ? 380 : 290;
+      }
+      if (!height) {
+        height = this.templateData.properties.paperSize === "a3" ? 130 : 100;
+      }
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - height;
+      }
+      var canvas = document.createElement("canvas");
+      canvas.width = window.innerWidth;
+      canvas.height = window.innerHeight / 2;
+      canvg(canvas, svg, {
+        ignoreMouse: true,
+        ignoreAnimation: true,
+        ignoreDimensions: true
+      });
+      var imgData = canvas.toDataURL("image/png");
+      this.pdf.doc.addImage(imgData, "PNG", x, y, width, height);
+    },
+    addDiagramLegend(position, offset, color) {
+      let x = offset.x,
+        y = offset.y;
+      this.pdf.doc.setFontSize(10);
+      let width =
+        (this.pdf.doc.getStringUnitWidth(">= LDC") * 10) / (72 / 25.6) + 15;
+      // if position is on the right, x needs to be calculate with pdf width and
+      // the size of the element
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - this.getTextHeight(6);
+      }
+
+      this.pdf.doc.setTextColor(color);
+      this.pdf.doc.setDrawColor(this.$options.COLORS.LDC);
+      this.pdf.doc.setFillColor(this.$options.COLORS.LDC);
+      this.pdf.doc.roundedRect(x, y, 10, 4, 1.5, 1.5, "FD");
+      this.pdf.doc.text(this.legend[0], x + 12, y + 3);
+
+      this.pdf.doc.setDrawColor(this.$options.COLORS.REST[0]);
+      this.pdf.doc.setFillColor(this.$options.COLORS.REST[0]);
+      this.pdf.doc.roundedRect(x, y + 5, 10, 4, 1.5, 1.5, "FD");
+      this.pdf.doc.text(this.legend[1], x + 12, y + 8);
+
+      this.pdf.doc.setDrawColor(this.$options.COLORS.REST[1]);
+      this.pdf.doc.setFillColor(this.$options.COLORS.REST[1]);
+      this.pdf.doc.roundedRect(x, y + 10, 10, 4, 1.5, 1.5, "FD");
+      this.pdf.doc.text(this.legend[2], x + 12, y + 13);
+
+      this.pdf.doc.setDrawColor(this.$options.COLORS.HIGHEST);
+      this.pdf.doc.setFillColor(this.$options.COLORS.HIGHEST);
+      this.pdf.doc.roundedRect(x, y + 15, 10, 4, 1.5, 1.5, "FD");
+      this.pdf.doc.text(this.legend[3], x + 12, y + 18);
+    },
+    legendStyle(index) {
+      const style = {
+        0: `background-color: ${this.$options.COLORS.LDC};`,
+        1: `background-color: ${this.$options.COLORS.REST[0]};`,
+        2: `background-color: ${this.$options.COLORS.REST[1]};`,
+        3: `background-color: ${this.$options.COLORS.HIGHEST};`
+      };
+      return style[index];
+    },
+    close() {
+      this.$store.commit("application/paneSetup", "DEFAULT");
+    },
+    drawDiagram() {
+      this.dimensions = this.getDimensions({
+        main: { top: 20, right: 20, bottom: 110, left: 200 }
+      });
+      this.yScale = d3
+        .scaleLinear()
+        .domain(this.frequencyToRange)
+        .range([this.dimensions.mainHeight - 30, 0]);
+      d3.select(".diagram-container svg").remove();
+      this.generateDiagramContainer();
+      this.drawBars();
+      this.drawScaleLabel();
+      this.drawScale();
+      this.drawTooltip();
+    },
+    generateDiagramContainer() {
+      const diagram = d3
+        .select(".diagram-container")
+        .append("svg")
+        .attr("width", this.dimensions.width)
+        .attr("height", this.dimensions.mainHeight);
+      this.diagram = diagram
+        .append("g")
+        .attr("transform", `translate(0 ${this.paddingTop})`);
+    },
+    drawTooltip() {
+      this.diagram
+        .append("text")
+        .text("banane")
+        .attr("font-size", "0.8em")
+        .attr("opacity", 0)
+        .attr("id", "tooltip");
+    },
+    drawBars() {
+      const everyBar = this.diagram
+        .selectAll("g")
+        .data(this.fwData)
+        .enter()
+        .append("g")
+        .attr("transform", (d, i) => {
+          const dx = this.paddingRight + i * this.spaceBetween;
+          return `translate(${dx})`;
+        });
+      this.drawSingleBars(everyBar);
+      this.drawLabelPerBar(everyBar);
+    },
+    drawSingleBars(everyBar) {
+      this.drawLDC(everyBar);
+      this.drawHighestLevel(everyBar);
+      this.drawLowerLevels(everyBar);
+    },
+    drawLowerLevels(everyBar) {
+      everyBar
+        .selectAll("g")
+        .data(d => d.lowerLevels)
+        .enter()
+        .append("rect")
+        .on("mouseover", function() {
+          d3.select(this).attr("opacity", "0.8");
+          d3.select("#tooltip").attr("opacity", 1);
+        })
+        .on("mouseout", function() {
+          d3.select(this).attr("opacity", 1);
+          d3.select("#tooltip").attr("opacity", 0);
+        })
+        .on("mousemove", function(d) {
+          let y = d3.mouse(this)[1];
+          const dy = document
+            .querySelector(".diagram-container")
+            .getBoundingClientRect().left;
+          const value = Number.parseFloat(hoursInDays(d.height)).toFixed(2);
+          d3.select("#tooltip")
+            .text(value)
+            .attr("y", y - 10)
+            .attr("x", d3.event.pageX - dy);
+          //d3.event.pageX gives coordinates relative to SVG
+          //dy gives offset of svg on page
+        })
+        .attr("y", d => {
+          return 2 * this.yScale(0) - this.yScale(hoursInDays(d.translateY));
+        })
+        .attr("height", d => {
+          return this.yScale(0) - this.yScale(hoursInDays(d.height));
+        })
+        .attr("x", this.ldcoffset)
+        .attr("width", this.barsWidth - this.ldcoffset)
+        .attr("fill", (d, i) => {
+          return this.$options.COLORS.REST[i];
+        });
+    },
+    fnheight(name) {
+      return d => this.yScale(0) - this.yScale(hoursInDays(d[name]));
+    },
+    drawLDC(everyBar) {
+      const height = this.fnheight("ldc");
+      everyBar
+        .append("rect")
+        .on("mouseover", function() {
+          d3.select(this).attr("opacity", "0.8");
+          d3.select("#tooltip").attr("opacity", 1);
+        })
+        .on("mouseout", function() {
+          d3.select(this).attr("opacity", 1);
+          d3.select("#tooltip").attr("opacity", 0);
+        })
+        .on("mousemove", function(d) {
+          let y = d3.mouse(this)[1];
+          const dy = document
+            .querySelector(".diagram-container")
+            .getBoundingClientRect().left;
+          const value = Number.parseFloat(hoursInDays(d.ldc)).toFixed(2);
+          d3.select("#tooltip")
+            .text(value)
+            .attr("y", y - 50)
+            .attr("x", d3.event.pageX - dy);
+          //d3.event.pageX gives coordinates relative to SVG
+          //dy gives offset of svg on page
+        })
+        .attr("y", this.yScale(0))
+        .attr("height", height)
+        .attr("x", -this.ldcoffset)
+        .attr("width", this.barsWidth - this.ldcoffset)
+        .attr("transform", d => `translate(0 ${-1 * height(d)})`)
+        .attr("fill", this.$options.COLORS.LDC)
+        .attr("id", "ldc");
+    },
+    drawHighestLevel(everyBar) {
+      const height = this.fnheight("highestLevel");
+      everyBar
+        .append("rect")
+        .on("mouseover", function() {
+          d3.select(this).attr("opacity", "0.8");
+          d3.select("#tooltip").attr("opacity", 1);
+        })
+        .on("mouseout", function() {
+          d3.select(this).attr("opacity", 1);
+          d3.select("#tooltip").attr("opacity", 0);
+        })
+        .on("mousemove", function(d) {
+          let y = d3.mouse(this)[1];
+          const dy = document
+            .querySelector(".diagram-container")
+            .getBoundingClientRect().left;
+          const value = Number.parseFloat(hoursInDays(d.highestLevel)).toFixed(
+            2
+          );
+          d3.select("#tooltip")
+            .text(value)
+            .attr("y", y - 50)
+            .attr("x", d3.event.pageX - dy);
+          //d3.event.pageX gives coordinates relative to SVG
+          //dy gives offset of svg on page
+        })
+        .attr("y", this.yScale(0))
+        .attr("x", this.ldcoffset)
+        .attr("height", height)
+        .attr("width", this.barsWidth - this.ldcoffset)
+        .attr("transform", d => `translate(0 ${-1 * height(d)})`)
+        .attr("fill", this.$options.COLORS.HIGHEST);
+    },
+    drawLabelPerBar(everyBar) {
+      everyBar
+        .append("text")
+        .text(d => d.label)
+        .attr("y", this.labelPaddingTop);
+    },
+    drawScaleLabel() {
+      const center = this.dimensions.mainHeight / 2;
+      this.diagram
+        .append("text")
+        .text(this.$options.LEGEND)
+        .attr("text-anchor", "middle")
+        .attr("x", 0)
+        .attr("y", 0)
+        .attr("dy", "1em")
+        .attr("transform", `translate(0, ${center}), rotate(-90)`);
+    },
+    drawScale() {
+      const yAxis = d3.axisLeft().scale(this.yScale);
+      this.diagram
+        .append("g")
+        .attr("transform", `translate(${this.scalePaddingLeft})`)
+        .call(yAxis)
+        .selectAll(".tick text")
+        .attr("fill", "black")
+        .select(function() {
+          return this.parentNode;
+        })
+        .selectAll(".tick line")
+        .attr("stroke", "black");
+      this.diagram.selectAll(".domain").attr("stroke", "black");
+    }
+  },
+  watch: {
+    fwData() {
+      this.drawDiagram();
+    }
+  },
+  LEGEND: app.$gettext("Sum of days"),
+  COLORS: {
+    LDC: "#cdcdcd",
+    HIGHEST: "#3675ff",
+    REST: ["#782121", "#ff6c6c", "#ffaaaa"]
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/fairway/AvailableFairwayDepthDialogue.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,591 @@
+<template>
+  <div
+    :class="[
+      'box ui-element rounded bg-white text-nowrap',
+      { expanded: showFairwayDepth }
+    ]"
+  >
+    <div style="width: 18rem">
+      <UIBoxHeader icon="chart-line" :title="label" :closeCallback="close" />
+      <div class="box-body">
+        <UISpinnerOverlay v-if="loading" />
+        <div class="mb-2 d-flex justify-content-between align-items-center">
+          <div>
+            <input :value="$options.BOTTLENECK" type="radio" v-model="type" />
+            <small class="ml-1 text-muted">
+              <translate>Bottlenecks</translate>
+            </small>
+          </div>
+          <div>
+            <input :value="$options.STRETCH" type="radio" v-model="type" />
+            <small class="ml-1 text-muted">
+              <translate>Stretches</translate>
+            </small>
+          </div>
+          <div>
+            <input :value="$options.SECTION" type="radio" v-model="type" />
+            <small class="ml-1 text-muted">
+              <translate>Sections</translate>
+            </small>
+          </div>
+        </div>
+        <select
+          v-if="type === $options.BOTTLENECK"
+          @change="entrySelected"
+          class="form-control font-weight-bold"
+          v-model="selectedEntry"
+        >
+          <option :value="null">{{ placeholder }}</option>
+          <optgroup
+            v-for="(bottlenecksForCountry, cc) in orderedBottlenecks"
+            :key="cc"
+            :label="cc"
+          >
+            <option
+              v-for="bn in bottlenecksForCountry"
+              :key="bn.properties.name"
+              :value="bn"
+            >
+              {{ bn.properties.name }}
+            </option>
+          </optgroup>
+        </select>
+        <select
+          v-else-if="type === $options.STRETCH"
+          @change="entrySelected"
+          class="form-control font-weight-bold"
+          v-model="selectedEntry"
+        >
+          <option :value="null">{{ placeholder }}</option>
+          <option
+            v-for="stretch in stretches"
+            :value="stretch"
+            :key="stretch.id"
+          >
+            {{ stretch.properties.name }}
+          </option>
+        </select>
+        <select
+          v-else-if="type === $options.SECTION"
+          @change="entrySelected"
+          class="form-control font-weight-bold"
+          v-model="selectedEntry"
+        >
+          <option :value="null">{{ placeholder }}</option>
+          <option
+            v-for="section in sections"
+            :value="section"
+            :key="section.id"
+          >
+            {{ section.properties.name }}
+          </option>
+        </select>
+        <div class="d-flex mt-2">
+          <div class="d-flex flex-column w-50 mr-1">
+            <small class="my-auto text-muted">
+              <translate>Type</translate>
+            </small>
+            <select
+              v-model="selectedFrequency"
+              class="form-control form-control-sm"
+            >
+              <option
+                v-for="(option, index) in $options.FREQUENCIES"
+                :value="option"
+                :key="index"
+              >
+                <translate>{{ option }}</translate>
+              </option>
+            </select>
+          </div>
+          <div class="d-flex flex-column w-50 ml-1">
+            <small class="my-auto text-muted"><translate>LOS</translate></small>
+            <select v-model="los" class="form-control form-control-sm">
+              <option value="1">1</option>
+              <option value="2">2</option>
+              <option value="3">3</option>
+            </select>
+          </div>
+        </div>
+        <div class="d-flex mt-2">
+          <div class="d-flex flex-column w-50 mr-1">
+            <small for="from" class="my-auto text-muted">
+              <translate>Date from</translate>
+            </small>
+            <input
+              id="from"
+              v-model="fromDate"
+              class="form-control form-control-sm"
+              type="date"
+            />
+          </div>
+          <div class="d-flex flex-column w-50 ml-1">
+            <small for="to" class="my-auto text-muted">
+              <translate>Date to</translate>
+            </small>
+            <input
+              id="to"
+              v-model="toDate"
+              class="form-control form-control-sm"
+              type="date"
+            />
+          </div>
+        </div>
+
+        <div v-if="depthLimitVisible" class="d-flex mt-2" :key="1">
+          <div class="d-flex flex-column w-50 mr-1">
+            <small for="from" class="my-auto text-muted">
+              <translate>Depthlimit 1 (in cm)</translate>
+            </small>
+            <input
+              id="depthlimit1"
+              v-model.number="depthLimit1"
+              class="form-control form-control-sm"
+              type="number"
+              min="0"
+            />
+          </div>
+          <div
+            v-if="depthLimitVisible"
+            class="d-flex flex-column w-50 ml-1"
+            :key="2"
+          >
+            <small for="to" class="my-auto text-muted">
+              <translate>Depthlimit 2 ( in cm)</translate>
+            </small>
+            <input
+              id="depthlimit2"
+              v-model.number="depthLimit2"
+              class="form-control form-control-sm"
+              type="number"
+              min="0"
+            />
+          </div>
+        </div>
+        <div v-if="widthLimitVisible" class="d-flex mt-2" :key="3">
+          <div class="d-flex flex-column w-50 mr-1">
+            <small for="from" class="my-auto text-muted">
+              <translate>Widthlimit 1</translate>
+            </small>
+            <input
+              id="widthLimit"
+              v-model.number="widthLimit1"
+              class="form-control form-control-sm"
+              type="number"
+              min="0"
+            />
+          </div>
+          <div
+            v-if="widthLimitVisible"
+            class="d-flex flex-column w-50 mr-1"
+            :key="4"
+          >
+            <small for="from" class="my-auto text-muted">
+              <translate>Widthlimit 2</translate>
+            </small>
+            <input
+              id="widthLimit"
+              v-model.number="widthLimit2"
+              class="form-control form-control-sm"
+              type="number"
+              min="0"
+            />
+          </div>
+        </div>
+
+        <div class="mt-3">
+          <button
+            @click="openFairwaydepthDiagram"
+            :disabled="!isComplete"
+            class="btn btn-info btn-sm d-block w-100"
+          >
+            <translate>Available fairway depth</translate>
+          </button>
+          <button
+            @click="openFairwaydepthLNWLDiagram"
+            :disabled="!isComplete"
+            class="btn btn-info btn-sm d-block w-100 mt-2"
+          >
+            <translate>Available fairway depth vs LNWL</translate>
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+input,
+select {
+  font-size: 0.8em;
+}
+</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>
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+
+import app from "@/main";
+import { displayError } from "@/lib/errors";
+import { mapState, mapGetters } from "vuex";
+import { FREQUENCIES, LIMITINGFACTORS } from "@/store/fairwayavailability";
+
+export default {
+  data() {
+    return {
+      loading: false
+    };
+  },
+  computed: {
+    ...mapState("application", [
+      "showFairwayDepth",
+      "paneSetup",
+      "showProfiles"
+    ]),
+    ...mapState("fairwayavailability", [
+      "selectedFairwayAvailabilityFeature",
+      "from",
+      "to",
+      "frequency",
+      "LOS",
+      "depthlimit1",
+      "depthlimit2",
+      "widthlimit1",
+      "widthlimit2"
+    ]),
+    ...mapState("imports", [
+      "stretches",
+      "sections",
+      "selectedStretchId",
+      "selectedSectionId"
+    ]),
+    ...mapState("bottlenecks", ["bottlenecksList", "selectedBottleneck"]),
+    ...mapGetters("map", ["openLayersMap"]),
+    ...mapGetters("bottlenecks", [
+      "orderedBottlenecks",
+      "limitingFactorsPerBottleneck"
+    ]),
+    depthLimitVisible() {
+      if (this.type !== this.$options.BOTTLENECK) return true;
+      if (
+        this.selectedEntry &&
+        this.limitingFactorsPerBottleneck[this.selectedEntry.properties.name] ==
+          this.$options.LIMITINGFACTORS.DEPTH
+      )
+        return true;
+      return false;
+    },
+    widthLimitVisible() {
+      if (this.type !== this.$options.BOTTLENECK) return true;
+      if (
+        this.selectedEntry &&
+        this.limitingFactorsPerBottleneck[this.selectedEntry.properties.name] ==
+          this.$options.LIMITINGFACTORS.WIDTH
+      )
+        return true;
+    },
+    limitingFactor() {
+      if (this.type !== this.$options.BOTTLENECK) return;
+      if (this.selectedEntry)
+        return this.limitingFactorsPerBottleneck[
+          this.selectedEntry.properties.name
+        ];
+    },
+    isComplete() {
+      return (
+        this.from !== null &&
+        this.to !== null &&
+        this.frequency !== null &&
+        this.los !== null &&
+        this.selectedFairwayAvailabilityFeature !== null
+      );
+    },
+    type: {
+      get() {
+        return this.$store.state.fairwayavailability.type;
+      },
+      set(type) {
+        this.$store.commit("fairwayavailability/type", type);
+      }
+    },
+    los: {
+      get() {
+        return this.LOS;
+      },
+      set(value) {
+        this.$store.commit("fairwayavailability/setLOS", value);
+      }
+    },
+    fromDate: {
+      get() {
+        return this.from;
+      },
+      set(value) {
+        this.$store.commit("fairwayavailability/setFrom", value);
+      }
+    },
+    toDate: {
+      get() {
+        return this.to;
+      },
+      set(value) {
+        this.$store.commit("fairwayavailability/setTo", value);
+      }
+    },
+    depthLimit1: {
+      get() {
+        return this.depthlimit1;
+      },
+      set(value) {
+        this.$store.commit("fairwayavailability/setDepthlimit1", value);
+      }
+    },
+    depthLimit2: {
+      get() {
+        return this.depthlimit2;
+      },
+      set(value) {
+        this.$store.commit("fairwayavailability/setDepthlimit2", value);
+      }
+    },
+    widthLimit1: {
+      get() {
+        return this.widthlimit1;
+      },
+      set(value) {
+        this.$store.commit("fairwayavailability/setWidthlimit1", value);
+      }
+    },
+    widthLimit2: {
+      get() {
+        return this.widthlimit2;
+      },
+      set(value) {
+        this.$store.commit("fairwayavailability/setWidthlimit2", value);
+      }
+    },
+    selectedFrequency: {
+      get() {
+        return this.frequency;
+      },
+      set(value) {
+        this.$store.commit("fairwayavailability/setFrequency", value);
+      }
+    },
+    selectedEntry: {
+      get() {
+        return this.selectedFairwayAvailabilityFeature;
+      },
+      set(feature) {
+        this.$store.commit(
+          "fairwayavailability/setSelectedFairwayAvailability",
+          feature
+        );
+      }
+    },
+    label() {
+      return this.$gettext("Available fairway depth");
+    },
+    placeholder() {
+      if (this.type === this.$options.BOTTLENECK)
+        return this.$gettext("Select bottleneck");
+      if (this.type === this.$options.STRETCH)
+        return this.$gettext("Select stretch");
+      return this.$gettext("Select section");
+    }
+  },
+  watch: {
+    selectedBottleneck() {
+      this.type = this.$options.BOTTLENECK;
+      this.setSelectedBottleneck();
+    },
+    selectedStretchId() {
+      this.type = this.$options.STRETCH;
+      this.setSelectedStretch();
+    },
+    selectedSectionId() {
+      this.type = this.$options.SECTION;
+      this.setSelectedSection();
+    },
+    type(type) {
+      if (type === this.$options.BOTTLENECK && this.selectedBottleneck) {
+        this.openLayersMap()
+          .getLayer("BOTTLENECKS")
+          .setVisible(true);
+        this.setSelectedBottleneck();
+      } else if (type === this.$options.STRETCH && this.selectedStretchId) {
+        this.openLayersMap()
+          .getLayer("STRETCHES")
+          .setVisible(true);
+        this.setSelectedStretch();
+      } else if (type === this.$options.SECTION && this.selectedSectionId) {
+        this.openLayersMap()
+          .getLayer("SECTIONS")
+          .setVisible(true);
+        this.setSelectedSection();
+      } else {
+        this.$store.commit(
+          "fairwayavailability/setSelectedFairwayAvailability",
+          null
+        );
+      }
+    },
+    showFairwayDepth() {
+      if (this.showFairwayDepth) {
+        this.loading = true;
+        Promise.all([
+          this.$store.dispatch("bottlenecks/loadBottlenecks"),
+          this.$store.dispatch("bottlenecks/loadBottlenecksList"),
+          this.$store.dispatch("imports/loadStretches"),
+          this.$store.dispatch("imports/loadSections")
+        ])
+          .then(() => {
+            if (this.selectedBottleneck) this.setSelectedBottleneck();
+          })
+          .finally(() => (this.loading = false));
+      }
+    }
+  },
+  methods: {
+    openFairwaydepthLNWLDiagram() {
+      this.loading = true;
+      this.$store
+        .dispatch("fairwayavailability/loadAvailableFairwayDepthLNWLDiagram", {
+          feature: this.selectedFairwayAvailabilityFeature,
+          from: this.from,
+          to: this.to,
+          frequency: this.frequency,
+          LOS: this.los,
+          type: this.type,
+          depthLimit1: this.depthLimit1,
+          depthLimit2: this.depthLimit2,
+          widthLimit1: this.widthLimit1,
+          widthLimit2: this.widthLimit2,
+          limitingFactor: this.limitingFactor
+        })
+        .then(() => {
+          this.$store.commit(
+            "application/paneSetup",
+            "AVAILABLEFAIRWAYDEPTHLNWL"
+          );
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        })
+        .finally(() => {
+          this.loading = false;
+        });
+    },
+    openFairwaydepthDiagram() {
+      this.loading = true;
+      this.$store
+        .dispatch("fairwayavailability/loadAvailableFairwayDepth", {
+          feature: this.selectedFairwayAvailabilityFeature,
+          from: this.from,
+          to: this.to,
+          frequency: this.frequency,
+          LOS: this.los,
+          type: this.type,
+          depthLimit1: this.depthLimit1,
+          depthLimit2: this.depthLimit2,
+          widthLimit1: this.widthLimit1,
+          widthLimit2: this.widthLimit2,
+          limitingFactor: this.limitingFactor
+        })
+        .then(() => {
+          this.$store.commit("application/paneSetup", "AVAILABLEFAIRWAYDEPTH");
+        })
+        .catch(error => {
+          console.log(error);
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        })
+        .finally(() => {
+          this.loading = false;
+        });
+    },
+    close() {
+      this.$store.commit("application/showFairwayDepth", false);
+      this.$store.commit("application/showFairwayDepthLNWL", false);
+    },
+    entrySelected() {
+      if (this.type === this.$options.BOTTLENECK) {
+        this.openLayersMap()
+          .getLayer("BOTTLENECKS")
+          .setVisible(true);
+        if (this.showProfiles) {
+          this.$store.dispatch(
+            "bottlenecks/setSelectedBottleneck",
+            this.selectedFairwayAvailabilityFeature.properties.name
+          );
+        }
+      }
+      if (this.type === this.$options.STRETCH) {
+        this.openLayersMap()
+          .getLayer("STRETCHES")
+          .setVisible(true);
+      }
+      if (this.type === this.$options.SECTION) {
+        this.openLayersMap()
+          .getLayer("SECTIONS")
+          .setVisible(true);
+      }
+      if (this.selectedFairwayAvailabilityFeature) {
+        this.$store.dispatch("map/moveToFeauture", {
+          feature: this.selectedFairwayAvailabilityFeature,
+          zoom: 17,
+          preventZoomOut: true
+        });
+      }
+    },
+    setSelectedBottleneck() {
+      const bn = this.bottlenecksList.filter(
+        x => x.properties.name === this.selectedBottleneck
+      )[0];
+      this.$store.commit(
+        "fairwayavailability/setSelectedFairwayAvailability",
+        bn
+      );
+    },
+    setSelectedStretch() {
+      const stretch = this.stretches.find(x => x.id === this.selectedStretchId);
+      this.$store.commit(
+        "fairwayavailability/setSelectedFairwayAvailability",
+        stretch
+      );
+    },
+    setSelectedSection() {
+      const section = this.sections.find(x => x.id === this.selectedSectionId);
+      this.$store.commit(
+        "fairwayavailability/setSelectedFairwayAvailability",
+        section
+      );
+    }
+  },
+  BOTTLENECK: "bottleneck",
+  SECTION: "section",
+  STRETCH: "stretch",
+  AVAILABLEFAIRWAYDEPTH: app.$gettext("Available Fairway Depth"),
+  FREQUENCIES: FREQUENCIES,
+  LIMITINGFACTORS: LIMITINGFACTORS
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/fairway/AvailableFairwayDepthLNWL.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,644 @@
+<template>
+  <div class="d-flex flex-column flex-fill">
+    <UIBoxHeader icon="chart-area" :title="title" :closeCallback="close" />
+    <UISpinnerOverlay v-if="loading" />
+    <div class="d-flex flex-fill">
+      <DiagramLegend>
+        <div v-for="(entry, index) in legendLNWL" :key="index" class="legend">
+          <span
+            :style="
+              `${legendStyle(
+                index
+              )}; border-radius: 0.25rem; width: 40px; height: 20px;`
+            "
+          ></span>
+          {{ entry }}
+        </div>
+        <div>
+          <select
+            @change="applyChange"
+            v-model="form.template"
+            class="form-control d-block custom-select-sm w-100 mt-2"
+          >
+            <option
+              v-for="template in templates"
+              :value="template"
+              :key="template.name"
+            >
+              {{ template.name }}
+            </option>
+          </select>
+          <button
+            @click="downloadPDF"
+            type="button"
+            class="btn btn-sm btn-info d-block w-100 mt-2"
+          >
+            <translate>Export to PDF</translate>
+          </button>
+          <a
+            :href="dataLink"
+            :download="csvFileName"
+            class="mt-2 btn btn-sm btn-info w-100"
+            >Download CSV</a
+          >
+        </div>
+      </DiagramLegend>
+      <div
+        ref="diagramContainer"
+        :id="containerId"
+        class="mx-auto my-auto diagram-container"
+      ></div>
+    </div>
+  </div>
+</template>
+
+<style></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>
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ * Fadi Abbud <fadi.abbud@intevation.de>
+ */
+import * as d3 from "d3";
+import app from "@/main";
+import debounce from "debounce";
+import { diagram } from "@/lib/mixins";
+import { mapState } from "vuex";
+import filters from "@/lib/filters.js";
+import jsPDF from "jspdf";
+import canvg from "canvg";
+import { pdfgen } from "@/lib/mixins";
+import { HTTP } from "@/lib/http";
+import { displayError } from "@/lib/errors";
+
+export default {
+  mixins: [diagram, pdfgen],
+  components: {
+    DiagramLegend: () => import("@/components/DiagramLegend")
+  },
+  data() {
+    return {
+      containerId: "availablefairwaydepthlnwl",
+      loading: false,
+      labelPaddingTop: 15,
+      scalePaddingLeft: 50,
+      paddingTop: 10,
+      diagram: null,
+      yScale: null,
+      dimensions: null,
+      pdf: {
+        doc: null,
+        width: null,
+        height: null
+      },
+      form: {
+        template: null
+      },
+      templateData: null,
+      templates: [],
+      defaultTemplate: {
+        name: "Default",
+        properties: {
+          paperSize: "a4"
+        },
+        elements: [
+          {
+            type: "diagram",
+            position: "topleft",
+            offset: { x: 20, y: 60 },
+            width: 290,
+            height: 100
+          },
+          {
+            type: "diagramtitle",
+            position: "topleft",
+            offset: { x: 70, y: 20 },
+            fontsize: 20,
+            color: "steelblue"
+          },
+          {
+            type: "diagramlegend",
+            position: "topleft",
+            offset: { x: 30, y: 160 },
+            color: "black"
+          }
+        ]
+      }
+    };
+  },
+  created() {
+    window.addEventListener("resize", debounce(this.drawDiagram), 200);
+  },
+  mounted() {
+    this.drawDiagram();
+    this.templates[0] = this.defaultTemplate;
+    this.form.template = this.templates[0];
+    this.templateData = this.form.template;
+    HTTP.get("/templates/diagram", {
+      headers: {
+        "X-Gemma-Auth": localStorage.getItem("token"),
+        "Content-type": "text/xml; charset=UTF-8"
+      }
+    })
+      .then(response => {
+        if (response.data.length) {
+          this.templates = response.data;
+          this.form.template = this.templates[0];
+          this.templates[this.templates.length] = this.defaultTemplate;
+          this.applyChange();
+        }
+      })
+      .catch(e => {
+        const { status, data } = e.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      });
+  },
+  computed: {
+    ...mapState("fairwayavailability", [
+      "selectedFairwayAvailabilityFeature",
+      "fwLNWLData",
+      "from",
+      "to",
+      "frequency",
+      "csv",
+      "depthlimit1",
+      "depthlimit2",
+      "widthlimit1",
+      "widthlimit2"
+    ]),
+    legendLNWL() {
+      const d = [this.depthlimit1, this.depthlimit2].sort();
+      const w = [this.widthlimit1, this.widthlimit2].sort();
+      const lowerBound = [d[0], w[0]].filter(x => x).join(", ");
+      const upperBound = [d[1], w[1]].filter(x => x).join(", ");
+      return [
+        `> LDC`,
+        `< ${lowerBound}`,
+        `< ${upperBound}`,
+        `>= ${upperBound}`
+      ];
+    },
+    dataLink() {
+      return `data:text/csv;charset=utf-8, ${encodeURIComponent(this.csv)}`;
+    },
+    csvFileName() {
+      return `${this.$gettext("fairwayavailabilityLNWL")}-${
+        this.featureName
+      }-${filters.surveyDate(this.fromDate)}-${filters.surveyDate(
+        this.toDate
+      )}-${this.$gettext(this.frequency)}-.csv`;
+    },
+    fromDate() {
+      return this.from;
+    },
+    toDate() {
+      return this.to;
+    },
+    availability() {
+      return this.plainAvailability;
+    },
+    title() {
+      return `Available Fairway Depth vs LNWL: ${
+        this.featureName
+      } (${filters.surveyDate(this.fromDate)} - ${filters.surveyDate(
+        this.toDate
+      )}) ${this.$gettext(this.frequency)}`;
+    },
+    featureName() {
+      if (this.selectedFairwayAvailabilityFeature == null) return "";
+      return this.selectedFairwayAvailabilityFeature.properties.name;
+    },
+    widthPerItem() {
+      return Math.min(
+        (this.dimensions.width - this.scalePaddingLeft) /
+          this.fwLNWLData.length,
+        180
+      );
+    },
+    ldcWidth() {
+      return this.widthPerItem * 0.3;
+    },
+    afdWidth() {
+      return this.widthPerItem * 0.5;
+    },
+    spaceBetween() {
+      return this.widthPerItem * 0.2;
+    }
+  },
+  methods: {
+    legendStyle(index) {
+      const style = {
+        0: `background-color: ${this.$options.LWNLCOLORS.LDC};`,
+        1: `background-color: ${this.$options.AFDCOLORS[2]};`,
+        2: `background-color: ${this.$options.AFDCOLORS[1]};`,
+        3: `background-color: ${this.$options.AFDCOLORS[0]};`
+      };
+      return style[index];
+    },
+    applyChange() {
+      if (this.form.template.hasOwnProperty("properties")) {
+        this.templateData = this.defaultTemplate;
+        return;
+      }
+      if (this.form.template) {
+        HTTP.get("/templates/diagram/" + this.form.template.name, {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token"),
+            "Content-type": "text/xml; charset=UTF-8"
+          }
+        })
+          .then(response => {
+            this.templateData = response.data.template_data;
+          })
+          .catch(e => {
+            const { status, data } = e.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          });
+      }
+    },
+    downloadPDF() {
+      this.pdf.doc = new jsPDF(
+        "l",
+        "mm",
+        this.templateData.properties.paperSize
+      );
+      // pdf width and height in millimeter (landscape)
+      this.pdf.width =
+        this.templateData.properties.paperSize === "a3" ? 420 : 297;
+      this.pdf.height =
+        this.templateData.properties.paperSize === "a3" ? 297 : 210;
+      if (this.templateData) {
+        // default values if some are missing in template
+        let defaultFontSize = 11,
+          defaultColor = "black",
+          defaultWidth = 70,
+          defaultTextColor = "black",
+          defaultBorderColor = "white",
+          defaultBgColor = "white",
+          defaultRounding = 2,
+          defaultPadding = 2,
+          defaultOffset = { x: 0, y: 0 };
+        this.templateData.elements.forEach(e => {
+          switch (e.type) {
+            case "diagram": {
+              this.addDiagram(
+                e.position,
+                e.offset || defaultOffset,
+                e.width,
+                e.height
+              );
+              break;
+            }
+            case "diagramtitle": {
+              let title = `Available Fairway Depth vs LNWL: ${
+                this.featureName
+              }`;
+              this.addDiagramTitle(
+                e.position,
+                e.offset || defaultOffset,
+                e.fontsize || defaultFontSize,
+                e.color || defaultColor,
+                title
+              );
+              break;
+            }
+            case "diagramlegend": {
+              this.addDiagramLegend(
+                e.position,
+                e.offset || defaultOffset,
+                e.color || defaultColor
+              );
+              break;
+            }
+            case "text": {
+              this.addText(
+                e.position,
+                e.offset || defaultOffset,
+                e.width || defaultWidth,
+                e.fontsize || defaultFontSize,
+                e.color || defaultTextColor,
+                e.text || ""
+              );
+              break;
+            }
+            case "image": {
+              this.addImage(
+                e.url,
+                e.format || "",
+                e.position,
+                e.offset || defaultOffset,
+                e.width || 90,
+                e.height || 60
+              );
+              break;
+            }
+            case "box": {
+              this.addBox(
+                e.position,
+                e.offset || defaultOffset,
+                e.width || 90,
+                e.height || 60,
+                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
+                e.color || defaultBgColor,
+                e.brcolor || defaultBorderColor
+              );
+              break;
+            }
+            case "textbox": {
+              this.addTextBox(
+                e.position,
+                e.offset || defaultOffset,
+                e.width,
+                e.height,
+                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
+                e.padding || defaultPadding,
+                e.fontsize || defaultFontSize,
+                e.color || defaultTextColor,
+                e.background || defaultBgColor,
+                e.text || "",
+                e.brcolor || defaultBorderColor
+              );
+              break;
+            }
+          }
+        });
+      }
+      this.pdf.doc.save(`Available Fairway Depth LNWL: ${this.featureName}`);
+    },
+    addDiagram(position, offset, width, height) {
+      let x = offset.x,
+        y = offset.y;
+      var svg = this.$refs.diagramContainer.innerHTML;
+      if (svg) {
+        svg = svg.replace(/\r?\n|\r/g, "").trim();
+      }
+      // use default width,height if they are missing in the template definition
+      if (!width) {
+        width = this.templateData.properties.paperSize === "a3" ? 380 : 290;
+      }
+      if (!height) {
+        height = this.templateData.properties.paperSize === "a3" ? 130 : 100;
+      }
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - height;
+      }
+      var canvas = document.createElement("canvas");
+      canvas.width = window.innerWidth;
+      canvas.height = window.innerHeight / 2;
+      canvg(canvas, svg, {
+        ignoreMouse: true,
+        ignoreAnimation: true,
+        ignoreDimensions: true
+      });
+      var imgData = canvas.toDataURL("image/png");
+      this.pdf.doc.addImage(imgData, "PNG", x, y, width, height);
+    },
+    addDiagramLegend(position, offset, color) {
+      let x = offset.x,
+        y = offset.y;
+      this.pdf.doc.setFontSize(10);
+      let width =
+        (this.pdf.doc.getStringUnitWidth(">= LDC") * 10) / (72 / 25.6) + 15;
+      // if position is on the right, x needs to be calculate with pdf width and
+      // the size of the element
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - this.getTextHeight(6);
+      }
+      this.pdf.doc.setTextColor(color);
+      this.pdf.doc.setDrawColor(this.$options.LWNLCOLORS.LDC);
+      this.pdf.doc.setFillColor(this.$options.LWNLCOLORS.LDC);
+      this.pdf.doc.roundedRect(x, y, 10, 4, 1.5, 1.5, "FD");
+      this.pdf.doc.text(this.legendLNWL[0], x + 12, y + 3);
+
+      this.pdf.doc.setDrawColor(this.$options.AFDCOLORS[2]);
+      this.pdf.doc.setFillColor(this.$options.AFDCOLORS[2]);
+      this.pdf.doc.roundedRect(x, y + 5, 10, 4, 1.5, 1.5, "FD");
+      this.pdf.doc.text(this.legendLNWL[1], x + 12, y + 8);
+
+      this.pdf.doc.setDrawColor(this.$options.AFDCOLORS[1]);
+      this.pdf.doc.setFillColor(this.$options.AFDCOLORS[1]);
+      this.pdf.doc.roundedRect(x, y + 10, 10, 4, 1.5, 1.5, "FD");
+      this.pdf.doc.text(this.legendLNWL[2], x + 12, y + 13);
+
+      this.pdf.doc.setDrawColor(this.$options.AFDCOLORS[0]);
+      this.pdf.doc.setFillColor(this.$options.AFDCOLORS[0]);
+      this.pdf.doc.roundedRect(x, y + 15, 10, 4, 1.5, 1.5, "FD");
+      this.pdf.doc.text(this.legendLNWL[3], x + 12, y + 18);
+    },
+    close() {
+      this.$store.commit("application/paneSetup", "DEFAULT");
+    },
+    drawDiagram() {
+      this.dimensions = this.getDimensions({
+        main: { top: 20, right: 20, bottom: 110, left: 200 }
+      });
+      this.yScale = d3
+        .scaleLinear()
+        .domain([0, 100])
+        .range([this.dimensions.mainHeight - 30, 0]);
+      d3.select(".diagram-container svg").remove();
+      this.generateDiagramContainer();
+      this.drawBars();
+      this.drawScaleLabel();
+      this.drawScale();
+      this.drawTooltip();
+    },
+    drawTooltip() {
+      this.diagram
+        .append("text")
+        .text("")
+        .attr("font-size", "0.8em")
+        .attr("opacity", 0)
+        .attr("id", "tooltip");
+    },
+    generateDiagramContainer() {
+      const diagram = d3
+        .select(".diagram-container")
+        .append("svg")
+        .attr("width", this.dimensions.width)
+        .attr("height", this.dimensions.mainHeight);
+      this.diagram = diagram
+        .append("g")
+        .attr("transform", `translate(0 ${this.paddingTop})`);
+    },
+    drawBars() {
+      if (this.fwLNWLData) {
+        this.fwLNWLData.forEach((data, i) => {
+          this.drawLNWL(data, i);
+          this.drawAFD(data, i);
+          this.drawLabel(data.date, i);
+        });
+      }
+    },
+    drawLabel(date, i) {
+      this.diagram
+        .append("text")
+        .text(date)
+        .attr("text-anchor", "middle")
+        .attr(
+          "transform",
+          `translate(${this.scalePaddingLeft +
+            this.widthPerItem * i +
+            this.widthPerItem / 2} ${this.dimensions.mainHeight - 15})`
+        );
+    },
+    drawAFD(data, i) {
+      let afd = this.diagram
+        .append("g")
+        .attr(
+          "transform",
+          `translate(${this.scalePaddingLeft +
+            this.spaceBetween / 2 +
+            this.widthPerItem * i +
+            this.ldcWidth})`
+        );
+      afd
+        .selectAll("rect")
+        .data([data.above, data.between, data.below])
+        .enter()
+        .append("rect")
+        .on("mouseover", function() {
+          d3.select(this).attr("opacity", "0.8");
+          d3.select("#tooltip").attr("opacity", 1);
+        })
+        .on("mouseout", function() {
+          d3.select(this).attr("opacity", 1);
+          d3.select("#tooltip").attr("opacity", 0);
+        })
+        .on("mousemove", function(d) {
+          let y = d3.mouse(this)[1];
+          const dy = document
+            .querySelector(".diagram-container")
+            .getBoundingClientRect().left;
+          d3.select("#tooltip")
+            .text(d.toFixed(2))
+            .attr("y", y - 10)
+            .attr("x", d3.event.pageX - dy);
+          //d3.event.pageX gives coordinates relative to SVG
+          //dy gives offset of svg on page
+        })
+        .attr("height", d => {
+          return this.yScale(0) - this.yScale(d);
+        })
+        .attr("y", (d, i) => {
+          if (i === 0) {
+            return this.yScale(d);
+          }
+          if (i === 1) {
+            return this.yScale(data.above + d);
+          }
+          if (i === 2) {
+            return this.yScale(data.above + data.between + d);
+          }
+        })
+        .attr("width", this.afdWidth)
+        .attr("fill", (d, i) => {
+          return this.$options.AFDCOLORS[i];
+        });
+    },
+    drawLNWL(data, i) {
+      let lnwl = this.diagram
+        .append("g")
+        .attr(
+          "transform",
+          `translate(${this.scalePaddingLeft +
+            this.spaceBetween / 2 +
+            this.widthPerItem * i})`
+        );
+      lnwl
+        .append("rect")
+        .datum([data.ldc])
+        .on("mouseover", function() {
+          d3.select(this).attr("opacity", "0.8");
+          d3.select("#tooltip").attr("opacity", 1);
+        })
+        .on("mouseout", function() {
+          d3.select(this).attr("opacity", 1);
+          d3.select("#tooltip").attr("opacity", 0);
+        })
+        .on("mousemove", function(d) {
+          let y = d3.mouse(this)[1];
+          const dy = document
+            .querySelector(".diagram-container")
+            .getBoundingClientRect().left;
+          d3.select("#tooltip")
+            .text(d[0].toFixed(2))
+            .attr("y", y - 10)
+            .attr("x", d3.event.pageX - dy);
+          //d3.event.pageX gives coordinates relative to SVG
+          //dy gives offset of svg on page
+        })
+        .attr("height", d => {
+          return this.yScale(0) - this.yScale(d);
+        })
+        .attr("y", d => {
+          return this.yScale(d);
+        })
+        .attr("width", this.ldcWidth)
+        .attr("fill", () => {
+          return this.$options.LWNLCOLORS.LDC;
+        });
+    },
+    drawScaleLabel() {
+      const center = this.dimensions.mainHeight / 2;
+      this.diagram
+        .append("text")
+        .text(this.$options.LEGEND)
+        .attr("text-anchor", "middle")
+        .attr("x", 0)
+        .attr("y", 0)
+        .attr("dy", "1em")
+        .attr("transform", `translate(0, ${center}), rotate(-90)`);
+    },
+    drawScale() {
+      const yAxis = d3.axisLeft().scale(this.yScale);
+      this.diagram
+        .append("g")
+        .attr("transform", `translate(${this.scalePaddingLeft})`)
+        .call(yAxis)
+        .selectAll(".tick text")
+        .attr("fill", "black")
+        .select(function() {
+          return this.parentNode;
+        })
+        .selectAll(".tick line")
+        .attr("stroke", "black");
+      this.diagram.selectAll(".domain").attr("stroke", "black");
+    }
+  },
+  watch: {
+    fwLNWLData() {
+      this.drawDiagram();
+    }
+  },
+  LEGEND: app.$gettext("Percent"),
+  AFDCOLORS: ["#3636ff", "#f49b7f", "#e15472"],
+  LWNLCOLORS: {
+    LDC: "#97ddf3",
+    HDC: "#43FFE1"
+  }
+};
+</script>
--- a/client/src/components/fairway/Fairwayprofile.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/fairway/Fairwayprofile.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,5 +1,65 @@
 <template>
-  <div class="fairwayprofile m-3 mt-0 bg-white flex-grow-1"></div>
+  <div class="d-flex flex-column flex-fill">
+    <UIBoxHeader icon="chart-area" :title="title" :closeCallback="close" />
+    <div class="d-flex flex-fill">
+      <DiagramLegend>
+        <div class="legend">
+          <span
+            style="background-color: #5995ff; width: 20px; height: 20px;"
+          ></span>
+          Water
+        </div>
+        <div class="legend">
+          <span
+            style="background-color: #1f4fff; width: 20px; height: 20px;"
+          ></span>
+          Fairway
+        </div>
+        <div class="legend">
+          <span
+            style="width: 14px; height: 14px; background-color: #4a2f06; border: solid 3px black; background-clip: padding-box; box-sizing: content-box;"
+          ></span>
+          Sediment
+        </div>
+        <div class="legend">
+          <span
+            style="width: 14px; height: 14px; background-color: rgba(74, 47, 6, 0.6); border: solid 3px #943007; background-clip: padding-box; box-sizing: content-box;"
+          ></span>
+          Sediment (Compare)
+        </div>
+        <div>
+          <select
+            v-model="form.template"
+            @change="applyChange"
+            class="form-control d-block custom-select-sm w-100"
+          >
+            <option
+              v-for="template in templates"
+              :value="template"
+              :key="template.name"
+            >
+              {{ template.name }}
+            </option>
+          </select>
+          <button
+            @click="downloadPDF"
+            type="button"
+            class="btn btn-sm btn-info d-block w-100 mt-2"
+          >
+            <translate>Export to PDF</translate>
+          </button>
+        </div>
+      </DiagramLegend>
+      <div
+        ref="diagramContainer"
+        class="d-flex flex-fill justify-content-center align-items-center diagram-container"
+      >
+        <div v-if="!fairwayData">
+          <translate>No data available.</translate>
+        </div>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script>
@@ -16,32 +76,78 @@
  * Author(s):
  * Thomas Junk <thomas.junk@intevation.de>
  * Markus Kottländer <markus.kottlaender@intevation.de>
+ * Fadi Abbud <fadi.abbud@intevation.de>
  */
 import * as d3 from "d3";
 import { mapState, mapGetters } from "vuex";
 import debounce from "debounce";
+import jsPDF from "jspdf";
+import canvg from "canvg";
+import { pdfgen } from "@/lib/mixins";
+import { HTTP } from "@/lib/http";
+import { displayError } from "@/lib/errors";
 
 const GROUND_COLOR = "#4A2F06";
+const WATER_COLOR = "#005DFF";
 
 export default {
+  mixins: [pdfgen],
   name: "fairwayprofile",
+  components: {
+    DiagramLegend: () => import("@/components/DiagramLegend")
+  },
   data() {
     return {
-      coordinatesInput: "",
-      coordinatesSelect: null,
-      cutLabel: "",
-      showLabelInput: false,
       width: null,
       height: null,
       margin: {
         top: 20,
-        right: 40,
-        bottom: 30,
-        left: 40
-      }
+        right: 80,
+        bottom: 60,
+        left: 80
+      },
+      form: {
+        template: null
+      },
+      templates: [],
+      defaultTemplate: {
+        name: "default",
+        properties: {
+          paperSize: "a4"
+        },
+        elements: [
+          {
+            type: "diagram",
+            position: "topleft",
+            offset: { x: 20, y: 60 },
+            width: 290,
+            height: 100
+          },
+          {
+            type: "diagramtitle",
+            position: "topleft",
+            offset: { x: 90, y: 30 },
+            fontsize: 22,
+            color: "steelblue"
+          },
+          {
+            type: "diagramlegend",
+            position: "topleft",
+            offset: { x: 30, y: 160 },
+            color: "black"
+          }
+        ]
+      },
+      pdf: {
+        doc: null,
+        width: 32,
+        height: 297
+      },
+      templateData: null
     };
   },
   computed: {
+    ...mapGetters("map", ["openLayersMap"]),
     ...mapGetters("fairwayprofile", ["totalLength"]),
     ...mapState("fairwayprofile", [
       "additionalSurvey",
@@ -50,13 +156,21 @@
       "endPoint",
       "fairwayData",
       "maxAlt",
-      "referenceWaterLevel",
-      "selectedWaterLevel",
-      "waterLevels"
+      "selectedWaterLevel"
     ]),
-    ...mapState("bottlenecks", ["selectedSurvey"]),
-    relativeWaterLevelDelta() {
-      return this.selectedWaterLevel.value - this.referenceWaterLevel;
+    ...mapState("bottlenecks", ["selectedSurvey", "selectedBottleneck"]),
+    ...mapState("application", ["paneSetup"]),
+    title() {
+      let dates = [this.selectedSurvey.date_info];
+      let waterlevelLabel =
+        this.selectedWaterLevel === "ref"
+          ? this.selectedSurvey.depth_reference
+          : "Current";
+      if (this.additionalSurvey) dates.push(this.additionalSurvey.date_info);
+      dates.map(d => this.$options.filters.dateTime(d, true));
+      return `${this.$gettext("Fairwayprofile")}: ${
+        this.selectedBottleneck
+      } (${dates.join(", ")}) WL: ${waterlevelLabel} (${this.waterlevel} cm)`;
     },
     currentData() {
       if (
@@ -74,18 +188,20 @@
         return [];
       return this.currentProfile[this.additionalSurvey.date_info].points;
     },
-    waterColor() {
-      return "#005DFF";
-    },
-    xScale() {
-      return [0, this.totalLength];
+    bottleneck() {
+      return this.openLayersMap()
+        .getLayer("BOTTLENECKS")
+        .getSource()
+        .getFeatures()
+        .find(f => f.get("objnam") === this.selectedBottleneck);
     },
-    yScaleRight() {
-      //ToDO calcReleativeDepth(this.maxAlt) to get the
-      // maximum depth according to the actual waterlevel
-      // additionally: take the one which is higher reference or current waterlevel
-      const DELTA = this.maxAlt * 1.1 - this.maxAlt;
-      return [this.maxAlt * 1 + DELTA, -DELTA];
+    waterlevel() {
+      return this.selectedWaterLevel === "ref"
+        ? this.refWaterlevel
+        : this.bottleneck.get("gm_waterlevel");
+    },
+    refWaterlevel() {
+      return this.selectedSurvey.waterlevel_value;
     }
   },
   watch: {
@@ -109,41 +225,222 @@
     },
     fairwayData() {
       this.drawDiagram();
+    },
+    selectedBottleneck() {
+      this.$store.commit("application/paneSetup", "DEFAULT");
     }
   },
   methods: {
-    calcRelativeDepth(depth) {
-      /* takes a depth value and substracts the delta of the relative waterlevel
-       * say the reference level is above the current level, the ground is nearer,
-       * thus, the depth is lower.
-       *
-       * E.g.:
-       *
-       * Reference waterlevel 5m, current 4m => delta = -1m
-       * If the distance to the ground was 3m from the 5m mark
-       * it is now only 2m from the current waterlevel.
-       *
-       *  Vice versa:
-       *
-       *  If the reference level is 5m and the current 6m => delta = +1m
-       *  The ground is one meter farer away from the current waterlevel
-       *
-       */
-      return depth - this.relativeWaterLevelDelta;
+    close() {
+      this.$store.commit(
+        "application/paneSetup",
+        this.paneSetup === "COMPARESURVEYS_FAIRWAYPROFILE"
+          ? "COMPARESURVEYS"
+          : "DEFAULT"
+      );
+      this.$store.dispatch("fairwayprofile/clearCurrentProfile");
+    },
+    applyChange() {
+      if (this.form.template.hasOwnProperty("properties")) {
+        this.templateData = this.defaultTemplate;
+        return;
+      }
+      if (this.form.template) {
+        HTTP.get("/templates/diagram/" + this.form.template.name, {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token"),
+            "Content-type": "text/xml; charset=UTF-8"
+          }
+        })
+          .then(response => {
+            this.templateData = response.data.template_data;
+          })
+          .catch(e => {
+            const { status, data } = e.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          });
+      }
+    },
+    downloadPDF() {
+      if (this.templateData) {
+        this.pdf.doc = new jsPDF(
+          "l",
+          "mm",
+          this.templateData.properties.paperSize
+        );
+        // pdf width and height in millimeter (landscape)
+        this.pdf.width =
+          this.templateData.properties.paperSize === "a3" ? 420 : 297;
+        this.pdf.height =
+          this.templateData.properties.paperSize === "a3" ? 297 : 210;
+        // default values if some are missing in template
+        let defaultFontSize = 11,
+          defaultWidth = 70,
+          defaultTextColor = "black",
+          defaultBorderColor = "white",
+          defaultBgColor = "white",
+          defaultRounding = 2,
+          defaultPadding = 2,
+          defaultOffset = { x: 0, y: 0 };
+        this.templateData.elements.forEach(e => {
+          switch (e.type) {
+            case "diagram": {
+              this.addDiagram(
+                e.position,
+                e.offset || defaultOffset,
+                e.width,
+                e.height
+              );
+              break;
+            }
+            case "diagramlegend": {
+              this.addDiagramLegend(
+                e.position,
+                e.offset || defaultOffset,
+                e.color || defaultTextColor
+              );
+              break;
+            }
+            case "diagramtitle": {
+              let fairwayInfo =
+                this.selectedBottleneck +
+                " (" +
+                this.selectedSurvey.date_info +
+                ")";
+              this.addDiagramTitle(
+                e.position,
+                e.offset || defaultOffset,
+                e.fontsize || defaultFontSize,
+                e.color || defaultTextColor,
+                fairwayInfo
+              );
+              break;
+            }
+            case "image": {
+              this.addImage(
+                e.url,
+                e.format || "",
+                e.position,
+                e.offset || defaultOffset,
+                e.width || 90,
+                e.height || 60
+              );
+              break;
+            }
+            case "text": {
+              this.addText(
+                e.position,
+                e.offset || defaultOffset,
+                e.width || defaultWidth,
+                e.fontsize || defaultFontSize,
+                e.color || defaultTextColor,
+                e.text || ""
+              );
+              break;
+            }
+            case "box": {
+              this.addBox(
+                e.position,
+                e.offset || defaultOffset,
+                e.width || 90,
+                e.height || 60,
+                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
+                e.color || defaultBgColor,
+                e.brcolor || defaultBorderColor
+              );
+              break;
+            }
+            case "textbox": {
+              this.addTextBox(
+                e.position,
+                e.offset || defaultOffset,
+                e.width,
+                e.height,
+                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
+                e.padding || defaultPadding,
+                e.fontsize || defaultFontSize,
+                e.color || defaultTextColor,
+                e.background || defaultBgColor,
+                e.text || "",
+                e.brcolor || defaultBorderColor
+              );
+              break;
+            }
+          }
+        });
+      }
+      this.pdf.doc.save(this.title.replace(/\s/g, "_").replace(/[():,]/g, ""));
+    },
+    addDiagram(position, offset, width, height) {
+      let x = offset.x,
+        y = offset.y;
+      var svg = this.$refs.diagramContainer.innerHTML;
+      if (svg) {
+        svg = svg.replace(/\r?\n|\r/g, "").trim();
+      }
+      // use default width,height if they are missing in the template definition
+      if (!width) {
+        width = this.templateData.properties.paperSize === "a3" ? 380 : 290;
+      }
+      if (!height) {
+        height = this.templateData.properties.paperSize === "a3" ? 130 : 100;
+      }
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - height;
+      }
+      var canvas = document.createElement("canvas");
+      canvas.width = window.innerWidth;
+      canvas.height = window.innerHeight / 2;
+      canvg(canvas, svg, {
+        ignoreMouse: true,
+        ignoreAnimation: true,
+        ignoreDimensions: true
+      });
+      var imgData = canvas.toDataURL("image/png");
+      this.pdf.doc.addImage(imgData, "PNG", x, y, width, height);
+    },
+    // Diagram legend
+    addDiagramLegend(position, offset, color) {
+      let x = offset.x,
+        y = offset.y;
+      let width =
+        (this.pdf.doc.getStringUnitWidth("Ground") * 10) / (72 / 25.6) + 5;
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - this.getTextHeight(3);
+      }
+      this.pdf.doc.setFontSize(10);
+      this.pdf.doc.setTextColor(color);
+      this.pdf.doc.setDrawColor("white");
+      this.pdf.doc.setFillColor("#5995ff");
+      this.pdf.doc.circle(x, y, 2, "FD");
+      this.pdf.doc.text(x + 3, y + 1, "Water");
+      this.pdf.doc.setFillColor("#1f4fff");
+      this.pdf.doc.circle(x, y + 5, 2, "FD");
+      this.pdf.doc.text(x + 3, y + 6, "Fairway");
+      this.pdf.doc.setFillColor("#4a2f06");
+      this.pdf.doc.circle(x, y + 10, 2, "FD");
+      this.pdf.doc.text(x + 3, y + 11, "Ground");
     },
     drawDiagram() {
-      this.coordinatesSelect = null;
-      const chartDiv = document.querySelector(".fairwayprofile");
-      d3.select(".fairwayprofile svg").remove();
+      d3.select(".diagram-container 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;
+      let svg = d3.select(".diagram-container").append("svg");
+      svg.attr("width", "100%");
+      svg.attr("height", "100%");
+      const width = this.width - this.margin.right - this.margin.left;
+      const height = this.height - this.margin.top - this.margin.bottom;
       const currentData = this.currentData;
       const additionalData = this.additionalData;
-      const { xScale, yScaleRight, graph } = this.generateCoordinates(
+      const { xScale, yScaleRight, graph } = this.generateScalesAndGraph(
         svg,
         height,
         width
@@ -183,7 +480,6 @@
       }
       for (let data of this.fairwayData) {
         const [startPoint, endPoint, depth] = data.coordinates[0];
-        const style = data.style();
         let fairwayArea = d3
           .area()
           .x(function(d) {
@@ -198,7 +494,8 @@
           .datum([{ x: startPoint, y: depth }, { x: endPoint, y: depth }])
           .attr("fill", "#002AFF")
           .attr("fill-opacity", 0.65)
-          .attr("stroke", style[0].getStroke().getColor())
+          .attr("stroke", "#002AFF")
+          .attr("stroke-width", 2)
           .attr("d", fairwayArea);
       }
     },
@@ -206,16 +503,23 @@
       graph
         .append("text")
         .attr("transform", ["rotate(-90)"])
-        .attr("y", this.width - 60)
+        .attr("y", this.width - 100)
         .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("transform", ["rotate(-90)"])
+        .attr("y", -50)
+        .attr("x", -(this.height - this.margin.top - this.margin.bottom) / 2)
+        .attr("fill", "black")
+        .style("text-anchor", "middle")
+        .text("Waterlevel [cm]");
+      graph
+        .append("text")
+        .attr("y", -50)
+        .attr("x", -(height / 4))
         .attr("dy", "1em")
         .attr("fill", "black")
         .style("text-anchor", "middle")
@@ -225,21 +529,35 @@
         ])
         .text("Width [m]");
     },
-    generateCoordinates(svg, height, width) {
+    generateScalesAndGraph(svg, height, width) {
       let xScale = d3
         .scaleLinear()
-        .domain(this.xScale)
+        .domain([0, this.totalLength])
         .rangeRound([0, width]);
 
-      xScale.ticks(5);
-
       let yScaleRight = d3
         .scaleLinear()
-        .domain(this.yScaleRight)
+        .domain([
+          this.maxAlt * 1.1 +
+            Math.abs(this.waterlevel - this.refWaterlevel) / 100,
+          -(this.maxAlt * 0.1)
+        ])
         .rangeRound([height, 0]);
 
-      let xAxis = d3.axisBottom(xScale);
-      let yAxis2 = d3.axisRight(yScaleRight);
+      let yScaleLeft = d3
+        .scaleLinear()
+        .domain([
+          this.waterlevel -
+            (this.maxAlt * 100 +
+              Math.abs(this.waterlevel - this.refWaterlevel)),
+          this.waterlevel + this.maxAlt * 0.1 * 100
+        ])
+        .rangeRound([height, 0]);
+
+      let xAxis = d3.axisBottom(xScale).ticks(5);
+      let yAxisRight = d3.axisRight(yScaleRight);
+      let yAxisLeft = d3.axisLeft(yScaleLeft);
+
       let graph = svg
         .append("g")
         .attr(
@@ -249,11 +567,38 @@
       graph
         .append("g")
         .attr("transform", "translate(0," + height + ")")
-        .call(xAxis.ticks(5));
+        .call(xAxis)
+        .selectAll(".tick text")
+        .attr("fill", "black")
+        .select(function() {
+          return this.parentNode;
+        })
+        .selectAll(".tick line")
+        .attr("stroke", "black");
       graph
         .append("g")
         .attr("transform", "translate(" + width + ",0)")
-        .call(yAxis2);
+        .call(yAxisRight)
+        .selectAll(".tick text")
+        .attr("fill", "black")
+        .select(function() {
+          return this.parentNode;
+        })
+        .selectAll(".tick line")
+        .attr("stroke", "black");
+      graph
+        .append("g")
+        .attr("transform", "translate(0 0)")
+        .call(yAxisLeft)
+        .selectAll(".tick text")
+        .attr("fill", "black")
+        .select(function() {
+          return this.parentNode;
+        })
+        .selectAll(".tick line")
+        .attr("stroke", "black");
+
+      graph.selectAll(".domain").attr("stroke", "black");
       return { xScale, yScaleRight, graph };
     },
     drawWaterlevel({ graph, xScale, yScaleRight, height }) {
@@ -270,8 +615,8 @@
         .append("path")
         .datum([{ x: 0, y: 0 }, { x: this.totalLength, y: 0 }])
         .attr("fill-opacity", 0.65)
-        .attr("fill", this.waterColor)
-        .attr("stroke", this.waterColor)
+        .attr("fill", WATER_COLOR)
+        .attr("stroke", "transparent")
         .attr("d", waterArea);
     },
     drawProfile({
@@ -290,25 +635,27 @@
           .x(d => {
             return xScale(d.x);
           })
-          .y(d => {
-            return yScaleRight(d.y);
-          });
+          .y(d =>
+            yScaleRight(
+              d.y + Math.abs(this.waterlevel - this.refWaterlevel) / 100
+            )
+          );
         let profileArea = d3
           .area()
           .x(function(d) {
             return xScale(d.x);
           })
           .y0(height)
-          .y1(function(d) {
-            return yScaleRight(d.y);
-          });
+          .y1(d =>
+            yScaleRight(
+              d.y + Math.abs(this.waterlevel - this.refWaterlevel) / 100
+            )
+          );
         graph
           .append("path")
           .datum(part)
           .attr("fill", color)
-          .attr("stroke", color)
-          .attr("stroke-width", 3)
-          .attr("stroke-opacity", opacity)
+          .attr("stroke", "transparent")
           .attr("fill-opacity", opacity)
           .attr("d", profileArea);
         graph
@@ -320,15 +667,16 @@
           .attr("stroke-linecap", "round")
           .attr("stroke-width", 3)
           .attr("stroke-opacity", opacity)
-          .attr("fill-opacity", opacity)
+          .attr("fill-opacity", 0)
           .attr("d", profileLine);
       }
     },
     scaleFairwayProfile() {
-      if (!document.querySelector(".fairwayprofile")) return;
-      const clientHeight = document.querySelector(".fairwayprofile")
+      if (!document.querySelector(".diagram-container")) return;
+      const clientHeight = document.querySelector(".diagram-container")
         .clientHeight;
-      const clientWidth = document.querySelector(".fairwayprofile").clientWidth;
+      const clientWidth = document.querySelector(".diagram-container")
+        .clientWidth;
       if (!clientHeight || !clientWidth) return;
       this.height = clientHeight;
       this.width = clientWidth;
@@ -339,9 +687,33 @@
   },
   mounted() {
     this.drawDiagram();
+    this.templates[0] = this.defaultTemplate;
+    this.form.template = this.templates[0];
+    this.templateData = this.form.template;
+    HTTP.get("/templates/diagram", {
+      headers: {
+        "X-Gemma-Auth": localStorage.getItem("token"),
+        "Content-type": "text/xml; charset=UTF-8"
+      }
+    })
+      .then(response => {
+        if (response.data.length) {
+          this.templates = response.data;
+          this.form.template = this.templates[0];
+          this.templates[this.templates.length] = this.defaultTemplate;
+          this.applyChange();
+        }
+      })
+      .catch(e => {
+        const { status, data } = e.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      });
   },
   updated() {
-    this.scaleFairwayProfile();
+    this.drawDiagram();
   },
   destroyed() {
     window.removeEventListener("resize", debounce(this.drawDiagram));
--- a/client/src/components/fairway/Profiles.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/fairway/Profiles.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -12,11 +12,7 @@
         :closeCallback="close"
       />
       <div class="box-body">
-        <transition name="fade">
-          <div class="loading" v-if="surveysLoading || profileLoading">
-            <font-awesome-icon icon="spinner" spin />
-          </div>
-        </transition>
+        <UISpinnerOverlay v-if="surveysLoading || profileLoading" />
         <select
           @change="moveToBottleneck"
           v-model="selectedBottleneck"
@@ -25,16 +21,23 @@
           <option :value="null">
             <translate>Select Bottleneck</translate>
           </option>
-          <option
-            v-for="bn in bottlenecksList"
-            :key="bn.properties.name"
-            :value="bn.properties.name"
-            >{{ bn.properties.name }}</option
+          <optgroup
+            v-for="(bottlenecksForCountry, cc) in orderedBottlenecks"
+            :key="cc"
+            :label="cc"
           >
+            <option
+              v-for="bn in bottlenecksForCountry"
+              :key="bn.properties.name"
+              :value="bn.properties.name"
+            >
+              {{ bn.properties.name }}
+            </option>
+          </optgroup>
         </select>
         <div v-if="selectedBottleneck">
           <div class="d-flex mt-2">
-            <div class="flex-fill">
+            <div class="flex-fill" style="max-width: 75px;">
               <small class="text-muted">
                 <translate>Waterlevel</translate>:
               </small>
@@ -42,15 +45,11 @@
                 v-model="selectedWaterLevel"
                 class="form-control form-control-sm small"
               >
-                <option value="" v-if="Object.keys(waterLevels).length === 0">
-                  <translate>Current</translate>
+                <option value="ref">
+                  <translate>Depth Reference</translate>
                 </option>
-                <option
-                  v-for="wl in Object.keys(waterLevels)"
-                  :key="wl"
-                  :value="wl"
-                >
-                  {{ wl | surveyDate }}
+                <option value="current">
+                  <translate>Current Waterlevel</translate>
                 </option>
               </select>
             </div>
@@ -89,6 +88,46 @@
               </select>
             </div>
           </div>
+          <div class="mt-2 d-flex" v-if="additionalSurvey">
+            <button
+              v-if="differencesLoading"
+              class="btn btn-info btn-xs flex-fill"
+              disabled
+            >
+              <font-awesome-icon icon="spinner" spin class="mr-1" />
+              <translate>Calculating differences</translate>
+            </button>
+            <button
+              class="btn btn-info btn-xs flex-fill"
+              @click="differencesVisible ? showSurvey() : showDifferences()"
+              v-else
+            >
+              <translate v-if="differencesVisible" key="showsurvey"
+                >Show survey</translate
+              >
+              <translate v-else key="showdifferences"
+                >Show differences</translate
+              >
+            </button>
+            <button
+              v-if="!paneSetup.includes('FAIRWAYPROFILE')"
+              class="btn btn-info btn-xs ml-2"
+              @click="$store.commit('application/paneRotate')"
+              v-tooltip="rotatePanesTooltip"
+            >
+              <font-awesome-icon icon="redo" fixed-width />
+            </button>
+            <button
+              class="btn btn-info btn-xs ml-2"
+              @click="toggleSyncMaps()"
+              v-tooltip="syncMapsTooltip"
+            >
+              <font-awesome-icon
+                :icon="mapsAreSynced ? 'unlink' : 'link'"
+                fixed-width
+              />
+            </button>
+          </div>
           <hr class="w-100 mb-0" />
           <small class="text-muted d-block mt-2">
             <translate>Saved cross profiles</translate>:
@@ -171,10 +210,8 @@
               :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" }}
+                <font-awesome-icon :icon="cutToolEnabled ? 'times' : 'plus'" />
+                {{ cutToolEnabled ? "Cancel" : "New" }}
               </button>
             </div>
           </div>
@@ -217,6 +254,11 @@
   border-top-left-radius: $border-radius;
   border-bottom-left-radius: $border-radius;
 }
+
+input,
+select {
+  font-size: 0.8em;
+}
 </style>
 
 <script>
@@ -236,8 +278,9 @@
 import { mapState, mapGetters } from "vuex";
 import Feature from "ol/Feature";
 import LineString from "ol/geom/LineString";
-import { displayError, displayInfo } from "@/lib/errors.js";
-import { LAYERS } from "@/store/map.js";
+import { displayError, displayInfo } from "@/lib/errors";
+import { HTTP } from "@/lib/http";
+import { COMPARESURVEYS } from "@/components/paneSetups";
 
 export default {
   name: "profiles",
@@ -249,9 +292,8 @@
     };
   },
   computed: {
-    ...mapGetters("map", ["getVSourceByName"]),
-    ...mapState("application", ["showProfiles"]),
-    ...mapState("map", ["lineTool", "polygonTool", "cutTool"]),
+    ...mapState("application", ["showProfiles", "paneSetup"]),
+    ...mapState("map", ["openLayersMaps", "syncedMaps", "cutToolEnabled"]),
     ...mapState("bottlenecks", [
       "bottlenecksList",
       "surveys",
@@ -262,26 +304,26 @@
       "startPoint",
       "endPoint",
       "profileLoading",
-      "waterLevels"
+      "differencesLoading",
+      "waterLevels",
+      "currentProfile"
     ]),
+    ...mapGetters("map", ["openLayersMap"]),
+    ...mapGetters("bottlenecks", ["orderedBottlenecks"]),
     profilesLable() {
-      return this.$gettext("Profiles");
+      return this.$gettext("Bottleneck Surveys");
     },
     selectedBottleneck: {
       get() {
         return this.$store.state.bottlenecks.selectedBottleneck;
       },
       set(name) {
-        this.$store
-          .dispatch("bottlenecks/setSelectedBottleneck", name)
-          .then(() => {
-            this.$store.commit("bottlenecks/setFirstSurveySelected");
-          });
+        this.$store.dispatch("bottlenecks/setSelectedBottleneck", name);
       }
     },
     selectedWaterLevel: {
       get() {
-        return this.$store.state.fairwayprofile.selectedWaterLevel.date || "";
+        return this.$store.state.fairwayprofile.selectedWaterLevel;
       },
       set(value) {
         this.$store.commit("fairwayprofile/setSelectedWaterLevel", value);
@@ -312,8 +354,11 @@
         this.$store.commit("fairwayprofile/selectedCut", cut);
         if (!cut) {
           this.$store.commit("fairwayprofile/clearCurrentProfile");
-          this.$store.commit("application/showSplitscreen", false);
-          this.getVSourceByName(LAYERS.CUTTOOL).clear();
+          this.openLayersMaps.forEach(m => {
+            m.getLayer("CUTTOOL")
+              .getSource()
+              .clear();
+          });
         }
       }
     },
@@ -337,6 +382,28 @@
         .map(coord => parseFloat(coord.trim()))
         .filter(c => Number(c) === c);
       return coordinates.length === 4;
+    },
+    differencesVisible() {
+      return (
+        this.openLayersMap(COMPARESURVEYS.compare.id) &&
+        !this.openLayersMap(COMPARESURVEYS.compare.id)
+          .getLayer("BOTTLENECKISOLINE")
+          .getVisible() &&
+        this.openLayersMap(COMPARESURVEYS.compare.id)
+          .getLayer("DIFFERENCES")
+          .getVisible()
+      );
+    },
+    rotatePanesTooltip() {
+      return this.$gettext("Rotate Maps");
+    },
+    syncMapsTooltip() {
+      return this.$gettext(
+        this.mapsAreSynced ? "Unsynchronize Maps" : "Synchronize Maps"
+      );
+    },
+    mapsAreSynced() {
+      return this.syncedMaps.includes(COMPARESURVEYS.compare.id);
     }
   },
   watch: {
@@ -349,6 +416,22 @@
       this.loadProfile(survey);
     },
     additionalSurvey(survey) {
+      if (survey) {
+        this.loadDifferences();
+        this.$store.commit(
+          "application/paneSetup",
+          Object.keys(this.currentProfile).length
+            ? "COMPARESURVEYS_FAIRWAYPROFILE"
+            : "COMPARESURVEYS"
+        );
+        this.$store.commit("map/syncedMaps", [COMPARESURVEYS.compare.id]);
+      } else {
+        this.$store.commit(
+          "application/paneSetup",
+          Object.keys(this.currentProfile).length ? "FAIRWAYPROFILE" : "DEFAULT"
+        );
+        this.$store.commit("map/syncedMaps", []);
+      }
       this.loadProfile(survey);
     },
     selectedCut(cut) {
@@ -358,25 +441,75 @@
     }
   },
   methods: {
+    toggleSyncMaps() {
+      if (this.mapsAreSynced) {
+        this.$store.commit(
+          "map/syncedMaps",
+          this.syncedMaps.filter(m => m !== COMPARESURVEYS.compare.id)
+        );
+      } else {
+        this.$store.commit("map/syncedMaps", [COMPARESURVEYS.compare.id]);
+      }
+    },
+    loadDifferences() {
+      this.$store.commit("fairwayprofile/setDifferencesLoading", true);
+      HTTP.post(
+        "/diff",
+        {
+          bottleneck: this.selectedSurvey.bottleneck_id,
+          minuend: this.selectedSurvey.date_info,
+          subtrahend: this.additionalSurvey.date_info
+        },
+        {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token")
+          }
+        }
+      )
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        })
+        .finally(() => {
+          this.$store.commit("fairwayprofile/setDifferencesLoading", false);
+        });
+    },
+    showDifferences() {
+      this.openLayersMap(COMPARESURVEYS.compare.id)
+        .getLayer("BOTTLENECKISOLINE")
+        .setVisible(false);
+      this.openLayersMap(COMPARESURVEYS.compare.id)
+        .getLayer("DIFFERENCES")
+        .setVisible(true);
+    },
+    showSurvey() {
+      this.openLayersMap(COMPARESURVEYS.compare.id)
+        .getLayer("BOTTLENECKISOLINE")
+        .setVisible(true);
+      this.openLayersMap(COMPARESURVEYS.compare.id)
+        .getLayer("DIFFERENCES")
+        .setVisible(false);
+    },
     close() {
       this.$store.commit("application/showProfiles", false);
     },
     loadProfile(survey) {
       if (survey) {
         this.$store.commit("fairwayprofile/profileLoading", true);
-        this.$store.commit("application/splitscreenLoading", true);
         this.$store
           .dispatch("fairwayprofile/loadProfile", survey)
           .finally(() => {
             this.$store.commit("fairwayprofile/profileLoading", false);
-            this.$store.commit("application/splitscreenLoading", false);
           });
       }
     },
     toggleCutTool() {
-      this.cutTool.setActive(!this.cutTool.getActive());
-      this.lineTool.setActive(false);
-      this.polygonTool.setActive(false);
+      this.$store.commit("map/cutToolEnabled", !this.cutToolEnabled);
+      this.$store.commit("map/lineToolEnabled", false);
+      this.$store.commit("map/polygonToolEnabled", false);
       this.$store.commit("map/setCurrentMeasurement", null);
     },
     onCopyCoordinates() {
@@ -402,14 +535,22 @@
       coordinates = coordinates.filter(c => Number(c) === c);
       if (coordinates.length === 4) {
         // draw line on map
-        this.getVSourceByName(LAYERS.CUTTOOL).clear();
+        this.openLayersMaps.forEach(m => {
+          m.getLayer("CUTTOOL")
+            .getSource()
+            .clear();
+        });
         const cut = new Feature({
           geometry: new LineString([
             [coordinates[0], coordinates[1]],
             [coordinates[2], coordinates[3]]
           ]).transform("EPSG:4326", "EPSG:3857")
         });
-        this.getVSourceByName(LAYERS.CUTTOOL).addFeature(cut);
+        this.openLayersMaps.forEach(m => {
+          m.getLayer("CUTTOOL")
+            .getSource()
+            .addFeature(cut);
+        });
 
         // draw diagram
         this.$store.dispatch("fairwayprofile/cut", cut);
@@ -481,7 +622,7 @@
         bn => bn.properties.name === this.selectedBottleneck
       );
       if (!bottleneck) return;
-      this.$store.commit("map/moveToExtent", {
+      this.$store.dispatch("map/moveToFeauture", {
         feature: bottleneck,
         zoom: 17,
         preventZoomOut: true
--- a/client/src/components/gauge/Gauges.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/gauge/Gauges.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -12,13 +12,8 @@
         :closeCallback="close"
       />
       <div class="box-body">
-        <transition name="fade">
-          <div class="loading" v-if="loading">
-            <font-awesome-icon icon="spinner" spin />
-          </div>
-        </transition>
+        <UISpinnerOverlay v-if="loading" />
         <select
-          @change="moveToGauge"
           v-model="selectedGaugeISRS"
           class="form-control font-weight-bold"
         >
@@ -67,11 +62,23 @@
           >
             <translate>Show Waterlevels</translate>
           </button>
-          <hr />
+          <hr class="mb-1" />
+          <div class="row no-gutters mb-2">
+            <small class="text-muted">
+              <translate>Compare to</translate>:
+            </small>
+            <input
+              type="number"
+              step="1"
+              min="1900"
+              :max="new Date().getFullYear()"
+              class="form-control form-control-sm small"
+              v-model="yearCompare"
+            />
+          </div>
           <button
             @click="showHydrologicalConditionsDiagram()"
             class="btn btn-sm btn-info d-block w-100 mt-2"
-            disabled
           >
             <translate>Show Hydrological Conditions</translate>
           </button>
@@ -81,6 +88,13 @@
   </div>
 </template>
 
+<style lang="scss" scoped>
+input,
+select {
+  font-size: 0.8em;
+}
+</style>
+
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -96,7 +110,7 @@
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
 import { mapState, mapGetters } from "vuex";
-import { displayError } from "@/lib/errors.js";
+import { displayError } from "@/lib/errors";
 
 export default {
   data() {
@@ -105,8 +119,8 @@
     };
   },
   computed: {
-    ...mapState("application", ["showGauges", "activeSplitscreenId"]),
-    ...mapState("gauges", ["gauges"]),
+    ...mapState("application", ["showGauges", "paneSetup"]),
+    ...mapState("gauges", ["gauges", "longtermInterval"]),
     ...mapGetters("gauges", ["selectedGauge"]),
     gaugesLabel() {
       return this.$gettext("Gauges");
@@ -146,15 +160,27 @@
       set(date) {
         this.$store.commit("gauges/dateTo", date);
       }
+    },
+    yearCompare: {
+      get() {
+        return this.$store.state.gauges.yearCompare;
+      },
+      set(year) {
+        this.$store.commit("gauges/yearCompare", year);
+      }
     }
   },
   watch: {
-    selectedGaugeISRS() {
-      if (this.activeSplitscreenId === "gauge-waterlevel") {
-        this.showWaterlevelDiagram();
-      }
-      if (this.activeSplitscreenId === "gauge-hydrological-conditions") {
-        this.showHydrologicalConditionsDiagram();
+    selectedGaugeISRS(gauge) {
+      if (gauge) {
+        let coordinates = this.selectedGauge.geometry.coordinates;
+        this.$store.dispatch("map/moveMap", {
+          coordinates,
+          zoom: 15,
+          preventZoomOut: true
+        });
+      } else {
+        this.$store.commit("application/paneSetup", "DEFAULT");
       }
     }
   },
@@ -162,47 +188,8 @@
     close() {
       this.$store.commit("application/showGauges", false);
     },
-    moveToGauge() {
-      if (!this.selectedGauge) return;
-      this.$store.commit("map/moveToExtent", {
-        feature: this.selectedGauge,
-        zoom: null,
-        preventZoomOut: true
-      });
-    },
     showWaterlevelDiagram() {
-      // for panning the map to the gauge on opening the diagram: needs to be
-      // set outside of the expandCallback to not always refer to the currently
-      // selectedGauge
-      let coordinates = this.selectedGauge.geometry.coordinates;
-
-      // configure splitscreen
-      let splitscreenConf = {
-        id: "gauge-waterlevel",
-        component: "waterlevel",
-        title:
-          this.$gettext("Waterlevel") +
-          ": " +
-          this.gaugeLabel(this.selectedGauge),
-        icon: "ruler-vertical",
-        closeCallback: () => {
-          this.$store.commit("gauges/selectedGaugeISRS", null);
-          this.$store.commit("gauges/waterlevels", []);
-        },
-        expandCallback: () => {
-          this.$store.commit("map/moveMap", {
-            coordinates,
-            zoom: 17,
-            preventZoomOut: true
-          });
-        }
-      };
-      this.$store.commit("application/addSplitscreen", splitscreenConf);
-      this.$store.commit("application/activeSplitscreenId", splitscreenConf.id);
-      this.$store.commit("application/splitscreenLoading", true);
       this.loading = true;
-      this.$store.commit("application/showSplitscreen", true);
-
       Promise.all([
         this.$store.dispatch("gauges/loadWaterlevels"),
         this.$store.dispatch("gauges/loadNashSutcliffe")
@@ -215,45 +202,25 @@
           });
         })
         .finally(() => {
-          this.$store.commit("application/splitscreenLoading", false);
+          this.$store.commit(
+            "application/paneSetup",
+            [
+              "GAUGE_HYDROLOGICALCONDITIONS",
+              "GAUGE_WATERLEVEL_HYDROLOGICALCONDITIONS"
+            ].includes(this.paneSetup)
+              ? "GAUGE_WATERLEVEL_HYDROLOGICALCONDITIONS"
+              : "GAUGE_WATERLEVEL"
+          );
           this.loading = false;
         });
     },
     showHydrologicalConditionsDiagram() {
-      // for panning the map to the gauge on opening the diagram: needs to be
-      // set outside of the expandCallback to not always refer to the currently
-      // selectedGauge
-      let coordinates = this.selectedGauge.geometry.coordinates;
+      this.loading = true;
 
-      // configure splitscreen
-      let splitscreenConf = {
-        id: "gauge-hydrological-conditions",
-        component: "hydrological-conditions",
-        title:
-          this.$gettext("Hydrological Conditions") +
-          ": " +
-          this.gaugeLabel(this.selectedGauge),
-        icon: "ruler-vertical",
-        closeCallback: () => {
-          this.$store.commit("gauges/selectedGaugeISRS", null);
-          this.$store.commit("gauges/meanWaterlevels", []);
-        },
-        expandCallback: () => {
-          this.$store.commit("map/moveMap", {
-            coordinates,
-            zoom: 17,
-            preventZoomOut: true
-          });
-        }
-      };
-      this.$store.commit("application/addSplitscreen", splitscreenConf);
-      this.$store.commit("application/activeSplitscreenId", splitscreenConf.id);
-      this.$store.commit("application/splitscreenLoading", true);
-      this.loading = true;
-      this.$store.commit("application/showSplitscreen", true);
-
-      this.$store
-        .dispatch("gauges/loadMeanWaterlevels")
+      Promise.all([
+        this.$store.dispatch("gauges/loadLongtermWaterlevels"),
+        this.$store.dispatch("gauges/loadYearWaterlevels")
+      ])
         .catch(error => {
           const { status, data } = error.response;
           displayError({
@@ -262,7 +229,15 @@
           });
         })
         .finally(() => {
-          this.$store.commit("application/splitscreenLoading", false);
+          this.$store.commit(
+            "application/paneSetup",
+            [
+              "GAUGE_WATERLEVEL",
+              "GAUGE_WATERLEVEL_HYDROLOGICALCONDITIONS"
+            ].includes(this.paneSetup)
+              ? "GAUGE_WATERLEVEL_HYDROLOGICALCONDITIONS"
+              : "GAUGE_HYDROLOGICALCONDITIONS"
+          );
           this.loading = false;
         });
     },
--- a/client/src/components/gauge/HydrologicalConditions.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/gauge/HydrologicalConditions.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,53 +1,93 @@
 <template>
-  <div
-    class="d-flex flex-fill justify-content-center align-items-center diagram-container"
-  >
-    <div v-if="!meanWaterlevels.length">
-      <translate>No data available.</translate>
+  <div class="d-flex flex-column flex-fill">
+    <UIBoxHeader
+      icon="ruler-vertical"
+      :title="title"
+      :closeCallback="close"
+      class="rounded-0"
+    />
+    <div class="d-flex flex-fill">
+      <DiagramLegend id="diagramlegendId">
+        <div class="legend">
+          <span
+            style="background-color: red; width: 20px; height: 20px;"
+          ></span>
+          {{ yearCompare }}
+        </div>
+        <div class="legend">
+          <span
+            style="background-color: orange; width: 20px; height: 20px;"
+          ></span>
+          Q25%
+        </div>
+        <div class="legend">
+          <span
+            style="background-color: black; width: 20px; height: 20px;"
+          ></span>
+          Median
+        </div>
+        <div class="legend">
+          <span
+            style="background-color: purple; width: 20px; height: 20px;"
+          ></span>
+          Q75%
+        </div>
+        <div class="legend">
+          <span
+            style="background-color: lightsteelblue; width: 20px; height: 20px;"
+          ></span>
+          Long-term Amplitude
+        </div>
+        <select
+          @change="applyChange"
+          v-model="form.template"
+          class="form-control d-block custom-select-sm w-100"
+        >
+          <option
+            v-for="template in templates"
+            :value="template"
+            :key="template.name"
+          >
+            {{ template.name }}
+          </option>
+        </select>
+        <div>
+          <button
+            @click="downloadPDF"
+            type="button"
+            class="btn btn-sm btn-info d-block w-100 mt-2"
+            :disabled="!longtermWaterlevels.length"
+          >
+            <translate>Export to PDF</translate>
+          </button>
+        </div>
+        <a
+          :class="[
+            'btn btn-sm btn-info d-block w-100 mt-2',
+            { disabled: !longtermWaterlevels.length }
+          ]"
+          :href="csvLink"
+          :download="csvFileName"
+        >
+          <translate>Export as CSV</translate>
+        </a>
+      </DiagramLegend>
+      <div
+        class="d-flex flex-fill justify-content-center align-items-center"
+        :id="containerId"
+      >
+        <div v-if="!longtermWaterlevels.length">
+          <translate>No data available.</translate>
+        </div>
+      </div>
     </div>
+    <div
+      id="tmpContainer"
+      style="position: absolute; z-index: -1; top: 600px;"
+    ></div>
   </div>
 </template>
 
-<style lang="sass" scoped>
-.diagram-container
-  /deep/
-    .line
-      clip-path: url(#clip)
-
-    .hdc-line,
-    .ldc-line,
-    .mw-line
-      stroke-width: 1
-      fill: none
-      clip-path: url(#clip)
-    .hdc-line
-      stroke: red
-    .ldc-line
-      stroke: green
-    .mw-line
-      stroke: grey
-    .ref-waterlevel-label
-      font-size: 11px
-      fill: #999
-
-    .tick
-      line
-        stroke-dasharray: 5
-        stroke: #ccc
-
-    .zoom
-      cursor: move
-      fill: none
-      pointer-events: all
-    .brush
-      .selection
-        stroke: none
-        fill-opacity: 0.2
-      .handle
-        stroke: rgba($color-info, 0.5)
-        fill: rgba($color-info, 0.5)
-</style>
-
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -55,330 +95,1019 @@
  * SPDX-License-Identifier: AGPL-3.0-or-later
  * License-Filename: LICENSES/AGPL-3.0.txt
  *
- * Copyright (C) 2018 by via donau
+ * Copyright (C) 2019 by via donau
  *   – Österreichische Wasserstraßen-Gesellschaft mbH
  * Software engineering by Intevation GmbH
  *
  * Author(s):
  * Markus Kottländer <markus.kottlaender@intevation.de>
+ * Fadi Abbud <fadi.abbud@intevation.de>
  */
 
 import { mapState, mapGetters } from "vuex";
-import * as d3Base from "d3";
+import * as d3 from "d3";
 import debounce from "debounce";
-import { lineChunked } from "d3-line-chunked";
 import { startOfYear, endOfYear } from "date-fns";
-
-// we should load only d3 modules we need but for now we'll go with the lazy way
-// https://www.giacomodebidda.com/how-to-import-d3-plugins-with-webpack/
-const d3 = Object.assign(d3Base, { lineChunked });
+import jsPDF from "jspdf";
+import canvg from "canvg";
+import { pdfgen } from "@/lib/mixins";
+import { HTTP } from "@/lib/http";
+import { displayError } from "@/lib/errors";
 
 export default {
+  mixins: [pdfgen],
+  components: {
+    DiagramLegend: () => import("@/components/DiagramLegend")
+  },
+  data() {
+    return {
+      containerId: "hydrologicalconditions-diagram-container",
+      svg: null,
+      diagram: null,
+      navigation: null,
+      dimensions: null,
+      extent: null,
+      scale: null,
+      axes: null,
+      templateData: null,
+      form: {
+        template: null,
+        form: null
+      },
+      templates: [],
+      defaultTemplate: {
+        name: "Default",
+        properties: {
+          paperSize: "a4"
+        },
+        elements: [
+          {
+            type: "diagram",
+            position: "topleft",
+            offset: { x: 15, y: 50 },
+            width: 290,
+            height: 100
+          },
+          {
+            type: "diagramlegend",
+            position: "topleft",
+            offset: { x: 30, y: 150 },
+            colot: "black"
+          },
+          {
+            type: "diagramtitle",
+            position: "topleft",
+            offset: { x: 50, y: 26 },
+            fontsize: 22,
+            color: "steelblue"
+          }
+        ]
+      },
+      pdf: {
+        doc: null,
+        width: 420,
+        height: 297
+      }
+    };
+  },
   computed: {
-    ...mapState("gauges", ["meanWaterlevels"]),
-    ...mapGetters("gauges", ["selectedGauge", "minMaxWaterlevelForDay"])
+    ...mapState("application", ["paneSetup"]),
+    ...mapState("gauges", [
+      "longtermWaterlevels",
+      "yearWaterlevels",
+      "yearCompare",
+      "longtermInterval"
+    ]),
+    ...mapGetters("gauges", ["selectedGauge"]),
+    title() {
+      return `${this.selectedGauge.properties.objname}: ${this.$gettext(
+        "Hydrological Conditions"
+      )} (${this.longtermInterval.join(" - ")})`;
+    },
+    csvLink() {
+      return "data:text/csv;charset=utf-8," + encodeURIComponent(this.csvData);
+    },
+    csvFileName() {
+      return `${this.$gettext("hydrological-conditions")}-${
+        this.selectedGauge.properties.objname
+      }-${this.longtermInterval.join(" - ")}.csv`;
+    },
+    csvData() {
+      // We cannot directly use the csv data provided by the backend because the
+      // diagram uses data from two endpoints, longterm- and yearWaterlevels.
+      // So we need to merge them here to have them in one csv export.
+      let merged = this.longtermWaterlevels
+        .filter(d => d) // copy array, don't mutate original
+        .map(d => {
+          let yearData = this.yearWaterlevels.find(y => {
+            return d.date.getTime() === y.date.getTime();
+          });
+          d[this.yearCompare] = yearData ? yearData.mean : "";
+          return `${d.date.getMonth() + 1}-${d.date.getDate()};${d.min};${
+            d.max
+          };${d.mean};${d.median};${d.q25};${d.q75};${d[this.yearCompare]}`;
+        })
+        .join("\n");
+      return `#Interval: ${this.longtermInterval.join(
+        " - "
+      )}\n#date;#min;#max;#mean;#median;#q25;#q75;#${
+        this.yearCompare
+      }\n${merged}`;
+    }
   },
   watch: {
-    meanWaterlevels() {
+    paneSetup() {
+      this.$nextTick(() => this.drawDiagram());
+    },
+    longtermWaterlevels() {
+      this.drawDiagram();
+    },
+    yearWaterlevels() {
       this.drawDiagram();
     }
   },
   methods: {
+    close() {
+      this.$store.commit(
+        "application/paneSetup",
+        this.paneSetup === "GAUGE_WATERLEVEL_HYDROLOGICALCONDITIONS"
+          ? "GAUGE_WATERLEVEL"
+          : "DEFAULT"
+      );
+    },
+    downloadPDF() {
+      if (this.templateData) {
+        this.pdf.doc = new jsPDF(
+          "l",
+          "mm",
+          this.templateData.properties.paperSize
+        );
+        // pdf width and height in millimeter (landscape)
+        this.pdf.width =
+          this.templateData.properties.paperSize === "a3" ? 420 : 297;
+        this.pdf.height =
+          this.templateData.properties.paperSize === "a3" ? 297 : 210;
+        // default values if some are missing in template
+        let defaultFontSize = 11,
+          defaultColor = "black",
+          defaultWidth = 70,
+          defaultTextColor = "black",
+          defaultBorderColor = "white",
+          defaultBgColor = "white",
+          defaultRounding = 2,
+          defaultPadding = 2,
+          defaultOffset = { x: 0, y: 0 };
+        this.templateData.elements.forEach(e => {
+          switch (e.type) {
+            case "diagram": {
+              this.addDiagram(
+                e.position,
+                e.offset || defaultOffset,
+                e.width,
+                e.height
+              );
+              break;
+            }
+            case "diagramtitle": {
+              let gaugeInfo =
+                this.selectedGauge.properties.objname +
+                " (" +
+                this.selectedGauge.id
+                  .split(".")[1]
+                  .replace(/[()]/g, "")
+                  .split(",")[3] +
+                "): Hydrological Conditions " +
+                this.longtermInterval.join(" - ");
+              this.addDiagramTitle(
+                e.position,
+                e.offset || defaultOffset,
+                e.fontsize || defaultFontSize,
+                e.color || defaultColor,
+                gaugeInfo
+              );
+              break;
+            }
+            case "diagramlegend": {
+              this.addDiagramLegend(
+                e.position,
+                e.offset || defaultOffset,
+                e.color || defaultColor
+              );
+              break;
+            }
+            case "image": {
+              this.addImage(
+                e.url,
+                e.format || "",
+                e.position,
+                e.offset || defaultOffset,
+                e.width || 90,
+                e.height || 60
+              );
+              break;
+            }
+            case "text": {
+              this.addText(
+                e.position,
+                e.offset || defaultOffset,
+                e.width || defaultWidth,
+                e.fontsize || defaultFontSize,
+                e.color || defaultTextColor,
+                e.text || ""
+              );
+              break;
+            }
+            case "box": {
+              this.addBox(
+                e.position,
+                e.offset || defaultOffset,
+                e.width || 90,
+                e.height || 60,
+                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
+                e.color || defaultBgColor,
+                e.brcolor || defaultBorderColor
+              );
+
+              break;
+            }
+            case "textbox": {
+              this.addTextBox(
+                e.position,
+                e.offset || defaultOffset,
+                e.width,
+                e.height,
+                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
+                e.padding || defaultPadding,
+                e.fontsize || defaultFontSize,
+                e.color || defaultTextColor,
+                e.background || defaultBgColor,
+                e.text || "",
+                e.brcolor || defaultBorderColor
+              );
+              break;
+            }
+          }
+        });
+      }
+      this.pdf.doc.save(
+        this.selectedGauge.properties.objname +
+          " Hydrological-condition Diagram.pdf"
+      );
+    },
+    addDiagram(position, offset, width, height) {
+      let x = offset.x,
+        y = offset.y;
+      // check if there are tow diagrams on the screen
+      // draw the diagram in a separated html element to get the full size
+      if (
+        ["GAUGE_WATERLEVEL_HYDROLOGICALCONDITIONS"].indexOf(this.paneSetup) !==
+        -1
+      ) {
+        this.containerId = "tmpContainer";
+        // set width and height
+        document.querySelector("#tmpContainer").style.width =
+          document.querySelector("#hydrologicalconditions-diagram-container")
+            .clientWidth *
+            2 +
+          document.querySelector("#diagramlegendId").clientWidth +
+          "px";
+        document.querySelector("#tmpContainer").style.height =
+          document.querySelector("#hydrologicalconditions-diagram-container")
+            .clientHeight + "px";
+        this.drawDiagram();
+      }
+      var svg = document.getElementById(this.containerId).innerHTML;
+      if (svg) {
+        svg = svg.replace(/\r?\n|\r/g, "").trim();
+      }
+      this.containerId = "hydrologicalconditions-diagram-container";
+      var canvas = document.createElement("canvas");
+      canvas.width = window.innerWidth;
+      canvas.height = window.innerHeight / 2;
+      canvg(canvas, svg, {
+        ignoreMouse: true,
+        ignoreAnimation: true,
+        ignoreDimensions: true
+      });
+      var imgData = canvas.toDataURL("image/png");
+      // use default width,height if they are missing in the template definition
+      if (!width) {
+        width = this.templateData.properties.paperSize === "a3" ? 380 : 290;
+      }
+      if (!height) {
+        height = this.templateData.properties.paperSize === "a3" ? 130 : 100;
+      }
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - height;
+      }
+      this.pdf.doc.addImage(imgData, "PNG", x, y, width, height);
+    },
+    applyChange() {
+      if (this.form.template.hasOwnProperty("properties")) {
+        this.templateData = this.defaultTemplate;
+        return;
+      }
+      if (this.form.template) {
+        HTTP.get("/templates/diagram/" + this.form.template.name, {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token"),
+            "Content-type": "text/xml; charset=UTF-8"
+          }
+        })
+          .then(response => {
+            this.templateData = response.data.template_data;
+          })
+          .catch(e => {
+            const { status, data } = e.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          });
+      }
+    },
+    // Diagram legend
+    addDiagramLegend(position, offset, color) {
+      let x = offset.x,
+        y = offset.y;
+      let width =
+        (this.pdf.doc.getStringUnitWidth("Long-term Amplitude") * 10) /
+          (72 / 25.6) +
+        5;
+      // if position is on the right, x needs to be calculate with pdf width and
+      // the size of the element
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - this.getTextHeight(4);
+      }
+      this.pdf.doc.setFontSize(10);
+      this.pdf.doc.setTextColor(color);
+      this.pdf.doc.setDrawColor("white");
+      this.pdf.doc.setFillColor("red");
+      this.pdf.doc.circle(x, y, 2, "FD");
+      this.pdf.doc.text(x + 3, y + 1, "" + this.yearCompare);
+      this.pdf.doc.setFillColor("orange");
+      this.pdf.doc.circle(x, y + 5, 2, "FD");
+      this.pdf.doc.text(x + 3, y + 6, "Q25%");
+      this.pdf.doc.setFillColor("black");
+      this.pdf.doc.circle(x, y + 10, 2, "FD");
+      this.pdf.doc.text(x + 3, y + 11, "Median ");
+      this.pdf.doc.setFillColor("purple");
+      this.pdf.doc.circle(x, y + 15, 2, "FD");
+      this.pdf.doc.text(x + 3, y + 16, "Q75%");
+      this.pdf.doc.setFillColor("lightsteelblue");
+      this.pdf.doc.circle(x, y + 20, 2, "FD");
+      this.pdf.doc.text(x + 3, y + 21, "Long-term Amplitude");
+    },
     drawDiagram() {
-      // TODO: Optimize code. I'm pretty sure all of this can be done in a much
-      // more elegant way and with less lines of code.
-
       // remove old diagram
-      d3.select(".diagram-container svg").remove();
+      d3.select("#" + this.containerId + " svg").remove();
+      if (!this.selectedGauge || !this.longtermWaterlevels.length) return;
+      // PREPARE HELPERS
 
-      if (!this.selectedGauge || !this.meanWaterlevels.length) return;
-
-      // get HDC/LDC/MW of the gauge
-      let refWaterLevels = JSON.parse(
+      // HDC/LDC/MW for the selected gauge
+      const refWaterLevels = JSON.parse(
         this.selectedGauge.properties.reference_water_levels
       );
 
-      // CREATE SVG AND SET DIMENSIONS/MARGINS
+      // dimensions (widths, heights, margins)
+      this.dimensions = this.getDimensions();
+
+      // get min/max values for date and waterlevel axis
+      this.extent = this.getExtent(refWaterLevels);
+
+      // scaling helpers
+      this.scale = this.getScale();
 
-      let svgWidth = document.querySelector(".diagram-container").clientWidth;
-      let svgHeight = document.querySelector(".diagram-container").clientHeight;
-      let svg = d3
-        .select(".diagram-container")
+      // creating the axes based on the scales
+      this.axes = this.getAxes();
+
+      // DRAW DIAGRAM/NAVIGATION AREAS
+
+      // create svg
+      this.svg = d3
+        .select("#" + this.containerId)
         .append("svg")
         .attr("width", "100%")
         .attr("height", "100%");
-      let mainMargin = { top: 20, right: 20, bottom: 110, left: 80 },
-        navMargin = {
-          top: svgHeight - mainMargin.top - 65,
-          right: 20,
-          bottom: 30,
-          left: 80
-        },
-        width = +svgWidth - mainMargin.left - mainMargin.right,
-        mainHeight = +svgHeight - mainMargin.top - mainMargin.bottom,
-        navHeight = +svgHeight - navMargin.top - navMargin.bottom;
+
+      // create container for main diagram
+      this.diagram = this.svg
+        .append("g")
+        .attr("class", "main")
+        .attr(
+          "transform",
+          `translate(${this.dimensions.mainMargin.left}, ${
+            this.dimensions.mainMargin.top
+          })`
+        );
+
+      // create container for navigation diagram
+      this.navigation = this.svg
+        .append("g")
+        .attr("class", "nav")
+        .attr(
+          "transform",
+          `translate(${this.dimensions.navMargin.left}, ${
+            this.dimensions.navMargin.top
+          })`
+        );
+
+      // define visible area, everything outside this area will be hidden
+      this.svg
+        .append("defs")
+        .append("clipPath")
+        .attr("id", "hydrocond-clip")
+        .append("rect")
+        .attr("width", this.dimensions.width)
+        .attr("height", this.dimensions.mainHeight);
 
-      // PREPARING AXES/SCALING
+      // DRAW DIAGRAM PARTS
+
+      // Each drawSomething function (with the exception of drawRefLines)
+      // returns a fuction to update the respective chart/area/etc. These
+      // updater functions are used by the zoom feature to rescale all elements.
+      const updaters = [];
+
+      // draw
+      updaters.push(this.drawAxes());
+      updaters.push(this.drawWaterlevelMinMaxAreaChart());
+      updaters.push(this.drawWaterlevelLineChart("median"));
+      updaters.push(this.drawWaterlevelLineChart("q25"));
+      updaters.push(this.drawWaterlevelLineChart("q75"));
+      updaters.push(this.drawWaterlevelLineChart("mean", this.yearWaterlevels));
+      updaters.push(this.drawNowLines());
+      this.drawRefLines(refWaterLevels); // static, doesn't need an updater
+
+      // INTERACTIONS
+
+      // create rectanlge on the main chart area to capture mouse events
+      const eventRect = this.svg
+        .append("rect")
+        .attr("id", "zoom-hydrocond")
+        .attr("class", "zoom")
+        .attr("width", this.dimensions.width)
+        .attr("height", this.dimensions.mainHeight)
+        .attr(
+          "transform",
+          `translate(${this.dimensions.mainMargin.left}, ${
+            this.dimensions.mainMargin.top
+          })`
+        );
 
-      // scaling helpers to convert real values to pixels
-      // based on the diagrams dimensions
-      let x = d3.scaleTime().range([0, width]),
-        x2 = d3.scaleTime().range([0, width]),
-        y = d3.scaleLinear().range([mainHeight, 0]),
-        y2 = d3.scaleLinear().range([navHeight, 0]);
-      // find min/max values for the waterlevel axis
-      // including HDC/LDC (+/- 1/8 HDC-LDC)
-      let WaterlevelMinMax = d3.extent(
-        [
-          ...this.meanWaterlevels,
-          {
-            waterlevel:
-              refWaterLevels.HDC + (refWaterLevels.HDC - refWaterLevels.LDC) / 8
-          },
-          {
-            waterlevel: Math.max(
-              refWaterLevels.LDC -
-                (refWaterLevels.HDC - refWaterLevels.LDC) / 8,
-              0
-            )
-          }
-        ],
-        d => d.waterlevel
+      this.createZoom(updaters, eventRect);
+      this.createTooltips(eventRect);
+      this.setInlineStyles();
+    },
+    setInlineStyles() {
+      this.svg.selectAll(".hide").attr("fill-opacity", 0);
+      this.svg
+        .selectAll(".line")
+        .attr("clip-path", "url(#hydrocond-clip)")
+        .attr("stroke-width", 2)
+        .attr("fill", "none");
+      this.svg.selectAll(".line.mean").attr("stroke", "red");
+      this.svg.selectAll(".line.median").attr("stroke", "black");
+      this.svg.selectAll(".line.q25").attr("stroke", "orange");
+      this.svg.selectAll(".line.q75").attr("stroke", "purple");
+      this.svg
+        .selectAll(".area")
+        .attr("clip-path", "url(#hydrocond-clip)")
+        .attr("stroke", "none")
+        .attr("fill", "lightsteelblue");
+      this.svg
+        .selectAll(".hdc-line, .ldc-line, .mw-line, .rn-line")
+        .attr("stroke-width", 1)
+        .attr("fill", "none")
+        .attr("clip-path", "url(#hydrocond-clip)");
+      this.svg.selectAll(".hdc-line").attr("stroke", "red");
+      this.svg.selectAll(".ldc-line").attr("stroke", "green");
+      this.svg.selectAll(".mw-line").attr("stroke", "grey");
+      this.svg.selectAll(".rn-line").attr("stroke", "grey");
+      this.svg
+        .selectAll(".ref-waterlevel-label")
+        .style("font-size", "10px")
+        .attr("fill", "black");
+      this.svg
+        .selectAll(".ref-waterlevel-label-background")
+        .attr("fill", "rgba(255, 255, 255, 0.6)");
+      this.svg
+        .selectAll(".now-line")
+        .attr("stroke", "#999")
+        .attr("stroke-width", 1)
+        .attr("stroke-dasharray", "5, 5")
+        .attr("clip-path", "url(#hydrocond-clip)");
+      this.svg
+        .selectAll(".now-line-label")
+        .attr("fill", "#999")
+        .style("font-size", "11px");
+      this.svg
+        .selectAll(".tick line")
+        .attr("stroke-dasharray", 5)
+        .attr("stroke", " #ccc");
+      this.svg.selectAll(".tick text").attr("fill", "black");
+      this.svg.selectAll(".domain").attr("stroke", "black");
+
+      this.svg
+        .selectAll(".zoom")
+        .attr("cursor", "move")
+        .attr("fill", "none")
+        .attr("pointer-events", "all");
+      this.svg
+        .selectAll(".brush .selection")
+        .attr("stroke", "none")
+        .attr("fill-opacity", 0.2);
+      this.svg
+        .selectAll(".brush .handle")
+        .attr("stroke", "rgba(23, 162, 184, 0.5)")
+        .attr("fill", "rgba(23, 162, 184, 0.5)");
+      this.svg
+        .selectAll(".chart-dots")
+        .attr("clip-path", "url(#hydrocond-clip)");
+      this.svg
+        .selectAll(".chart-dots .chart-dot")
+        .attr("fill", "black")
+        .attr("stroke", "black")
+        .attr("stroke-opacity", 0)
+        .style("pointer-events", "none")
+        .attr("fill-opacity", 0)
+        .transition()
+        .attr("fill-opacity", "0.1s");
+      this.svg
+        .selectAll(".chart-tooltip")
+        .attr("fill-opacity", 0)
+        .transition()
+        .attr("fill-opacity", "0.3s");
+      this.svg
+        .selectAll(".chart-tooltip rect")
+        .attr("fill", "#fff")
+        .attr("stroke", "#ccc");
+      this.svg
+        .selectAll(".chart-tooltip text")
+        .attr("fill", "666")
+        .style("font-size", "0.8em");
+    },
+    getDimensions() {
+      // dimensions and margins
+      const svgWidth = document.querySelector("#" + this.containerId)
+        .clientWidth;
+      const svgHeight = document.querySelector("#" + this.containerId)
+        .clientHeight;
+      const mainMargin = { top: 20, right: 20, bottom: 110, left: 80 };
+      const navMargin = {
+        top: svgHeight - mainMargin.top - 65,
+        right: 20,
+        bottom: 30,
+        left: 80
+      };
+      const width = +svgWidth - mainMargin.left - mainMargin.right;
+      const mainHeight = +svgHeight - mainMargin.top - mainMargin.bottom;
+      const navHeight = +svgHeight - navMargin.top - navMargin.bottom;
+
+      return { width, mainHeight, navHeight, mainMargin, navMargin };
+    },
+    getExtent(refWaterLevels) {
+      const waterlevelsRelevantForExtent = [];
+      this.longtermWaterlevels.forEach(wl => {
+        waterlevelsRelevantForExtent.push(wl.min, wl.max);
+      });
+      waterlevelsRelevantForExtent.push(
+        refWaterLevels.HDC + (refWaterLevels.HDC - refWaterLevels.LDC) / 8,
+        Math.max(
+          refWaterLevels.LDC - (refWaterLevels.HDC - refWaterLevels.LDC) / 4,
+          0
+        )
       );
+      return {
+        // set min/max values for the date axis
+        date: [startOfYear(new Date()), endOfYear(new Date())],
+        // set min/max values for the waterlevel axis
+        // including HDC (+ 1/8 HDC-LDC) and LDC (- 1/4 HDC-LDC)
+        waterlevel: d3.extent(waterlevelsRelevantForExtent)
+      };
+    },
+    getScale() {
+      // scaling helpers to convert real world values into pixels
+      const x = d3.scaleTime().range([0, this.dimensions.width]);
+      const y = d3.scaleLinear().range([this.dimensions.mainHeight, 0]);
+      const x2 = d3.scaleTime().range([0, this.dimensions.width]);
+      const y2 = d3.scaleLinear().range([this.dimensions.navHeight, 0]);
+
       // setting the min and max values for the diagram axes
-      let yearStart = startOfYear(new Date());
-      let yearEnd = endOfYear(new Date());
-      x.domain(d3.extent([yearStart, yearEnd]));
-      y.domain(WaterlevelMinMax);
+      x.domain(d3.extent(this.extent.date));
+      y.domain(this.extent.waterlevel);
       x2.domain(x.domain());
       y2.domain(y.domain());
-      // creating the axes based on these scales
-      let xAxis = d3
-        .axisTop(x)
-        .tickSizeInner(mainHeight)
-        .tickSizeOuter(0)
-        .tickFormat(date => {
-          return (d3.timeSecond(date) < date
-            ? d3.timeFormat(".%L")
-            : d3.timeMinute(date) < date
-            ? d3.timeFormat(":%S")
-            : d3.timeHour(date) < date
-            ? d3.timeFormat("%I:%M")
-            : d3.timeDay(date) < date
-            ? d3.timeFormat("%I %p")
-            : d3.timeMonth(date) < date
-            ? d3.timeWeek(date) < date
-              ? d3.timeFormat("%a %d")
-              : d3.timeFormat("%b %d")
-            : d3.timeFormat("%B"))(date);
-        });
-      let xAxis2 = d3.axisBottom(x2).tickFormat(d3.timeFormat("%B"));
-      let yAxis = d3
-        .axisRight(y)
-        .tickSizeInner(width)
-        .tickSizeOuter(0);
 
-      // PREPARING CHART FUNCTIONS
-
-      // points are "next to each other" when they are exactly 1 day apart
-      const isNext = (prev, current) =>
-        current.date - prev.date === 24 * 60 * 60 * 1000;
+      return { x, y, x2, y2 };
+    },
+    getAxes() {
+      return {
+        x: d3
+          .axisTop(this.scale.x)
+          .tickSizeInner(this.dimensions.mainHeight)
+          .tickSizeOuter(0)
+          .tickFormat(date => {
+            // make the x-axis label formats dynamic, based on zoom
+            // but never display year numbers since they don't make any sense in
+            // this diagram
+            return (d3.timeSecond(date) < date
+              ? d3.timeFormat(".%L")
+              : d3.timeMinute(date) < date
+              ? d3.timeFormat(":%S")
+              : d3.timeHour(date) < date
+              ? d3.timeFormat("%I:%M")
+              : d3.timeDay(date) < date
+              ? d3.timeFormat("%I %p")
+              : d3.timeMonth(date) < date
+              ? d3.timeWeek(date) < date
+                ? d3.timeFormat("%a %d")
+                : d3.timeFormat("%b %d")
+              : d3.timeFormat("%B"))(date);
+          }),
+        y: d3
+          .axisRight(this.scale.y)
+          .tickSizeInner(this.dimensions.width)
+          .tickSizeOuter(0),
+        x2: d3.axisBottom(this.scale.x2)
+      };
+    },
+    drawNowLines() {
+      const nowLine = d3
+        .line()
+        .x(d => this.scale.x(d.x))
+        .y(d => this.scale.y(d.y));
 
-      // waterlevel line in big chart
-      // d3-line-chunked plugin: https://github.com/pbeshai/d3-line-chunked
-      var mainLineChart = d3
-        .lineChunked()
-        .x(d => x(d.date))
-        .y(d => y(d.waterlevel))
-        .curve(d3.curveLinear)
-        .isNext(isNext)
-        .pointAttrs({ r: 2.2 });
-      // waterlevel line in small chart
-      let navLineChart = d3
-        .lineChunked()
-        .x(d => x2(d.date))
-        .y(d => y2(d.waterlevel))
-        .curve(d3.curveMonotoneX)
-        .isNext(isNext)
-        .pointAttrs({ r: 1.7 });
-      // hdc/ldc/mw
-      let refWaterlevelLine = d3
-        .line()
-        .x(d => x(d.x))
-        .y(d => y(d.y));
+      const nowLabel = selection => {
+        selection.attr(
+          "transform",
+          `translate(${this.scale.x(new Date())}, ${this.scale.y(
+            this.extent.waterlevel[1] - 16
+          )})`
+        );
+      };
 
-      // DRAWING MAINCHART
+      // draw in main
+      this.diagram
+        .append("path")
+        .datum([
+          { x: new Date(), y: this.extent.waterlevel[0] },
+          { x: new Date(), y: this.extent.waterlevel[1] - 20 }
+        ])
+        .attr("class", "now-line")
+        .attr("d", nowLine);
+      this.diagram // label
+        .append("text")
+        .text(this.$gettext("Now"))
+        .attr("class", "now-line-label")
+        .attr("text-anchor", "middle")
+        .call(nowLabel);
 
-      // define visible chart area
-      // everything outside this area will be hidden (clipped)
-      svg
-        .append("defs")
-        .append("clipPath")
-        .attr("id", "clip")
-        .append("rect")
-        .attr("width", width)
-        .attr("height", mainHeight);
+      // draw in nav
+      this.navigation
+        .append("path")
+        .datum([
+          { x: new Date(), y: this.extent.waterlevel[0] },
+          { x: new Date(), y: this.extent.waterlevel[1] - 20 }
+        ])
+        .attr("class", "now-line")
+        .attr(
+          "d",
+          d3
+            .line()
+            .x(d => this.scale.x2(d.x))
+            .y(d => this.scale.y2(d.y))
+        );
 
-      let mainChart = svg
-        .append("g")
-        .attr("class", "main")
-        .attr("transform", `translate(${mainMargin.left}, ${mainMargin.top})`);
-
-      // axes
-      mainChart
+      return () => {
+        this.diagram.select(".now-line").attr("d", nowLine);
+        this.diagram.select(".now-line-label").call(nowLabel);
+      };
+    },
+    drawAxes() {
+      this.diagram
         .append("g")
         .attr("class", "axis--x")
-        .attr("transform", `translate(0, ${mainHeight})`)
-        .call(xAxis)
+        .attr("transform", `translate(0, ${this.dimensions.mainHeight})`)
+        .call(this.axes.x)
         .selectAll(".tick text")
         .attr("y", 15);
-      mainChart // label
+      this.diagram // label
         .append("text")
         .text(this.$gettext("Waterlevel [cm]"))
         .attr("text-anchor", "middle")
-        .attr("transform", `translate(-45, ${mainHeight / 2}) rotate(-90)`);
-      mainChart
+        .attr(
+          "transform",
+          `translate(-45, ${this.dimensions.mainHeight / 2}) rotate(-90)`
+        );
+      this.diagram
         .append("g")
-        .call(yAxis)
+        .call(this.axes.y)
         .selectAll(".tick text")
         .attr("x", -25);
 
-      // reference waterlevels
-      // HDC
-      mainChart
-        .append("path")
-        .datum([
-          { x: 0, y: refWaterLevels.HDC },
-          { x: yearEnd, y: refWaterLevels.HDC }
-        ])
-        .attr("class", "hdc-line")
-        .attr("d", refWaterlevelLine);
-      mainChart // label
-        .append("text")
-        .text("HDC")
-        .attr("class", "ref-waterlevel-label")
-        .attr("x", x(yearEnd) - 20)
-        .attr("y", y(refWaterLevels.HDC) - 3);
-      // LDC
-      mainChart
-        .append("path")
-        .datum([
-          { x: 0, y: refWaterLevels.LDC },
-          { x: yearEnd, y: refWaterLevels.LDC }
-        ])
-        .attr("class", "ldc-line")
-        .attr("d", refWaterlevelLine);
-      mainChart // label
-        .append("text")
-        .text("LDC")
-        .attr("class", "ref-waterlevel-label")
-        .attr("x", x(yearEnd) - 20)
-        .attr("y", y(refWaterLevels.LDC) - 3);
-      // MW
-      mainChart
-        .append("path")
-        .datum([
-          { x: 0, y: refWaterLevels.MW },
-          { x: yearEnd, y: refWaterLevels.MW }
-        ])
-        .attr("class", "mw-line")
-        .attr("d", refWaterlevelLine);
-      mainChart // label
-        .append("text")
-        .text("MW")
-        .attr("class", "ref-waterlevel-label")
-        .attr("x", x(yearEnd) - 20)
-        .attr("y", y(refWaterLevels.MW) - 3);
-
-      // waterlevel chart
-      mainChart
-        .append("g")
-        .attr("class", "line")
-        .datum([])
-        .call(mainLineChart);
-
-      // DRAWING NAVCHART
-
-      let navChart = svg
-        .append("g")
-        .attr("class", "nav")
-        .attr("transform", `translate(${navMargin.left}, ${navMargin.top})`);
-
-      // axis (nav chart only has x-axis)
-      navChart
+      this.navigation
         .append("g")
         .attr("class", "axis axis--x")
-        .attr("transform", `translate(0, ${navHeight})`)
-        .call(xAxis2);
+        .attr("transform", `translate(0, ${this.dimensions.navHeight})`)
+        .call(this.axes.x2);
+
+      return () => {
+        this.diagram
+          .select(".axis--x")
+          .call(this.axes.x)
+          .selectAll(".tick text")
+          .attr("y", 15);
+      };
+    },
+    drawWaterlevelMinMaxAreaChart() {
+      const areaChart = isNav =>
+        d3
+          .area()
+          .x(d => this.scale[isNav ? "x2" : "x"](d.date))
+          .y0(d => this.scale[isNav ? "y2" : "y"](d.min))
+          .y1(d => this.scale[isNav ? "y2" : "y"](d.max));
+
+      this.diagram
+        .append("path")
+        .datum(this.longtermWaterlevels)
+        .attr("class", "area")
+        .attr("d", areaChart());
+
+      this.navigation
+        .append("path")
+        .datum(this.longtermWaterlevels)
+        .attr("class", "area")
+        .attr("d", areaChart(true));
 
-      // waterlevel chart
-      navChart
-        .append("g")
-        .attr("class", "line")
-        .datum([])
-        .call(navLineChart);
+      return () => {
+        this.diagram.select(".area").attr("d", areaChart());
+      };
+    },
+    drawWaterlevelLineChart(type, data) {
+      const lineChart = type =>
+        d3
+          .line()
+          .x(d => this.scale.x(d.date))
+          .y(d => this.scale.y(d[type]))
+          .curve(d3.curveLinear);
+      this.diagram
+        .append("path")
+        .attr("class", "line " + type)
+        .datum(data || this.longtermWaterlevels)
+        .attr("d", lineChart(type));
+
+      return () => {
+        this.diagram.select(".line." + type).attr("d", lineChart(type));
+      };
+    },
+    drawRefLines(refWaterLevels) {
+      const refWaterlevelLine = d3
+        .line()
+        .x(d => this.scale.x(d.x))
+        .y(d => this.scale.y(d.y));
 
-      // INTERACTIVITY
-
-      // selecting time period in nav chart
-      let brush = d3
+      for (let ref in refWaterLevels) {
+        if (refWaterLevels[ref]) {
+          this.diagram
+            .append("path")
+            .datum([
+              { x: 0, y: refWaterLevels[ref] },
+              { x: this.extent.date[1], y: refWaterLevels[ref] }
+            ])
+            .attr("class", ref.toLowerCase() + "-line")
+            .attr("d", refWaterlevelLine);
+          this.diagram // label
+            .append("rect")
+            .attr("class", "ref-waterlevel-label-background")
+            .attr("x", 1)
+            .attr("y", this.scale.y(refWaterLevels[ref]) - 13)
+            .attr("width", 55)
+            .attr("height", 12);
+          this.diagram
+            .append("text")
+            .text(`${ref} (${refWaterLevels[ref]})`)
+            .attr("class", "ref-waterlevel-label")
+            .attr("x", 5)
+            .attr("y", this.scale.y(refWaterLevels[ref]) - 3);
+        }
+      }
+    },
+    createZoom(updaters, eventRect) {
+      const brush = d3
         .brushX()
         .handleSize(4)
-        .extent([[0, 0], [width, navHeight]])
-        .on("brush end", () => {
-          if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom")
-            return; // ignore brush-by-zoom
-          let s = d3.event.selection || x2.range();
-          x.domain(s.map(x2.invert, x2));
-          mainChart.select(".line").call(mainLineChart);
-          mainChart
-            .select(".axis--x")
-            .call(xAxis)
-            .selectAll(".tick text")
-            .attr("y", 15);
-          svg
-            .select(".zoom")
-            .call(
-              zoom.transform,
-              d3.zoomIdentity.scale(width / (s[1] - s[0])).translate(-s[0], 0)
-            );
-        });
+        .extent([[0, 0], [this.dimensions.width, this.dimensions.navHeight]]);
 
-      // zooming with mousewheel in main chart
-      let zoom = d3
+      const zoom = d3
         .zoom()
         .scaleExtent([1, Infinity])
-        .translateExtent([[0, 0], [width, mainHeight]])
-        .extent([[0, 0], [width, mainHeight]])
-        .on("zoom", () => {
-          if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush")
-            return; // ignore zoom-by-brush
-          let t = d3.event.transform;
-          x.domain(t.rescaleX(x2).domain());
-          mainChart.select(".line").call(mainLineChart);
-          mainChart
-            .select(".axis--x")
-            .call(xAxis)
-            .selectAll(".tick text")
-            .attr("y", 15);
-          navChart
-            .select(".brush")
-            .call(brush.move, x.range().map(t.invertX, t));
-        });
+        .translateExtent([
+          [0, 0],
+          [this.dimensions.width, this.dimensions.mainHeight]
+        ])
+        .extent([[0, 0], [this.dimensions.width, this.dimensions.mainHeight]]);
 
-      navChart
+      brush.on("brush end", () => {
+        if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom")
+          return; // ignore brush-by-zoom
+        let s = d3.event.selection || this.scale.x2.range();
+        this.scale.x.domain(s.map(this.scale.x2.invert, this.scale.x2));
+        updaters.forEach(u => u && u());
+        this.setInlineStyles();
+        this.svg
+          .select(".zoom")
+          .call(
+            zoom.transform,
+            d3.zoomIdentity
+              .scale(this.dimensions.width / (s[1] - s[0]))
+              .translate(-s[0], 0)
+          );
+      });
+
+      zoom.on("zoom", () => {
+        if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush")
+          return; // ignore zoom-by-brush
+        let t = d3.event.transform;
+        this.scale.x.domain(t.rescaleX(this.scale.x2).domain());
+        updaters.forEach(u => u && u());
+        this.setInlineStyles();
+        this.navigation
+          .select(".brush")
+          .call(brush.move, this.scale.x.range().map(t.invertX, t));
+      });
+      zoom.on("start", () => {
+        this.svg.select(".chart-dot").style("opacity", 0);
+        this.svg.select(".chart-tooltip").style("opacity", 0);
+      });
+
+      this.navigation
         .append("g")
         .attr("class", "brush")
         .call(brush)
-        .call(brush.move, x.range());
+        .call(brush.move, this.scale.x.range());
 
-      svg
+      eventRect.call(zoom);
+    },
+    createTooltips(eventRect) {
+      // create clippable container for the dot
+      this.diagram
+        .append("g")
+        .attr("class", "chart-dots")
+        .append("circle")
+        .attr("class", "chart-dot")
+        .attr("r", 4);
+
+      // create container for the tooltip
+      const tooltip = this.diagram.append("g").attr("class", "chart-tooltip");
+      tooltip
         .append("rect")
-        .attr("class", "zoom")
-        .attr("width", width)
-        .attr("height", mainHeight)
-        .attr("transform", `translate(${mainMargin.left}, ${mainMargin.top})`)
-        .call(zoom);
+        .attr("rx", "0.25em")
+        .attr("ry", "0.25em");
+
+      // create container for multiple text rows
+      const tooltipText = tooltip.append("text").attr("text-anchor", "middle");
+
+      // padding inside the tooltip box and diagram padding to determine left
+      // and right offset from the diagram boundaries for the tooltip position.
+      const tooltipPadding = 10;
+      const diagramPadding = 5;
+
+      eventRect
+        .on("mouseover", () => {
+          this.diagram.select(".chart-dot").style("opacity", 1);
+          this.diagram.select(".chart-tooltip").style("opacity", 1);
+        })
+        .on("mouseout", () => {
+          this.diagram.select(".chart-dot").style("opacity", 0);
+          this.diagram.select(".chart-tooltip").style("opacity", 0);
+        })
+        .on("mousemove", () => {
+          // find data point closest to mouse
+          const x0 = this.scale.x.invert(
+              d3.mouse(document.getElementById("zoom-hydrocond"))[0]
+            ),
+            i = d3.bisector(d => d.date).left(this.longtermWaterlevels, x0, 1),
+            d0 = this.longtermWaterlevels[i - 1],
+            d1 = this.longtermWaterlevels[i] || d0,
+            d = x0 - d0.date > d1.date - x0 ? d1 : d0;
+
+          const coords = {
+            x: this.scale.x(d.date),
+            y: this.scale.y(d.median)
+          };
+
+          // position the dot
+          this.diagram
+            .select(".chart-dot")
+            .style("opacity", 1)
+            .attr("transform", `translate(${coords.x}, ${coords.y})`);
+
+          // remove current texts
+          tooltipText.selectAll("tspan").remove();
+
+          // write date
+          tooltipText
+            .append("tspan")
+            .attr("dominant-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text(
+              d.date.toLocaleString([], {
+                year: "2-digit",
+                month: "2-digit",
+                day: "2-digit"
+              })
+            );
+
+          tooltipText
+            .append("tspan")
+            .attr("x", 0)
+            .attr("y", 0)
+            .attr("dy", "1.4em")
+            .attr("dominant-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text(`Q75%: ${d.q75} cm`);
+          tooltipText
+            .append("tspan")
+            .attr("x", 0)
+            .attr("y", 0)
+            .attr("dy", "2.6em")
+            .attr("dominant-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text(`Median: ${d.median} cm`);
+          tooltipText
+            .append("tspan")
+            .attr("x", 0)
+            .attr("y", 0)
+            .attr("dy", "3.8em")
+            .attr("dominant-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text(`Q25%: ${d.q25} cm`);
+          tooltipText
+            .append("tspan")
+            .attr("x", 0)
+            .attr("y", 0)
+            .attr("dy", "5em")
+            .attr("dominant-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text(`Max: ${d.max} cm`);
+          tooltipText
+            .append("tspan")
+            .attr("x", 0)
+            .attr("y", 0)
+            .attr("dy", "6.2em")
+            .attr("dominant-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text(`Min: ${d.min} cm`);
+
+          const dYear = this.yearWaterlevels.find(
+            ywl => ywl.date.getTime() === d.date.getTime()
+          );
+          if (dYear) {
+            tooltipText
+              .append("tspan")
+              .attr("x", 0)
+              .attr("y", 0)
+              .attr("dy", "7.4em")
+              .attr("dominant-baseline", "hanging")
+              .attr("text-anchor", "middle")
+              .text(`${this.yearCompare}: ${dYear.mean.toFixed(1)} cm`);
+          }
+
+          // get text dimensions
+          const textBBox = tooltipText.node().getBBox();
+
+          this.diagram
+            .selectAll(".chart-tooltip text tspan")
+            .attr("x", textBBox.width / 2 + tooltipPadding)
+            .attr("y", tooltipPadding);
+
+          // position and scale tooltip box
+          const xMax =
+            this.dimensions.width -
+            (textBBox.width + diagramPadding + tooltipPadding * 2);
+          const tooltipX = Math.max(
+            diagramPadding,
+            Math.min(coords.x - (textBBox.width + tooltipPadding * 2) / 2, xMax)
+          );
+          let tooltipY = coords.y - (textBBox.height + tooltipPadding * 2) - 10;
+          if (coords.y < textBBox.height + tooltipPadding * 2) {
+            tooltipY = coords.y + 10;
+          }
+
+          this.diagram
+            .select(".chart-tooltip")
+            .style("opacity", 1)
+            .attr("transform", `translate(${tooltipX}, ${tooltipY})`)
+            .select("rect")
+            .attr("width", textBBox.width + tooltipPadding * 2)
+            .attr("height", textBBox.height + tooltipPadding * 2);
+        });
     }
   },
   created() {
@@ -386,9 +1115,36 @@
   },
   mounted() {
     this.drawDiagram();
+    this.templates[0] = this.defaultTemplate;
+    this.form.template = this.templates[0];
+    this.templateData = this.form.template;
+    HTTP.get("/templates/diagram", {
+      headers: {
+        "X-Gemma-Auth": localStorage.getItem("token"),
+        "Content-type": "text/xml; charset=UTF-8"
+      }
+    })
+      .then(response => {
+        if (response.data.length) {
+          this.templates = response.data;
+          this.form.template = this.templates[0];
+          this.templates[this.templates.length] = this.defaultTemplate;
+          this.applyChange();
+        }
+      })
+      .catch(e => {
+        const { status, data } = e.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      });
   },
   updated() {
     this.drawDiagram();
+  },
+  destroyed() {
+    window.removeEventListener("resize", debounce(this.drawDiagram));
   }
 };
 </script>
--- a/client/src/components/gauge/Waterlevel.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/gauge/Waterlevel.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,112 +1,90 @@
 <template>
-  <div
-    class="d-flex flex-fill justify-content-center align-items-center diagram-container"
-  >
-    <div v-if="!waterlevels.length">
-      <translate>No data available.</translate>
+  <div class="d-flex flex-column flex-fill">
+    <UIBoxHeader
+      icon="ruler-vertical"
+      :title="title"
+      :closeCallback="close"
+      class="rounded-0"
+    />
+    <div class="d-flex flex-fill">
+      <DiagramLegend id="diagramlegendId">
+        <div class="legend">
+          <span
+            style="background-color: steelblue; width: 20px; height: 20px;"
+          ></span>
+          Waterlevel
+        </div>
+        <div class="legend">
+          <span
+            style="width: 8px; height: 8px; background-color: rgba(70, 130, 180, 0.6); border: solid 7px rgba(70, 130, 180, 0.2); background-clip: padding-box; box-sizing: content-box;"
+          ></span>
+          Prediction
+        </div>
+        <div class="legend">
+          <span
+            style="background-color: rgba(0, 255, 0, 0.1); width: 20px; height: 20px;"
+          ></span>
+          Navigable Range
+        </div>
+        <div>
+          <select
+            @change="applyChange"
+            v-model="form.template"
+            class="form-control d-block custom-select-sm w-100"
+          >
+            <option
+              v-for="template in templates"
+              :value="template"
+              :key="template.name"
+            >
+              {{ template.name }}
+            </option>
+          </select>
+          <button
+            @click="downloadPDF"
+            type="button"
+            class="btn btn-sm btn-info d-block w-100 mt-2"
+            :disabled="!waterlevels.length"
+          >
+            <translate>Export to PDF</translate>
+          </button>
+          <a
+            :class="[
+              'btn btn-sm btn-info d-block w-100 mt-2',
+              { disabled: !waterlevels.length }
+            ]"
+            :href="csvLink"
+            :download="csvFileName"
+          >
+            <translate>Export as CSV</translate>
+          </a>
+
+          <button
+            @click="downloadSVG"
+            type="button"
+            class="btn btn-sm btn-info d-block w-100 mt-2"
+            :disabled="!waterlevels.length"
+          >
+            <translate>Export as SVG</translate>
+          </button>
+        </div>
+      </DiagramLegend>
+      <div
+        class="d-flex flex-fill justify-content-center align-items-center"
+        :id="containerId"
+      >
+        <div v-if="!waterlevels.length">
+          <translate>No data available.</translate>
+        </div>
+      </div>
     </div>
+    <div
+      id="pdfContainer"
+      style="position: absolute; z-index: -1; top: -9999px;"
+    ></div>
   </div>
 </template>
 
-<style lang="sass" scoped>
-.diagram-container
-  /deep/
-    .line
-      clip-path: url(#clip)
-      path
-        stroke: steelblue
-        stroke-width: 2
-        fill: none
-        &.d3-line-chunked-chunk-gap
-          stroke-width: 1
-          stroke-dasharray: 4, 4
-          stroke-opacity: 1
-      circle
-        stroke-width: 0
-        fill: steelblue
-
-    .hdc-line,
-    .ldc-line,
-    .mw-line
-      stroke-width: 1
-      fill: none
-      clip-path: url(#clip)
-    .hdc-line
-      stroke: red
-    .ldc-line
-      stroke: green
-    .mw-line
-      stroke: grey
-    .ref-waterlevel-label
-      font-size: 11px
-      fill: #999
-    .hdc-ldc-area
-      fill: rgba(0, 255, 0, 0.1)
-    .now-line
-      stroke: #999
-      stroke-width: 1
-      stroke-dasharray: 5, 5
-      clip-path: url(#clip)
-    .now-line-label
-      font-size: 11px
-      fill: #999
-    .prediction-area
-      fill: steelblue
-      fill-opacity: 0.2
-      clip-path: url(#clip)
-
-    path.nash-sutcliffe
-      fill: none
-      stroke: black
-      stroke-width: 1
-      clip-path: url(#clip)
-      &.ns72
-        fill: rgba(0, 0, 0, 0.05)
-    text.nash-sutcliffe
-      font-size: 10px
-      clip-path: url(#clip)
-      tspan:last-child
-        font-size: 9px
-        fill: #777
-
-    .tick
-      line
-        stroke-dasharray: 5
-        stroke: #ccc
-
-    .zoom
-      cursor: move
-      fill: none
-      pointer-events: all
-    .brush
-      .selection
-        stroke: none
-        fill-opacity: 0.2
-      .handle
-        stroke: rgba($color-info, 0.5)
-        fill: rgba($color-info, 0.5)
-
-    .chart-dots
-      clip-path: url(#clip)
-      .chart-dot
-        fill: steelblue
-        stroke: steelblue
-        pointer-events: none
-        opacity: 0
-        transition: opacity 0.1s
-    .chart-tooltip
-      opacity: 0
-      transition: opacity 0.3s
-      rect
-        fill: #fff
-        stroke: #ccc
-      text
-        fill: #666
-        font-size: 12px
-        tspan:last-child
-          font-weight: bold
-</style>
-
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -114,624 +92,1200 @@
  * SPDX-License-Identifier: AGPL-3.0-or-later
  * License-Filename: LICENSES/AGPL-3.0.txt
  *
- * Copyright (C) 2018 by via donau
+ * Copyright (C) 2019 by via donau
  *   – Österreichische Wasserstraßen-Gesellschaft mbH
  * Software engineering by Intevation GmbH
  *
  * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
+ * * Bernhard Reiter <bernhard@intevation.de>
+ * * Markus Kottländer <markus.kottlaender@intevation.de>
+ * * Fadi Abbud <fadi.abbud@intevation.de>
  */
 
 import { mapState, mapGetters } from "vuex";
 import * as d3Base from "d3";
 import { lineChunked } from "d3-line-chunked";
+import { endOfDay } from "date-fns";
 import debounce from "debounce";
-
+import jsPDF from "jspdf";
+import canvg from "canvg";
+import { saveAs } from "file-saver";
+import { pdfgen } from "@/lib/mixins";
+import { HTTP } from "@/lib/http";
+import { displayError } from "@/lib/errors";
 // we should load only d3 modules we need but for now we'll go with the lazy way
 // https://www.giacomodebidda.com/how-to-import-d3-plugins-with-webpack/
+// d3-line-chunked plugin: https://github.com/pbeshai/d3-line-chunked
 const d3 = Object.assign(d3Base, { lineChunked });
 
 export default {
+  mixins: [pdfgen],
+  components: {
+    DiagramLegend: () => import("@/components/DiagramLegend")
+  },
+  data() {
+    return {
+      containerId: "waterlevel-diagram-container",
+      svg: null,
+      diagram: null,
+      navigation: null,
+      dimensions: null,
+      extent: null,
+      scale: null,
+      axes: null,
+      form: {
+        template: null
+      },
+      templates: [],
+      defaultTemplate: {
+        name: "Default",
+        properties: {
+          paperSize: "a4"
+        },
+        elements: [
+          {
+            type: "diagram",
+            position: "topleft",
+            offset: { x: 15, y: 50 },
+            width: 290,
+            height: 100
+          },
+          {
+            type: "diagramlegend",
+            position: "topleft",
+            offset: { x: 30, y: 150 },
+            color: "black"
+          },
+          {
+            type: "diagramtitle",
+            position: "topleft",
+            offset: { x: 50, y: 26 },
+            fontsize: 22,
+            color: "steelblue"
+          },
+          {
+            type: "text",
+            position: "topleft",
+            offset: { x: 3, y: 3 },
+            fontsize: 8,
+            width: 90,
+            color: "gray",
+            text: this.$gettext("Generated by") + " " + "{user}, {date}"
+          }
+        ]
+      },
+      pdf: {
+        doc: null,
+        width: 420,
+        height: 297
+      },
+      templateData: null
+    };
+  },
   computed: {
+    ...mapState("application", ["paneSetup"]),
     ...mapState("gauges", [
+      "dateFrom",
       "waterlevels",
-      "dateFrom",
-      "dateTo",
+      "waterlevelsCSV",
       "nashSutcliffe"
     ]),
-    ...mapGetters("gauges", ["selectedGauge"])
+    ...mapGetters("gauges", ["selectedGauge"]),
+    title() {
+      return `${this.selectedGauge.properties.objname}: ${this.$gettext(
+        "Waterlevel"
+      )} (${this.dateFrom.toLocaleDateString()} - ${this.dateTo.toLocaleDateString()})`;
+    },
+    dateFrom: {
+      get() {
+        return this.$store.state.gauges.dateFrom;
+      }
+    },
+    dateTo: {
+      get() {
+        return this.$store.state.gauges.dateTo;
+      }
+    },
+    csvLink() {
+      return (
+        "data:text/csv;charset=utf-8," + encodeURIComponent(this.waterlevelsCSV)
+      );
+    },
+    csvFileName() {
+      return `${this.$gettext("waterlevels")}-${
+        this.selectedGauge.properties.objname
+      }-${this.dateFrom.toISOString().split("T")[0]}-${
+        this.dateTo.toISOString().split("T")[0]
+      }.csv`;
+    }
   },
   watch: {
+    paneSetup() {
+      this.$nextTick(() => this.drawDiagram());
+    },
     waterlevels() {
       this.drawDiagram();
     }
   },
   methods: {
+    close() {
+      this.$store.commit(
+        "application/paneSetup",
+        this.paneSetup === "GAUGE_WATERLEVEL_HYDROLOGICALCONDITIONS"
+          ? "GAUGE_HYDROLOGICALCONDITIONS"
+          : "DEFAULT"
+      );
+    },
+    downloadSVG() {
+      let svg = document.getElementById(this.containerId).firstElementChild;
+      let svgXML = new XMLSerializer().serializeToString(svg);
+      let blog = new Blob([svgXML], { type: "image/svg+xml;charset=utf-8" });
+      let filename =
+        this.selectedGauge.properties.objname + "-waterlevel-diagram.svg";
+      saveAs(blog, filename);
+    },
+    downloadPDF() {
+      this.pdf.doc = new jsPDF(
+        "l",
+        "mm",
+        this.templateData.properties.paperSize
+      );
+      // pdf width and height in millimeter (landscape)
+      this.pdf.width =
+        this.templateData.properties.paperSize === "a3" ? 420 : 297;
+      this.pdf.height =
+        this.templateData.properties.paperSize === "a3" ? 297 : 210;
+      // check the template elements
+      if (this.templateData) {
+        let defaultFontSize = 11,
+          defaultColor = "black",
+          defaultWidth = 70,
+          defaultTextColor = "black",
+          defaultBorderColor = "white",
+          defaultBgColor = "white",
+          defaultRounding = 2,
+          defaultPadding = 2,
+          defaultOffset = { x: 0, y: 0 };
+        this.templateData.elements.forEach(e => {
+          switch (e.type) {
+            case "diagram": {
+              this.addDiagram(
+                e.position,
+                e.offset || defaultOffset,
+                e.width,
+                e.height
+              );
+              break;
+            }
+            case "diagramlegend": {
+              this.addDiagramLegend(
+                e.position,
+                e.offset || defaultOffset,
+                e.color || defaultColor
+              );
+              break;
+            }
+            case "diagramtitle": {
+              let gaugeInfo =
+                this.selectedGauge.properties.objname +
+                " (" +
+                this.selectedGauge.id
+                  .split(".")[1]
+                  .replace(/[()]/g, "")
+                  .split(",")[3] +
+                "):" +
+                " Waterlevel " +
+                this.dateFrom.toLocaleDateString() +
+                " - " +
+                this.dateTo.toLocaleDateString();
+              this.addDiagramTitle(
+                e.position,
+                e.offset || defaultOffset,
+                e.fontsize || defaultFontSize,
+                e.color || defaultColor,
+                gaugeInfo
+              );
+              break;
+            }
+            case "text": {
+              this.addText(
+                e.position,
+                e.offset || defaultOffset,
+                e.width || defaultWidth,
+                e.fontsize || defaultFontSize,
+                e.color || defaultTextColor,
+                e.text || ""
+              );
+              break;
+            }
+            case "image": {
+              this.addImage(
+                e.url,
+                e.format || "",
+                e.position,
+                e.offset || defaultOffset,
+                e.width || 90,
+                e.height || 60
+              );
+              break;
+            }
+            case "box": {
+              this.addBox(
+                e.position,
+                e.offset || defaultOffset,
+                e.width || 90,
+                e.height || 60,
+                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
+                e.color || defaultBgColor,
+                e.brcolor || defaultBorderColor
+              );
+              break;
+            }
+            case "textbox": {
+              this.addTextBox(
+                e.position,
+                e.offset || defaultOffset,
+                e.width,
+                e.height,
+                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
+                e.padding || defaultPadding,
+                e.fontsize || defaultFontSize,
+                e.color || defaultTextColor,
+                e.background || defaultBgColor,
+                e.text || "",
+                e.brcolor || defaultBorderColor
+              );
+              break;
+            }
+          }
+        });
+      }
+      this.pdf.doc.save(
+        this.selectedGauge.properties.objname + " Waterlevel-Diagram.pdf"
+      );
+    },
+    applyChange() {
+      if (this.form.template.hasOwnProperty("properties")) {
+        this.templateData = this.defaultTemplate;
+        return;
+      }
+      if (this.form.template) {
+        HTTP.get("/templates/diagram/" + this.form.template.name, {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token"),
+            "Content-type": "text/xml; charset=UTF-8"
+          }
+        })
+          .then(response => {
+            this.templateData = response.data.template_data;
+          })
+          .catch(e => {
+            const { status, data } = e.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          });
+      }
+    },
+    addDiagram(position, offset, width, height) {
+      let x = offset.x,
+        y = offset.y;
+      // check if there are tow diagrams on screen
+      if (
+        ["GAUGE_WATERLEVEL_HYDROLOGICALCONDITIONS"].indexOf(this.paneSetup) !==
+        -1
+      ) {
+        this.containerId = "pdfContainer";
+        // set width and height
+        document.querySelector("#pdfContainer").style.width =
+          document.querySelector("#waterlevel-diagram-container").clientWidth *
+            2 +
+          document.querySelector("#diagramlegendId").clientWidth +
+          "px";
+        document.querySelector("#pdfContainer").style.height =
+          document.querySelector("#waterlevel-diagram-container").clientHeight +
+          "px";
+        this.drawDiagram();
+      }
+      var svg = document.getElementById(this.containerId).innerHTML;
+      if (svg) {
+        svg = svg.replace(/\r?\n|\r/g, "").trim();
+      }
+      this.containerId = "waterlevel-diagram-container";
+      var canvas = document.createElement("canvas");
+      canvas.width = window.innerWidth;
+      canvas.height = window.innerHeight / 2;
+      canvg(canvas, svg, {
+        ignoreMouse: true,
+        ignoreAnimation: true,
+        ignoreDimensions: true
+      });
+      var imgData = canvas.toDataURL("image/png");
+      // use default width,height if they are missing in the template definition
+      if (!width) {
+        width = this.templateData.properties.paperSize === "a3" ? 380 : 290;
+      }
+      if (!height) {
+        height = this.templateData.properties.paperSize === "a3" ? 130 : 100;
+      }
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - height;
+      }
+      this.pdf.doc.addImage(imgData, "PNG", x, y, width, height);
+    },
+    // Diagram legend
+    addDiagramLegend(position, offset, color) {
+      let x = offset.x;
+      let y = offset.y;
+      this.pdf.doc.setFontSize(10);
+      let width =
+        (this.pdf.doc.getStringUnitWidth("Navigable Range") * 10) /
+          (72 / 25.6) +
+        5;
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - this.getTextHeight(4);
+      }
+      this.pdf.doc.setTextColor(color);
+      this.pdf.doc.setDrawColor("white");
+      this.pdf.doc.setFillColor("steelblue");
+      this.pdf.doc.circle(x, y, 2, "FD");
+      this.pdf.doc.text(x + 3, y + 1, "Waterlevel");
+      this.pdf.doc.setFillColor("#dae6f0");
+      this.pdf.doc.circle(x, y + 5, 2, "FD");
+      this.pdf.doc.setFillColor("#e5ffe5");
+      this.pdf.doc.circle(x, y + 10, 2, "FD");
+      this.pdf.doc.text(x + 3, y + 11, "Navigable Range");
+      this.pdf.doc.setDrawColor("#90b4d2");
+      this.pdf.doc.setFillColor("#90b4d2");
+      this.pdf.doc.circle(x, y + 5, 0.6, "FD");
+      this.pdf.doc.text(x + 3, y + 6, "Prediction");
+    },
     drawDiagram() {
-      // TODO: Optimize code. I'm pretty sure all of this can be done in a much
-      // more elegant way and with less lines of code.
-
-      // remove old diagram
-      d3.select(".diagram-container svg").remove();
-
+      // remove old diagram and exit if necessary data is missing
+      d3.select("#" + this.containerId + " svg").remove();
       if (!this.selectedGauge || !this.waterlevels.length) return;
 
-      // get HDC/LDC/MW of the gauge
-      let refWaterLevels = JSON.parse(
+      // PREPARE HELPERS
+
+      // HDC/LDC/MW for the selected gauge
+      const refWaterLevels = JSON.parse(
         this.selectedGauge.properties.reference_water_levels
       );
 
-      // CREATE SVG AND SET DIMENSIONS/MARGINS
+      // dimensions (widths, heights, margins)
+      this.dimensions = this.getDimensions();
+
+      // get min/max values for date and waterlevel axis
+      this.extent = this.getExtent(refWaterLevels);
+
+      // scaling helpers
+      this.scale = this.getScale();
 
-      let svgWidth = document.querySelector(".diagram-container").clientWidth;
-      let svgHeight = document.querySelector(".diagram-container").clientHeight;
-      let svg = d3
-        .select(".diagram-container")
+      // creating the axes based on the scales
+      this.axes = {
+        x: d3
+          .axisTop(this.scale.x)
+          .tickSizeInner(this.dimensions.mainHeight)
+          .tickSizeOuter(0),
+        y: d3
+          .axisRight(this.scale.y)
+          .tickSizeInner(this.dimensions.width)
+          .tickSizeOuter(0),
+        x2: d3.axisBottom(this.scale.x2)
+      };
+
+      // DRAW DIAGRAM/NAVIGATION AREAS
+
+      // create svg
+      this.svg = d3
+        .select("#" + this.containerId)
         .append("svg")
         .attr("width", "100%")
         .attr("height", "100%");
-      let mainMargin = { top: 20, right: 20, bottom: 110, left: 80 },
-        navMargin = {
-          top: svgHeight - mainMargin.top - 65,
-          right: 20,
-          bottom: 30,
-          left: 80
-        },
-        width = +svgWidth - mainMargin.left - mainMargin.right,
-        mainHeight = +svgHeight - mainMargin.top - mainMargin.bottom,
-        navHeight = +svgHeight - navMargin.top - navMargin.bottom;
+
+      // create container for main diagram
+      this.diagram = this.svg
+        .append("g")
+        .attr("class", "main")
+        .attr(
+          "transform",
+          `translate(${this.dimensions.mainMargin.left}, ${
+            this.dimensions.mainMargin.top
+          })`
+        );
+
+      // create container for navigation diagram
+      this.navigation = this.svg
+        .append("g")
+        .attr("class", "nav")
+        .attr(
+          "transform",
+          `translate(${this.dimensions.navMargin.left}, ${
+            this.dimensions.navMargin.top
+          })`
+        );
+
+      // define visible area, everything outside this area will be hidden
+      this.svg
+        .append("defs")
+        .append("clipPath")
+        .attr("id", "waterlevel-clip")
+        .append("rect")
+        .attr("width", this.dimensions.width)
+        .attr("height", this.dimensions.mainHeight);
+
+      // DRAW DIAGRAM PARTS
+
+      // Each drawSomething function (with the exception of drawRefLines)
+      // returns a fuction to update the respective chart/area/etc. These
+      // updater functions are used by the zoom feature to rescale all elements.
+      const updaters = [];
+
+      // draw (order matters)
+      updaters.push(this.drawAxes());
+      updaters.push(this.drawWaterlevelChart());
+      updaters.push(this.drawPredictionAreas());
+      updaters.push(this.drawNowLines());
 
-      // PREPARING AXES/SCALING
+      // static, don't need updater
+      this.drawNavigationChart();
+      this.drawRefLines(refWaterLevels);
+
+      updaters.push(this.drawNashSutcliffe(72));
+      updaters.push(this.drawNashSutcliffe(48));
+      updaters.push(this.drawNashSutcliffe(24));
+
+      // INTERACTIONS
+
+      // create rectanlge on the main chart area to capture mouse events
+      const eventRect = this.svg
+        .append("rect")
+        .attr("id", "zoom-waterlevels")
+        .attr("class", "zoom")
+        .attr("width", this.dimensions.width)
+        .attr("height", this.dimensions.mainHeight)
+        .attr(
+          "transform",
+          `translate(${this.dimensions.mainMargin.left}, ${
+            this.dimensions.mainMargin.top
+          })`
+        );
+
+      this.createZoom(updaters, eventRect);
+      this.createTooltips(eventRect);
+      this.setInlineStyles();
+    },
+    //set the styles of the diagrams to include them in the pdf
+    setInlineStyles() {
+      this.svg
+        .selectAll(".line")
+        .attr("clip-path", "url(#waterlevel-clip)")
+        .selectAll("path")
+        .attr("stroke", "steelblue")
+        .attr("stroke-width", 2)
+        .attr("fill", "none");
+      this.svg
+        .selectAll(".line")
+        .selectAll("path.d3-line-chunked-chunk-gap")
+        .attr("stroke-opacity", 0);
+      this.svg
+        .selectAll(".line")
+        .selectAll("circle")
+        .attr("fill", "steelblue")
+        .attr("stroke-width", 0);
+      this.svg
+        .selectAll(".line")
+        .selectAll("circle.d3-line-chunked-chunk-predicted-point")
+        .attr("fill-opacity", 0.6);
 
-      // scaling helpers to convert real values to pixels
-      // based on the diagrams dimensions
-      let x = d3.scaleTime().range([0, width]),
-        x2 = d3.scaleTime().range([0, width]),
-        y = d3.scaleLinear().range([mainHeight, 0]),
-        y2 = d3.scaleLinear().range([navHeight, 0]);
-      // find min/max values for the waterlevel axis
-      // including HDC (+ 1/8 HDC-LDC) and LDC (- 1/4 HDC-LDC)
-      let WaterlevelMinMax = d3.extent(
-        [
-          ...this.waterlevels,
-          {
-            waterlevel:
-              refWaterLevels.HDC + (refWaterLevels.HDC - refWaterLevels.LDC) / 8
-          },
-          {
-            waterlevel: Math.max(
-              refWaterLevels.LDC -
-                (refWaterLevels.HDC - refWaterLevels.LDC) / 4,
-              0
-            )
-          }
+      this.svg
+        .selectAll(".hdc-line, .mw-line, .ldc-line, .rn-line")
+        .attr("stroke-width", 1)
+        .attr("fill", "none")
+        .attr("clip-path", "url(#waterlevel-clip)");
+      this.svg.selectAll(".hdc-line").attr("stroke", "red");
+      this.svg.selectAll(".ldc-line").attr("stroke", "green");
+      this.svg.selectAll(".mw-line").attr("stroke", "grey");
+      this.svg.selectAll(".rn-line").attr("stroke", "grey");
+      this.svg
+        .selectAll(".ref-waterlevel-label")
+        .style("font-size", "10px")
+        .attr("fill", "black");
+      this.svg
+        .selectAll(".ref-waterlevel-label-background")
+        .attr("fill", "rgb(255, 255, 255)")
+        .attr("fill-opacity", 0.6);
+      this.svg
+        .selectAll(".hdc-ldc-area")
+        .attr("fill", "rgb(0, 255, 0)")
+        .attr("fill-opacity", 0.1);
+      this.svg
+        .selectAll(".now-line")
+        .attr("stroke", "#999")
+        .attr("stroke-width", 1)
+        .attr("stroke-dasharray", "5, 5")
+        .attr("clip-path", "url(#waterlevel-clip)");
+      this.svg
+        .selectAll(".now-line-label")
+        .attr("font-size", "11px")
+        .attr("fill", "#999");
+      this.svg
+        .selectAll(".prediction-area")
+        .attr("fill", "steelblue")
+        .attr("fill-opacity", 0.2)
+        .attr("clip-path", "url(#waterlevel-clip)");
+      this.svg
+        .selectAll("path.nash-sutcliffe")
+        .attr("fill", "none")
+        .attr("stroke", "darkgrey")
+        .attr("stroke-width", 1)
+        .attr("clip-path", "url(#waterlevel-clip)");
+      this.svg
+        .selectAll("path.nash-sutcliffe.ns72")
+        .attr("fill", "rgb(255, 255, 255)")
+        .attr("fill-opacity", 0.5);
+      this.svg
+        .selectAll("text.nash-sutcliffe")
+        .style("font-size", "10px")
+        .attr("clip-path", "url(#waterlevel-clip)")
+        .selectAll("tspan:last-child, tspan:first-child")
+        .attr("fill", "#555");
+      this.svg
+        .selectAll(".tick line")
+        .attr("stroke-dasharray", 5)
+        .attr("stroke", "#ccc");
+      this.svg.selectAll(".tick text").attr("fill", "black");
+      this.svg.selectAll(".domain").attr("stroke", "black");
+      this.svg
+        .selectAll(".zoom")
+        .attr("cursor", "move")
+        .attr("fill", "none")
+        .attr("pointer-events", "all");
+      this.svg
+        .selectAll(".brush .selection")
+        .attr("stroke", "none")
+        .attr("fill-opacity", 0.2);
+      this.svg
+        .selectAll(".brush .handle")
+        .attr("stroke", "rgba(23, 162, 184, 0.5)")
+        .attr("fill", "rgba(23, 162, 184, 0.5)");
+      this.svg
+        .selectAll(".chart-dots")
+        .attr("clip-path", "url(#waterlevel-clip)");
+      this.svg
+        .selectAll(".chart-dots .chart-dot")
+        .attr("fill", "steelblue")
+        .attr("stroke", "steelblue")
+        .attr("stroke-opacity", 0)
+        .style("pointer-events", "none")
+        .transition()
+        .attr("fill-opacity", "0.1s");
+      this.svg
+        .selectAll(".chart-tooltip")
+        .attr("fill-opacity", 0)
+        .transition()
+        .attr("fill-opacity", "0.3s");
+      this.svg
+        .selectAll(".chart-tooltip rect")
+        .attr("fill", "#fff")
+        .attr("stroke", "#ccc");
+      this.svg
+        .selectAll(".chart-tooltip text")
+        .attr("fill", "666")
+        .style("font-size", "0.8em");
+    },
+    getDimensions() {
+      // dimensions and margins
+      const svgWidth = document.querySelector("#" + this.containerId)
+        .clientWidth;
+      const svgHeight = document.querySelector("#" + this.containerId)
+        .clientHeight;
+      const mainMargin = { top: 20, right: 20, bottom: 110, left: 80 };
+      const navMargin = {
+        top: svgHeight - mainMargin.top - 65,
+        right: 20,
+        bottom: 30,
+        left: 80
+      };
+      const width = +svgWidth - mainMargin.left - mainMargin.right;
+      const mainHeight = +svgHeight - mainMargin.top - mainMargin.bottom;
+      const navHeight = +svgHeight - navMargin.top - navMargin.bottom;
+
+      return { width, mainHeight, navHeight, mainMargin, navMargin };
+    },
+    getExtent(refWaterLevels) {
+      return {
+        // set min/max values for the date axis
+        date: [
+          this.waterlevels[0].date,
+          endOfDay(this.waterlevels[this.waterlevels.length - 1].date)
         ],
-        d => d.waterlevel
-      );
+        // set min/max values for the waterlevel axis
+        // including HDC (+ 1/8 HDC-LDC) and LDC (- 1/4 HDC-LDC)
+        waterlevel: d3.extent(
+          [
+            ...this.waterlevels,
+            {
+              waterlevel:
+                refWaterLevels.HDC +
+                (refWaterLevels.HDC - refWaterLevels.LDC) / 8
+            },
+            {
+              waterlevel: Math.max(
+                refWaterLevels.LDC -
+                  (refWaterLevels.HDC - refWaterLevels.LDC) / 4,
+                0
+              )
+            }
+          ],
+          d => d.waterlevel
+        )
+      };
+    },
+    getScale() {
+      // scaling helpers to convert real world values into pixels
+      const x = d3.scaleTime().range([0, this.dimensions.width]);
+      const y = d3.scaleLinear().range([this.dimensions.mainHeight, 0]);
+      const x2 = d3.scaleTime().range([0, this.dimensions.width]);
+      const y2 = d3.scaleLinear().range([this.dimensions.navHeight, 0]);
+
       // setting the min and max values for the diagram axes
-      let dateTo = new Date(this.dateTo.getTime() + 86400);
-      x.domain(d3.extent([this.dateFrom, dateTo]));
-      y.domain(WaterlevelMinMax);
+      x.domain(d3.extent(this.extent.date));
+      y.domain(this.extent.waterlevel);
       x2.domain(x.domain());
       y2.domain(y.domain());
-      // creating the axes based on these scales
-      let xAxis = d3
-        .axisTop(x)
-        .tickSizeInner(mainHeight)
-        .tickSizeOuter(0);
-      let xAxis2 = d3.axisBottom(x2);
-      let yAxis = d3
-        .axisRight(y)
-        .tickSizeInner(width)
-        .tickSizeOuter(0);
 
-      // PREPARING CHART FUNCTIONS
-
-      // points are "next to each other" when they are exactly 15 minutes apart
-      const isNext = (prev, current) =>
-        current.date - prev.date === 15 * 60 * 1000;
-
-      const preditionStyle = {
-        predicted: {
-          pointStyles: {
-            fill: "steelblue",
-            "fill-opacity": 0.6
-          }
-        }
-      };
-
-      // waterlevel line in big chart
-      // d3-line-chunked plugin: https://github.com/pbeshai/d3-line-chunked
-      var mainLineChart = d3
-        .lineChunked()
-        .x(d => x(d.date))
-        .y(d => y(d.waterlevel))
-        .curve(d3.curveLinear)
-        .isNext(isNext)
-        .pointAttrs({ r: 2.2 })
-        .chunk(d => (d.predicted ? "predicted" : "line"))
-        .chunkDefinitions(preditionStyle);
-      // waterlevel line in small chart
-      let navLineChart = d3
-        .lineChunked()
-        .x(d => x2(d.date))
-        .y(d => y2(d.waterlevel))
-        .curve(d3.curveMonotoneX)
-        .isNext(isNext)
-        .pointAttrs({ r: 1.7 })
-        .chunk(d => (d.predicted ? "predicted" : "line"))
-        .chunkDefinitions(preditionStyle);
-      // hdc/ldc/mw
-      let refWaterlevelLine = d3
-        .line()
-        .x(d => x(d.x))
-        .y(d => y(d.y));
-      // now
-      let nowLine = d3
-        .line()
-        .x(d => x(d.x))
-        .y(d => y(d.y));
-      let nowLineNav = d3
-        .line()
-        .x(d => x2(d.x))
-        .y(d => y2(d.y));
-      let nowLineLabel = selection => {
-        selection.attr(
-          "transform",
-          `translate(${x(new Date())}, ${y(WaterlevelMinMax[1] - 16)})`
-        );
-      };
-      // prediction area
-      let predictionArea = d3
-        .area()
-        .defined(d => d.predicted && d.min && d.max)
-        .x(d => x(d.date))
-        .y0(d => y(d.min))
-        .y1(d => y(d.max));
-      let predictionAreaNav = d3
-        .area()
-        .defined(d => d.predicted && d.min && d.max)
-        .x(d => x2(d.date))
-        .y0(d => y2(d.min))
-        .y1(d => y2(d.max));
-
-      // DRAWING MAINCHART
-
-      // define visible chart area
-      // everything outside this area will be hidden (clipped)
-      svg
-        .append("defs")
-        .append("clipPath")
-        .attr("id", "clip")
-        .append("rect")
-        .attr("width", width)
-        .attr("height", mainHeight);
-
-      let mainChart = svg
-        .append("g")
-        .attr("class", "main")
-        .attr("transform", `translate(${mainMargin.left}, ${mainMargin.top})`);
-
-      // axes
-      mainChart
+      return { x, y, x2, y2 };
+    },
+    drawAxes() {
+      this.diagram
         .append("g")
         .attr("class", "axis--x")
-        .attr("transform", `translate(0, ${mainHeight})`)
-        .call(xAxis)
+        .attr("transform", `translate(0, ${this.dimensions.mainHeight})`)
+        .call(this.axes.x)
         .selectAll(".tick text")
         .attr("y", 15);
-      mainChart // label
+      this.diagram // label
         .append("text")
         .text(this.$gettext("Waterlevel [cm]"))
         .attr("text-anchor", "middle")
-        .attr("transform", `translate(-45, ${mainHeight / 2}) rotate(-90)`);
-      mainChart
+        .attr(
+          "transform",
+          `translate(-45, ${this.dimensions.mainHeight / 2}) rotate(-90)`
+        );
+      this.diagram
         .append("g")
-        .call(yAxis)
+        .call(this.axes.y)
         .selectAll(".tick text")
         .attr("x", -25);
 
-      // reference waterlevels
-      // filling area between HDC and LDC
-      mainChart
-        .append("rect")
-        .attr("class", "hdc-ldc-area")
-        .attr("x", 0)
-        .attr("y", y(refWaterLevels.HDC))
-        .attr("width", width)
-        .attr("height", y(refWaterLevels.LDC) - y(refWaterLevels.HDC));
+      this.navigation
+        .append("g")
+        .attr("class", "axis axis--x")
+        .attr("transform", `translate(0, ${this.dimensions.navHeight})`)
+        .call(this.axes.x2);
+
+      return () => {
+        this.diagram
+          .select(".axis--x")
+          .call(this.axes.x)
+          .selectAll(".tick text")
+          .attr("y", 15);
+      };
+    },
+    drawWaterlevelChart() {
+      const waterlevelChartDrawer = () => {
+        let domainLeft = new Date(this.scale.x.domain()[0].getTime());
+        let domainRight = new Date(this.scale.x.domain()[1].getTime());
+        domainLeft.setDate(domainLeft.getDate() - 1);
+        domainRight.setDate(domainRight.getDate() + 1);
+
+        return (
+          d3
+            .lineChunked()
+            // render only data points that are visible in the current scale
+            .defined(d => d.date > domainLeft && d.date < domainRight)
+            .x(d => this.scale.x(d.date))
+            .y(d => this.scale.y(d.waterlevel))
+            .curve(d3.curveLinear)
+            .isNext(this.isNext())
+            .pointAttrs({ r: 1.7 })
+            .chunk(d => (d.predicted ? "predicted" : "line"))
+            .chunkDefinitions({ predicted: {} })
+        );
+      };
 
-      // HDC
-      mainChart
-        .append("path")
-        .datum([
-          { x: 0, y: refWaterLevels.HDC },
-          { x: dateTo, y: refWaterLevels.HDC }
-        ])
-        .attr("class", "hdc-line")
-        .attr("d", refWaterlevelLine);
-      mainChart // label
-        .append("text")
-        .text("HDC")
-        .attr("class", "ref-waterlevel-label")
-        .attr("x", x(dateTo) - 20)
-        .attr("y", y(refWaterLevels.HDC) - 3);
-      // LDC
-      mainChart
+      this.diagram
+        .append("g")
+        .attr("class", "line")
+        .datum(this.waterlevels)
+        .call(waterlevelChartDrawer());
+
+      return () => {
+        this.diagram.select(".line").call(waterlevelChartDrawer());
+      };
+    },
+    drawNavigationChart() {
+      this.navigation
+        .append("g")
+        .attr("class", "line")
+        .datum(this.waterlevels)
+        .call(
+          d3
+            .lineChunked()
+            .x(d => this.scale.x2(d.date))
+            .y(d => this.scale.y2(d.waterlevel))
+            .curve(d3.curveLinear)
+            .isNext(this.isNext())
+            .pointAttrs({ r: 1.7 })
+            .chunk(d => (d.predicted ? "predicted" : "line"))
+            .chunkDefinitions({ predicted: {} })
+        );
+    },
+    drawNowLines() {
+      const nowLine = d3
+        .line()
+        .x(d => this.scale.x(d.x))
+        .y(d => this.scale.y(d.y));
+
+      const nowLabel = selection => {
+        selection.attr(
+          "transform",
+          `translate(${this.scale.x(new Date())}, ${this.scale.y(
+            this.extent.waterlevel[1] - 16
+          )})`
+        );
+      };
+
+      // draw in main
+      this.diagram
         .append("path")
         .datum([
-          { x: 0, y: refWaterLevels.LDC },
-          { x: dateTo, y: refWaterLevels.LDC }
-        ])
-        .attr("class", "ldc-line")
-        .attr("d", refWaterlevelLine);
-      mainChart // label
-        .append("text")
-        .text("LDC")
-        .attr("class", "ref-waterlevel-label")
-        .attr("x", x(dateTo) - 20)
-        .attr("y", y(refWaterLevels.LDC) - 3);
-      // MW
-      mainChart
-        .append("path")
-        .datum([
-          { x: 0, y: refWaterLevels.MW },
-          { x: dateTo, y: refWaterLevels.MW }
-        ])
-        .attr("class", "mw-line")
-        .attr("d", refWaterlevelLine);
-      mainChart // label
-        .append("text")
-        .text("MW")
-        .attr("class", "ref-waterlevel-label")
-        .attr("x", x(dateTo) - 20)
-        .attr("y", y(refWaterLevels.MW) - 3);
-
-      // now
-      mainChart
-        .append("path")
-        .datum([
-          { x: new Date(), y: WaterlevelMinMax[0] },
-          { x: new Date(), y: WaterlevelMinMax[1] - 20 }
+          { x: new Date(), y: this.extent.waterlevel[0] },
+          { x: new Date(), y: this.extent.waterlevel[1] - 20 }
         ])
         .attr("class", "now-line")
         .attr("d", nowLine);
-      mainChart // label
+      this.diagram // label
         .append("text")
         .text(this.$gettext("Now"))
         .attr("class", "now-line-label")
         .attr("text-anchor", "middle")
-        .call(nowLineLabel);
+        .call(nowLabel);
+
+      // draw in nav
+      this.navigation
+        .append("path")
+        .datum([
+          { x: new Date(), y: this.extent.waterlevel[0] },
+          { x: new Date(), y: this.extent.waterlevel[1] - 20 }
+        ])
+        .attr("class", "now-line")
+        .attr(
+          "d",
+          d3
+            .line()
+            .x(d => this.scale.x2(d.x))
+            .y(d => this.scale.y2(d.y))
+        );
 
-      // prediction area
-      mainChart
+      return () => {
+        this.diagram.select(".now-line").attr("d", nowLine);
+        this.diagram.select(".now-line-label").call(nowLabel);
+      };
+    },
+    drawPredictionAreas() {
+      const predictionArea = isNav =>
+        d3
+          .area()
+          .defined(d => d.predicted && d.min && d.max)
+          .x(d => this.scale[isNav ? "x2" : "x"](d.date))
+          .y0(d => this.scale[isNav ? "y2" : "y"](d.min))
+          .y1(d => this.scale[isNav ? "y2" : "y"](d.max));
+
+      this.diagram
+        .append("path")
+        .datum(this.waterlevels)
+        .attr("class", "prediction-area")
+        .attr("d", predictionArea());
+
+      this.navigation
         .append("path")
         .datum(this.waterlevels)
         .attr("class", "prediction-area")
-        .attr("d", predictionArea);
-
-      // waterlevel chart
-      mainChart
-        .append("g")
-        .attr("class", "line")
-        .datum(this.waterlevels)
-        .call(mainLineChart);
-
-      // DRAWING NAVCHART
+        .attr("d", predictionArea(true));
 
-      let navChart = svg
-        .append("g")
-        .attr("class", "nav")
-        .attr("transform", `translate(${navMargin.left}, ${navMargin.top})`);
+      return () => {
+        this.diagram.select(".prediction-area").attr("d", predictionArea());
+      };
+    },
+    drawRefLines(refWaterLevels) {
+      // filling area between HDC and LDC
+      this.diagram
+        .append("rect")
+        .attr("class", "hdc-ldc-area")
+        .attr("x", 0)
+        .attr("y", this.scale.y(refWaterLevels.HDC))
+        .attr("width", this.dimensions.width)
+        .attr(
+          "height",
+          this.scale.y(refWaterLevels.LDC) - this.scale.y(refWaterLevels.HDC)
+        );
 
-      // axis (nav chart only has x-axis)
-      navChart
-        .append("g")
-        .attr("class", "axis axis--x")
-        .attr("transform", `translate(0, ${navHeight})`)
-        .call(xAxis2);
+      const refWaterlevelLine = d3
+        .line()
+        .x(d => this.scale.x(d.x))
+        .y(d => this.scale.y(d.y));
 
-      // now
-      navChart
-        .append("path")
-        .datum([
-          { x: new Date(), y: WaterlevelMinMax[0] },
-          { x: new Date(), y: WaterlevelMinMax[1] - 20 }
-        ])
-        .attr("class", "now-line")
-        .attr("d", nowLineNav);
-
-      // prediction area
-      navChart
-        .append("path")
-        .datum(this.waterlevels)
-        .attr("class", "prediction-area")
-        .attr("d", predictionAreaNav);
-
-      // waterlevel chart
-      navChart
-        .append("g")
-        .attr("class", "line")
-        .datum(this.waterlevels)
-        .call(navLineChart);
-
-      // NASH SUTCLIFFE
-
-      let nashSut24 = this.nashSutcliffe.coeffs.find(c => c.hours === 24);
-      let nashSut48 = this.nashSutcliffe.coeffs.find(c => c.hours === 48);
-      let nashSut72 = this.nashSutcliffe.coeffs.find(c => c.hours === 72);
-
-      let nashSutDateNow = new Date(this.nashSutcliffe.when);
-      let nashSutDate24 = new Date(this.nashSutcliffe.when);
-      let nashSutDate48 = new Date(this.nashSutcliffe.when);
-      let nashSutDate72 = new Date(this.nashSutcliffe.when);
-      nashSutDate24.setDate(nashSutDate24.getDate() - 1);
-      nashSutDate48.setDate(nashSutDate48.getDate() - 2);
-      nashSutDate72.setDate(nashSutDate72.getDate() - 3);
+      for (let ref in refWaterLevels) {
+        if (refWaterLevels[ref]) {
+          this.diagram
+            .append("path")
+            .datum([
+              { x: 0, y: refWaterLevels[ref] },
+              { x: this.extent.date[1], y: refWaterLevels[ref] }
+            ])
+            .attr("class", ref.toLowerCase() + "-line")
+            .attr("d", refWaterlevelLine);
+          this.diagram // label
+            .append("rect")
+            .attr("class", "ref-waterlevel-label-background")
+            .attr("x", 1)
+            .attr("y", this.scale.y(refWaterLevels[ref]) - 13)
+            .attr("width", 55)
+            .attr("height", 12);
+          this.diagram
+            .append("text")
+            .text(`${ref} (${refWaterLevels[ref]})`)
+            .attr("class", "ref-waterlevel-label")
+            .attr("x", 5)
+            .attr("y", this.scale.y(refWaterLevels[ref]) - 3);
+        }
+      }
+    },
+    drawNashSutcliffe(hours) {
+      const coeff = this.nashSutcliffe.coeffs.find(c => c.hours === hours);
+      const dateNow = new Date(this.nashSutcliffe.when);
+      const dateStart = new Date(dateNow.getTime() - hours * 60 * 60 * 1000);
 
       const nashSutcliffeBox = hours => {
+        // show/hide boxes depending on scale of chart (hide if > 90 days)
+        this.diagram
+          .selectAll("path.nash-sutcliffe")
+          .attr(
+            "stroke-opacity",
+            this.scale.x.domain()[1] - this.scale.x.domain()[0] > 90 * 86400000
+              ? 0
+              : 1
+          );
+
         return d3
           .area()
-          .x(d => x(d))
-          .y0(() => mainHeight + 0.5)
-          .y1(() => mainHeight - 15 * (hours / 24));
+          .x(d => this.scale.x(d))
+          .y0(() => this.dimensions.mainHeight + 0.5)
+          .y1(() => this.dimensions.mainHeight - 15 * (hours / 24));
       };
 
       const nashSutcliffeLabel = (label, date, hours) => {
         let days = hours / 24;
         label
-          .attr("x", x(date) + 3)
-          .attr("y", mainHeight - (15 * days + 0.5) + 12);
+          .attr("x", Math.min(this.scale.x(date), this.dimensions.width) - 4)
+          .attr("y", this.dimensions.mainHeight - (15 * days + 0.5) + 12);
       };
 
-      // Show nash-sutcliffe only when x-axis extent is smaller than 35 days
-      // (3024000000 ms). Since it shows squares representing 1, 2 and 3 days
-      // it does not make sense to show them on a x-axis with hundres of days.
-      if (this.nashSutcliffe && x.domain()[1] - x.domain()[0] < 3024000000) {
-        if (nashSut24.samples) {
-          mainChart
-            .append("path")
-            .datum([nashSutDate24, nashSutDateNow])
-            .attr("class", "nash-sutcliffe ns24")
-            .attr("d", nashSutcliffeBox(24));
-          mainChart
-            .append("text")
-            .attr("class", "nash-sutcliffe ns24")
-            .call(nashSutcliffeLabel, nashSutDate24, 24)
-            .append("tspan")
-            .text(nashSut24.value.toFixed(2))
-            .select(function() {
-              return this.parentNode;
-            })
-            .append("tspan")
-            .text(` (${nashSut24.samples})`)
-            .attr("dy", -1);
-        }
-        if (nashSut48.samples) {
-          mainChart
-            .append("path")
-            .datum([nashSutDate48, nashSutDateNow])
-            .attr("class", "nash-sutcliffe ns48")
-            .attr("d", nashSutcliffeBox(48));
-          mainChart
-            .append("text")
-            .attr("class", "nash-sutcliffe ns48")
-            .call(nashSutcliffeLabel, nashSutDate48, 48)
-            .append("tspan")
-            .text(nashSut48.value.toFixed(2))
-            .select(function() {
-              return this.parentNode;
-            })
-            .append("tspan")
-            .text(` (${nashSut48.samples})`)
-            .attr("dy", -1);
-        }
-        if (nashSut72.samples) {
-          mainChart
-            .append("path")
-            .datum([nashSutDate72, nashSutDateNow])
-            .attr("class", "nash-sutcliffe ns72")
-            .attr("d", nashSutcliffeBox(72));
-          mainChart
-            .append("text")
-            .attr("class", "nash-sutcliffe ns72")
-            .call(nashSutcliffeLabel, nashSutDate72, 72)
-            .append("tspan")
-            .text(nashSut72.value.toFixed(2))
-            .select(function() {
-              return this.parentNode;
-            })
-            .append("tspan")
-            .text(` (${nashSut72.samples})`)
-            .attr("dy", -1);
-        }
+      if (coeff.samples) {
+        this.diagram
+          .append("path")
+          .datum([dateStart, dateNow])
+          .attr("class", "nash-sutcliffe ns" + hours)
+          .attr("d", nashSutcliffeBox(hours));
+        this.diagram
+          .append("text")
+          .attr("class", "nash-sutcliffe ns" + hours)
+          .attr("text-anchor", "end")
+          .call(nashSutcliffeLabel, dateNow, hours)
+          .append("tspan")
+          .text(hours + "h: ")
+          .select(function() {
+            return this.parentNode;
+          })
+          .append("tspan")
+          .text(coeff.value.toFixed(2))
+          .select(function() {
+            return this.parentNode;
+          })
+          .append("tspan")
+          .text(` (${coeff.samples})`);
       }
 
-      // INTERACTIVITY
-
-      // selecting time period in nav chart
-      let brush = d3
+      return () => {
+        this.diagram
+          .select("path.nash-sutcliffe.ns" + hours)
+          .attr("d", nashSutcliffeBox(hours));
+        this.diagram
+          .select("text.nash-sutcliffe.ns" + hours)
+          .call(nashSutcliffeLabel, dateNow, hours);
+      };
+    },
+    createZoom(updaters, eventRect) {
+      const brush = d3
         .brushX()
         .handleSize(4)
-        .extent([[0, 0], [width, navHeight]])
-        .on("brush end", () => {
-          if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom")
-            return; // ignore brush-by-zoom
-          let s = d3.event.selection || x2.range();
-          x.domain(s.map(x2.invert, x2));
-          mainChart.select(".line").call(mainLineChart);
-          mainChart.select(".now-line").attr("d", nowLine);
-          mainChart.select(".now-line-label").call(nowLineLabel);
-          mainChart.select(".prediction-area").attr("d", predictionArea);
-          mainChart
-            .select("path.nash-sutcliffe.ns24")
-            .attr("d", nashSutcliffeBox(24));
-          mainChart
-            .select("text.nash-sutcliffe.ns24")
-            .call(nashSutcliffeLabel, nashSutDate24, 24);
-          mainChart
-            .select("path.nash-sutcliffe.ns48")
-            .attr("d", nashSutcliffeBox(48));
-          mainChart
-            .select("text.nash-sutcliffe.ns48")
-            .call(nashSutcliffeLabel, nashSutDate48, 48);
-          mainChart
-            .select("path.nash-sutcliffe.ns72")
-            .attr("d", nashSutcliffeBox(72));
-          mainChart
-            .select("text.nash-sutcliffe.ns72")
-            .call(nashSutcliffeLabel, nashSutDate72, 72);
-          mainChart
-            .select(".axis--x")
-            .call(xAxis)
-            .selectAll(".tick text")
-            .attr("y", 15);
-          svg
-            .select(".zoom")
-            .call(
-              zoom.transform,
-              d3.zoomIdentity.scale(width / (s[1] - s[0])).translate(-s[0], 0)
-            );
-        });
+        .extent([[0, 0], [this.dimensions.width, this.dimensions.navHeight]]);
 
-      // zooming with mousewheel in main chart
-      let zoom = d3
+      const zoom = d3
         .zoom()
         .scaleExtent([1, Infinity])
-        .translateExtent([[0, 0], [width, mainHeight]])
-        .extent([[0, 0], [width, mainHeight]])
-        .on("zoom", () => {
-          if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush")
-            return; // ignore zoom-by-brush
-          let t = d3.event.transform;
-          x.domain(t.rescaleX(x2).domain());
-          mainChart.select(".line").call(mainLineChart);
-          mainChart.select(".now-line").attr("d", nowLine);
-          mainChart.select(".now-line-label").call(nowLineLabel);
-          mainChart.select(".prediction-area").attr("d", predictionArea);
-          mainChart
-            .select("path.nash-sutcliffe.ns24")
-            .attr("d", nashSutcliffeBox(24));
-          mainChart
-            .select("text.nash-sutcliffe.ns24")
-            .call(nashSutcliffeLabel, nashSutDate24, 24);
-          mainChart
-            .select("path.nash-sutcliffe.ns48")
-            .attr("d", nashSutcliffeBox(48));
-          mainChart
-            .select("text.nash-sutcliffe.ns48")
-            .call(nashSutcliffeLabel, nashSutDate48, 48);
-          mainChart
-            .select("path.nash-sutcliffe.ns72")
-            .attr("d", nashSutcliffeBox(72));
-          mainChart
-            .select("text.nash-sutcliffe.ns72")
-            .call(nashSutcliffeLabel, nashSutDate72, 72);
-          mainChart
-            .select(".axis--x")
-            .call(xAxis)
-            .selectAll(".tick text")
-            .attr("y", 15);
-          navChart
-            .select(".brush")
-            .call(brush.move, x.range().map(t.invertX, t));
-        })
-        .on("start", () => {
-          svg.select(".chart-dot").style("opacity", 0);
-          svg.select(".chart-tooltip").style("opacity", 0);
-        });
+        .translateExtent([
+          [0, 0],
+          [this.dimensions.width, this.dimensions.mainHeight]
+        ])
+        .extent([[0, 0], [this.dimensions.width, this.dimensions.mainHeight]]);
 
-      navChart
+      brush.on("brush end", () => {
+        if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom")
+          return; // ignore brush-by-zoom
+        let s = d3.event.selection || this.scale.x2.range();
+        this.scale.x.domain(s.map(this.scale.x2.invert, this.scale.x2));
+        updaters.forEach(u => u && u());
+        this.setInlineStyles();
+        this.svg
+          .select(".zoom")
+          .call(
+            zoom.transform,
+            d3.zoomIdentity
+              .scale(this.dimensions.width / (s[1] - s[0]))
+              .translate(-s[0], 0)
+          );
+      });
+
+      zoom.on("zoom", () => {
+        if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush")
+          return; // ignore zoom-by-brush
+        let t = d3.event.transform;
+        this.scale.x.domain(t.rescaleX(this.scale.x2).domain());
+        updaters.forEach(u => u && u());
+        this.setInlineStyles();
+        this.navigation
+          .select(".brush")
+          .call(brush.move, this.scale.x.range().map(t.invertX, t));
+      });
+      zoom.on("start", () => {
+        this.svg.select(".chart-dot").style("opacity", 0);
+        this.svg.select(".chart-tooltip").style("opacity", 0);
+      });
+
+      this.navigation
         .append("g")
         .attr("class", "brush")
         .call(brush)
-        .call(brush.move, x.range());
+        .call(brush.move, this.scale.x.range());
 
-      let zoomRect = svg
-        .append("rect")
-        .attr("class", "zoom")
-        .attr("width", width)
-        .attr("height", mainHeight)
-        .attr("transform", `translate(${mainMargin.left}, ${mainMargin.top})`)
-        .call(zoom);
-
-      // TOOLTIPS
-
-      let dots = mainChart.append("g").attr("class", "chart-dots");
-      dots
+      eventRect.call(zoom);
+    },
+    createTooltips(eventRect) {
+      // create clippable container for the dot
+      this.diagram
+        .append("g")
+        .attr("class", "chart-dots")
         .append("circle")
         .attr("class", "chart-dot")
         .attr("r", 4);
-      let tooltips = mainChart.append("g").attr("class", "chart-tooltip");
-      tooltips
+
+      // create container for the tooltip
+      const tooltip = this.diagram.append("g").attr("class", "chart-tooltip");
+      tooltip
         .append("rect")
-        .attr("x", -25)
-        .attr("y", -25)
-        .attr("rx", 4)
-        .attr("ry", 4)
-        .attr("width", 105)
-        .attr("height", 40);
-      let tooltipText = tooltips.append("text");
-      tooltipText
-        .append("tspan")
-        .attr("x", -15)
-        .attr("y", -8);
-      tooltipText
-        .append("tspan")
-        .attr("x", 8)
-        .attr("y", 8);
+        .attr("rx", "0.25em")
+        .attr("ry", "0.25em");
+
+      // create container for multiple text rows
+      const tooltipText = tooltip.append("text").attr("text-anchor", "middle");
 
-      let bisectDate = d3.bisector(d => d.date).left;
-      zoomRect
+      // padding inside the tooltip box and diagram padding to determine left
+      // and right offset from the diagram boundaries for the tooltip position.
+      const tooltipPadding = 10;
+      const diagramPadding = 5;
+
+      eventRect
         .on("mouseover", () => {
-          svg.select(".chart-dot").style("opacity", 1);
-          svg.select(".chart-tooltip").style("opacity", 1);
+          this.diagram.select(".chart-dot").style("opacity", 1);
+          this.diagram.select(".chart-tooltip").style("opacity", 1);
         })
         .on("mouseout", () => {
-          svg.select(".chart-dot").style("opacity", 0);
-          svg.select(".chart-tooltip").style("opacity", 0);
+          this.diagram.select(".chart-dot").style("opacity", 0);
+          this.diagram.select(".chart-tooltip").style("opacity", 0);
         })
         .on("mousemove", () => {
-          let x0 = x.invert(d3.mouse(document.querySelector(".zoom"))[0]),
-            i = bisectDate(this.waterlevels, x0, 1),
+          // find data point closest to mouse
+          const x0 = this.scale.x.invert(
+              d3.mouse(document.getElementById("zoom-waterlevels"))[0]
+            ),
+            i = d3.bisector(d => d.date).left(this.waterlevels, x0, 1),
             d0 = this.waterlevels[i - 1],
             d1 = this.waterlevels[i] || d0,
             d = x0 - d0.date > d1.date - x0 ? d1 : d0;
 
-          svg
+          const coords = {
+            x: this.scale.x(d.date),
+            y: this.scale.y(d.waterlevel)
+          };
+
+          // position the dot
+          this.diagram
             .select(".chart-dot")
             .style("opacity", 1)
-            .attr("transform", `translate(${x(d.date)}, ${y(d.waterlevel)})`);
-          svg
+            .attr("transform", `translate(${coords.x}, ${coords.y})`);
+
+          // remove current texts
+          tooltipText.selectAll("tspan").remove();
+
+          // write date
+          tooltipText
+            .append("tspan")
+            .attr("dominant-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text(
+              d.date.toLocaleString([], {
+                year: "2-digit",
+                month: "2-digit",
+                day: "2-digit",
+                hour: "2-digit",
+                minute: "2-digit"
+              })
+            );
+
+          if (d.predicted) {
+            tooltipText
+              .append("tspan")
+              .attr("x", 0)
+              .attr("y", 0)
+              .attr("dy", "1.4em")
+              .attr("dominant-baseline", "hanging")
+              .attr("text-anchor", "middle")
+              .text(d.max + " cm");
+            tooltipText
+              .append("tspan")
+              .attr("x", 0)
+              .attr("y", 0)
+              .attr("dy", "2.6em")
+              .attr("dominant-baseline", "hanging")
+              .attr("text-anchor", "middle")
+              .attr("class", "font-weight-bold")
+              .text(d.waterlevel + " cm");
+            tooltipText
+              .append("tspan")
+              .attr("x", 0)
+              .attr("y", 0)
+              .attr("dy", "3.8em")
+              .attr("dominant-baseline", "hanging")
+              .attr("text-anchor", "middle")
+              .text(d.min + " cm");
+          } else {
+            tooltipText
+              .append("tspan")
+              .attr("x", 0)
+              .attr("y", 0)
+              .attr("dy", "1.4em")
+              .attr("dominant-baseline", "hanging")
+              .attr("text-anchor", "middle")
+              .attr("class", "font-weight-bold")
+              .text(d.waterlevel + " cm");
+          }
+
+          // get text dimensions
+          const textBBox = tooltipText.node().getBBox();
+
+          this.diagram
+            .selectAll(".chart-tooltip text tspan")
+            .attr("x", textBBox.width / 2 + tooltipPadding)
+            .attr("y", tooltipPadding);
+
+          // position and scale tooltip box
+          const xMax =
+            this.dimensions.width -
+            (textBBox.width + diagramPadding + tooltipPadding * 2);
+          const tooltipX = Math.max(
+            diagramPadding,
+            Math.min(coords.x - (textBBox.width + tooltipPadding * 2) / 2, xMax)
+          );
+          let tooltipY = coords.y - (textBBox.height + tooltipPadding * 2) - 10;
+          if (coords.y < textBBox.height + tooltipPadding * 2) {
+            tooltipY = coords.y + 10;
+          }
+
+          this.diagram
             .select(".chart-tooltip")
             .style("opacity", 1)
-            .attr(
-              "transform",
-              `translate(${x(d.date) - 25}, ${y(d.waterlevel) - 25})`
-            );
-          svg.select(".chart-tooltip text tspan:first-child").text(
-            d.date.toLocaleString([], {
-              year: "2-digit",
-              month: "2-digit",
-              day: "2-digit",
-              hour: "2-digit",
-              minute: "2-digit"
-            })
-          );
-          svg
-            .select(".chart-tooltip text tspan:last-child")
-            .text(d.waterlevel + " cm");
+            .attr("transform", `translate(${tooltipX}, ${tooltipY})`)
+            .select("rect")
+            .attr("width", textBBox.width + tooltipPadding * 2)
+            .attr("height", textBBox.height + tooltipPadding * 2);
         });
+    },
+    isNext() {
+      // Check whether points in the chart can be considered "next to each other".
+      // For that they need to be exactly 15 minutes apart (for automatically
+      // imported gauge measurements). If the chart shows more than 15 days then
+      // 1 hour is also valid (for approved gauge measurements).
+      return (prev, current) => {
+        let difference = (current.date - prev.date) / 1000;
+        if (
+          (this.scale.x.domain()[1] - this.scale.x.domain()[0]) / 86400000 >
+          15
+        )
+          return [900, 3600].includes(difference);
+        return difference === 900;
+      };
     }
   },
   created() {
     window.addEventListener("resize", debounce(this.drawDiagram), 100);
   },
   mounted() {
-    this.drawDiagram();
+    // Nasty but necessary if we don't want to use the updated hook to re-draw
+    // the diagram because this would re-draw it also for irrelevant reasons.
+    // In this case we need to wait for the child component (DiagramLegend) to
+    // render. According to the docs (https://vuejs.org/v2/api/#mounted) this
+    // should be possible with $nextTick() but it doesn't work because it does
+    // not guarantee that the DOM is not only updated but also re-painted on the
+    // screen.
+    setTimeout(this.drawDiagram, 50);
+
+    this.templates[0] = this.defaultTemplate;
+    this.form.template = this.templates[0];
+    this.templateData = this.form.template;
+    HTTP.get("/templates/diagram", {
+      headers: {
+        "X-Gemma-Auth": localStorage.getItem("token"),
+        "Content-type": "text/xml; charset=UTF-8"
+      }
+    })
+      .then(response => {
+        if (response.data.length) {
+          this.templates = response.data;
+          this.form.template = this.templates[0];
+          this.templates[this.templates.length] = this.defaultTemplate;
+          this.applyChange();
+        }
+      })
+      .catch(e => {
+        const { status, data } = e.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      });
   },
-  updated() {
-    this.drawDiagram();
+  destroyed() {
+    window.removeEventListener("resize", debounce(this.drawDiagram));
   }
 };
 </script>
--- a/client/src/components/identify/Identify.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/identify/Identify.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -13,7 +13,7 @@
       />
       <div class="features">
         <div v-if="currentMeasurement">
-          <small class="d-block bg-dark text-light text-center px-2 py-1">
+          <small class="d-block bg-secondary text-light px-2 py-1">
             {{ $gettext("Measurement") }}
           </small>
           <small class="d-flex justify-content-between px-2">
@@ -28,7 +28,7 @@
           :key="feature.getId()"
         >
           <small
-            class="d-flex justify-content-between bg-dark text-light px-2 py-1"
+            class="d-flex justify-content-between bg-secondary text-light px-2 py-1"
           >
             {{ $gettext(featureLabel(feature)) }}
             <a
@@ -56,6 +56,18 @@
           <translate>No features identified.</translate>
         </div>
       </div>
+      <div
+        v-if="userManualUrl"
+        class="border-top text-left pl-2"
+        style="font-size: 90%;"
+      >
+        <translate>Download</translate>
+        <a
+          :href="userManualUrl ? userManualUrl : '#'"
+          :download="usermanualFilename"
+          ><translate> User Manual</translate></a
+        >
+      </div>
       <div class="versioninfo border-top box-body">
         <span v-translate="{ license: 'AGPL-3.0-or-later' }">
           This app uses <i>gemma</i>, which is Free Software under <br />
@@ -138,16 +150,19 @@
   name: "identify",
   computed: {
     ...mapGetters("application", ["versionStr"]),
-    ...mapState("application", ["showIdentify"]),
+    ...mapState("application", ["showIdentify", "userManualUrl"]),
     ...mapGetters("map", ["filteredIdentifiedFeatures"]),
     ...mapState("map", ["currentMeasurement"]),
     identifiedLabel() {
-      return this.$gettext("Identified");
+      return this.$gettext("Identified Features");
+    },
+    usermanualFilename() {
+      return this.$gettext("User Manual");
     }
   },
   methods: {
     zoomTo(feature) {
-      this.$store.commit("map/moveMap", {
+      this.$store.dispatch("map/moveMap", {
         coordinates: getCenter(
           feature
             .getGeometry()
@@ -164,7 +179,14 @@
     },
     featureId(feature) {
       // cut away everything from the last . to the end
-      return feature.getId().replace(/[.][^.]*$/, "");
+      let id = "";
+      if (feature.getId) {
+        id = feature.getId();
+      }
+      if (feature.id) {
+        id = feature.id;
+      }
+      return id.replace(/[.][^.]*$/, "");
     },
     featureLabel(feature) {
       if (formatter.hasOwnProperty(this.featureId(feature))) {
--- a/client/src/components/identify/formatter.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/identify/formatter.js	Mon Jun 03 10:19:18 2019 +0200
@@ -4,7 +4,11 @@
     if (p.key === "objname") p.key = "Name";
     if (p.key === "staging_done" || p.key === "fa_critical")
       p.val = p.val ? "yes" : "no";
-    if (p.key === "date_info" || p.key === "fa_date_info") {
+    if (
+      p.key === "date_info" ||
+      p.key === "fa_date_info" ||
+      p.key === "gm_measuredate"
+    ) {
       p.val = new Date(p.val).toLocaleString();
     }
 
@@ -21,9 +25,14 @@
       if (p.key === "responsible_country") p.key = "Responsible Country";
       if (p.key === "fa_date_info") p.key = "Fairway Date";
       if (p.key === "fa_critical") p.key = "Fairway Critical";
+      if (p.key === "gauge_objname") p.key = "Reference Gauge";
+      if (p.key === "source_organization") p.key = "Source Organization";
+      if (p.key === "gm_measuredate") p.key = "Gauge Waterlevel Date";
+      if (p.key === "gm_waterlevel") p.key = "Gauge Waterlevel";
+      if (p.key === "gm_n_14d") p.key = "G.W. Count in Last 14 Days";
 
       // remove certain props
-      let propsToRemove = ["nobjnm", "reference_water_levels"];
+      let propsToRemove = ["nobjnm", "reference_water_levels", "fa_data"];
       if (propsToRemove.indexOf(p.key) !== -1) return null;
 
       return p;
@@ -38,6 +47,9 @@
   distance_marks_geoserver: {
     label: "Distance Mark"
   },
+  distance_marks_ashore_geoserver: {
+    label: "Distance Mark ashore"
+  },
   waterway_axis: {
     label: "Waterway Axis"
   },
@@ -47,8 +59,22 @@
   stretches_geoserver: {
     label: "Stretch"
   },
+  sections_geoserver: {
+    label: "Section"
+  },
   gauges_geoserver: {
-    label: "Gauge"
+    label: "Gauge",
+    props: p => {
+      if (p.key === "gm_measuredate") p.key = "Latest Waterlevel Date";
+      if (p.key === "gm_waterlevel") p.key = "Latest Waterlevel";
+      if (p.key === "gm_n_14d") p.key = "Measurement Count in Last 14 Days";
+
+      // remove certain props
+      let propsToRemove = ["nsc_data"];
+      if (propsToRemove.indexOf(p.key) !== -1) return null;
+
+      return p;
+    }
   }
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/Import.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,280 @@
+<template>
+  <div>
+    <UIBoxHeader icon="clock" :title="title" :closeCallback="$parent.close" />
+    <div v-if="mode === $options.MODES.LIST">
+      <UISpinnerOverlay v-if="loading" />
+      <UITableHeader
+        :columns="[
+          { id: 'id', title: `${idLabel}`, class: 'col-1' },
+          { id: 'kind', title: `${typeLabel}`, class: 'col-1' },
+          { id: 'user', title: `${ownerLabel}`, class: 'col-2' },
+          { id: 'country', title: `${countryLabel}`, class: 'col-1' },
+          { id: 'config.cron', title: `${scheduleLabel}`, class: 'col-2' },
+          { id: 'config.send-email', title: `${emailLabel}`, class: 'col-2' }
+        ]"
+      />
+      <UITableBody
+        :data="filteredSchedules | sortTable(sortColumn, sortDirection)"
+        :isActive="item => currentSchedule && item.id === currentSchedule.id"
+      >
+        <template v-slot:row="{ item: schedule }">
+          <div class="table-cell py-1 col-1">{{ schedule.id }}</div>
+          <div class="table-cell py-1 col-1">
+            {{ schedule.kind.toUpperCase() }}
+          </div>
+          <div style="width:115px;" class="table-cell py-1">
+            {{ schedule.user }}
+          </div>
+          <div style="width:55px;" class="table-cell py-1">
+            {{ userCountries[schedule.user] }}
+          </div>
+          <div class="table-cell py-1 col-2">{{ schedule.config.cron }}</div>
+          <div class="table-cell py-1 col-2">
+            <font-awesome-icon
+              v-if="schedule.config['send-email']"
+              class="fa-fw mr-2"
+              fixed-width
+              icon="check"
+            />
+          </div>
+          <div class="table-cell py-1 col justify-content-end">
+            <button
+              @click="triggerManualImport(schedule.id)"
+              class="btn btn-xs btn-dark mr-1"
+              :disabled="importScheduleDetailVisible"
+            >
+              <font-awesome-icon icon="play" fixed-width />
+            </button>
+            <button
+              @click="editSchedule(schedule.id)"
+              class="btn btn-xs btn-dark mr-1"
+              :disabled="importScheduleDetailVisible"
+            >
+              <font-awesome-icon icon="pencil-alt" fixed-width />
+            </button>
+            <button
+              @click="deleteSchedule(schedule)"
+              class="btn btn-xs btn-dark"
+              :disabled="importScheduleDetailVisible"
+            >
+              <font-awesome-icon icon="trash" fixed-width />
+            </button>
+          </div>
+        </template>
+      </UITableBody>
+    </div>
+    <ImportDetails v-if="mode === $options.MODES.EDIT"></ImportDetails>
+    <div
+      class="text-right border-top p-2"
+      v-if="mode === $options.MODES.LIST && !isOnetime"
+    >
+      <button :key="3" @click="newConfiguration()" class="btn btn-sm btn-info">
+        <translate>New import</translate>
+      </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.kottlaender@intevation.de>
+ */
+import { mapState, mapGetters } from "vuex";
+import { displayInfo, displayError } from "@/lib/errors";
+import { HTTP } from "@/lib/http";
+import {
+  IMPORTTYPES,
+  MODES
+  // IMPORTTYPEKIND,
+  // initializeCurrentSchedule
+} from "@/store/importschedule";
+import { sortTable } from "@/lib/mixins";
+
+export default {
+  mixins: [sortTable],
+  components: {
+    ImportDetails: () => import("./ImportDetails")
+  },
+  data() {
+    return {
+      loading: false,
+      sortColumn: "",
+      sortDirection: ""
+    };
+  },
+  methods: {
+    back() {
+      this.$store.commit("importschedule/setListMode");
+    },
+    newConfiguration() {
+      this.$store.commit("importschedule/setEditMode");
+    },
+    getSchedules() {
+      this.loading = true;
+      this.$store
+        .dispatch("importschedule/loadSchedules")
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        })
+        .finally(() => {
+          this.loading = false;
+        });
+    },
+    editSchedule(id) {
+      this.$store
+        .dispatch("importschedule/loadSchedule", id)
+        .then(() => {
+          this.$store.commit("importschedule/setEditMode");
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    },
+    triggerManualImport(id) {
+      HTTP.get("/imports/config/" + id + "/run", {
+        headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+      })
+        .then(response => {
+          const { id } = response.data;
+          displayInfo({
+            title: this.$gettext("Imports"),
+            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}`
+          });
+        });
+    },
+    deleteSchedule(schedule) {
+      console.log(schedule);
+      this.$store.commit("application/popup", {
+        icon: "trash",
+        title: this.$gettext("Delete Import"),
+        content:
+          this.$gettext("Do you really want to delete the import with ID") +
+          `<b>${schedule.id}</b>` +
+          this.$gettext("of type") +
+          `<b>${schedule.kind.toUpperCase()}</b>?`,
+        confirm: {
+          label: this.$gettext("Delete"),
+          icon: "trash",
+          callback: () => {
+            this.$store
+              .dispatch("importschedule/deleteSchedule", schedule.id)
+              .then(() => {
+                this.getSchedules();
+                displayInfo({
+                  title: this.$gettext("Imports"),
+                  message: this.$gettext("Deleted import: #") + schedule.id
+                });
+              })
+              .catch(error => {
+                const { status, data } = error.response;
+                displayError({
+                  title: this.$gettext("Backend Error"),
+                  message: `${status}: ${data.message || data}`
+                });
+              });
+          }
+        },
+        cancel: {
+          label: this.$gettext("Cancel"),
+          icon: "times"
+        }
+      });
+    }
+  },
+  computed: {
+    ...mapState("application", ["searchQuery"]),
+    ...mapState("importschedule", [
+      "mode",
+      "schedules",
+      "currentSchedule",
+      "importScheduleDetailVisible"
+    ]),
+    ...mapGetters("usermanagement", ["userCountries"]),
+    countryLabel() {
+      return this.$gettext("Country");
+    },
+    isOnetime() {
+      for (let kind of [
+        this.$options.IMPORTTYPES.SOUNDINGRESULTS,
+        this.$options.IMPORTTYPES.APPROVEDGAUGEMEASUREMENTS,
+        this.$options.IMPORTTYPES.WATERWAYPROFILES
+      ]) {
+        if (kind === this.currentSchedule.importType) return true;
+      }
+      return false;
+    },
+    title() {
+      return this.$gettext("Imports");
+    },
+    filteredSchedules() {
+      return this.schedules.filter(s => {
+        return (s.id + s.kind)
+          .toLowerCase()
+          .includes(this.searchQuery.toLowerCase());
+      });
+    },
+    importScheduleLabel() {
+      return this.$gettext("Import Schedule");
+    },
+    idLabel() {
+      return this.$gettext("ID");
+    },
+    typeLabel() {
+      return this.$gettext("Type");
+    },
+    ownerLabel() {
+      return this.$gettext("Owner");
+    },
+    scheduleLabel() {
+      return this.$gettext("Schedule");
+    },
+    emailLabel() {
+      return this.$gettext("Email");
+    }
+  },
+  mounted() {
+    this.$store
+      .dispatch("usermanagement/loadUsers")
+      .then(() => {
+        this.$store.commit("importschedule/setListMode");
+        this.$store.commit("importschedule/clearCurrentSchedule");
+        this.getSchedules();
+      })
+      .catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      });
+  },
+  IMPORTTYPES: IMPORTTYPES,
+  MODES: MODES
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/ImportDetails.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,143 @@
+<template>
+  <div class="text-left">
+    <div>
+      <div class="p-2 pb-3 border-bottom">
+        <small class="text-muted">
+          <translate>Import type</translate>
+        </small>
+        <select
+          v-model="Import"
+          class="custom-select custom-select-sm"
+          id="importtype"
+        >
+          <optgroup :label="regularLabel">
+            <option :value="$options.IMPORTTYPES.WATERWAYAREA">
+              <translate>Waterway area</translate>
+            </option>
+            <option :value="$options.IMPORTTYPES.WATERWAYAXIS">
+              <translate>Waterway axis</translate>
+            </option>
+            <option :value="$options.IMPORTTYPES.FAIRWAYDIMENSION">
+              <translate>Fairway dimension</translate>
+            </option>
+            <option :value="$options.IMPORTTYPES.DISTANCEMARKSVIRTUAL">
+              <translate>Distance marks virtual</translate>
+            </option>
+            <option :value="$options.IMPORTTYPES.DISTANCEMARKSASHORE">
+              <translate>Distance marks ashore</translate>
+            </option>
+            <option :value="$options.IMPORTTYPES.WATERWAYGAUGES">
+              <translate>Waterway gauges</translate>
+            </option>
+            <option :value="$options.IMPORTTYPES.BOTTLENECK">
+              <translate>Bottlenecks</translate>
+            </option>
+            <option :value="$options.IMPORTTYPES.FAIRWAYAVAILABILITY">
+              <translate>Available fairway depths</translate>
+            </option>
+            <option :value="$options.IMPORTTYPES.GAUGEMEASUREMENT">
+              <translate>Gauge measurement</translate>
+            </option>
+          </optgroup>
+          <optgroup :label="onetimeLabel">
+            <option :value="$options.IMPORTTYPES.SOUNDINGRESULTS">
+              <translate>Soundingresults</translate>
+            </option>
+            <option :value="$options.IMPORTTYPES.APPROVEDGAUGEMEASUREMENTS">
+              <translate>Approved Gaugemeasurements</translate>
+            </option>
+            <option :value="$options.IMPORTTYPES.WATERWAYPROFILES">
+              <translate>Waterway Profiles</translate>
+            </option>
+          </optgroup>
+        </select>
+      </div>
+      <ApprovedGaugeMeasurement
+        v-if="Import === $options.IMPORTTYPES.APPROVEDGAUGEMEASUREMENTS"
+        class="mt-1"
+      />
+      <WaterwayProfiles
+        class="mt-1"
+        v-if="Import === $options.IMPORTTYPES.WATERWAYPROFILES"
+      />
+      <SoundingResults
+        class="mt-1"
+        v-if="Import === $options.IMPORTTYPES.SOUNDINGRESULTS"
+      />
+      <ScheduledImports
+        class="mt-1"
+        v-if="Import && !isOnetime"
+      ></ScheduledImports>
+    </div>
+    <div v-if="!Import" class="p-2">
+      <button :key="1" @click="back()" class="btn btn-sm btn-warning">
+        Back
+      </button>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped></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, 2019 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Tom Gottfried <tom.gottfried@intevation.de>
+ */
+import { IMPORTTYPES } from "@/store/importschedule";
+import { mapState } from "vuex";
+export default {
+  components: {
+    ApprovedGaugeMeasurement: () => import("./types/ApprovedGaugeMeasurement"),
+    WaterwayProfiles: () => import("./types/WaterwayProfiles"),
+    SoundingResults: () => import("./types/Soundingresults"),
+    ScheduledImports: () => import("./ScheduledImports")
+  },
+  data() {
+    return {};
+  },
+  computed: {
+    ...mapState("importschedule", ["currentSchedule"]),
+    isOnetime() {
+      for (let kind of [
+        this.$options.IMPORTTYPES.SOUNDINGRESULTS,
+        this.$options.IMPORTTYPES.APPROVEDGAUGEMEASUREMENTS,
+        this.$options.IMPORTTYPES.WATERWAYPROFILES
+      ]) {
+        if (kind === this.currentSchedule.importType) return true;
+      }
+      return false;
+    },
+    Import: {
+      get() {
+        return this.currentSchedule.importType;
+      },
+      set(value) {
+        this.$store.commit("importschedule/setImportType", value);
+      }
+    },
+    onetimeLabel() {
+      return this.$gettext("Onetime Imports");
+    },
+    regularLabel() {
+      return this.$gettext("Regular Imports");
+    }
+  },
+  methods: {
+    back() {
+      this.$store.commit("importschedule/setListMode");
+    }
+  },
+  IMPORTTYPES: IMPORTTYPES
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/ScheduledImports.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,1029 @@
+<template>
+  <form @submit.prevent="save" class="w-100">
+    <div class="d-flex px-2">
+      <div :key="1" class="flex-column">
+        <small class="text-muted">
+          <translate>Email Notification</translate>
+        </small>
+        <div class="flex-flex-row text-left">
+          <toggle-button
+            :value="eMailNotification"
+            v-model="eMailNotification"
+            :sync="true"
+            class="mt-2"
+            :speed="100"
+            :labels="{
+              checked: this.$options.on,
+              unchecked: this.$options.off
+            }"
+            :width="60"
+            :height="30"
+          />
+        </div>
+      </div>
+      <div
+        :key="2"
+        v-if="directImportAvailable"
+        class="flex-column text-left ml-3"
+      >
+        <small class="text-muted">
+          <translate>Import via</translate>
+        </small>
+        <div>
+          <!-- '#75c791' is the DEFAULT_COLOR_CHECKED
+                  from vue-js-toggle-button as here both states are active -->
+          <toggle-button
+            :color="{ unchecked: '#75c791' }"
+            v-model="directImport"
+            class="mt-2"
+            :speed="100"
+            :labels="{
+              checked: this.$options.FILE,
+              unchecked: this.$options.URL
+            }"
+            :width="60"
+            :height="30"
+          />
+        </div>
+      </div>
+    </div>
+    <Availablefairwaydepth
+      v-if="
+        import_ == $options.IMPORTTYPES.FAIRWAYAVAILABILITY && !directImport
+      "
+      @urlChanged="setUrl"
+      :url="url"
+    />
+    <Bottleneck
+      v-if="import_ == $options.IMPORTTYPES.BOTTLENECK"
+      @urlChanged="setUrl"
+      @toleranceChanged="setTolerance"
+      :url="url"
+      :tolerance="tolerance"
+      :directImport="directImport"
+    />
+    <Distancemarksvirtual
+      v-if="import_ == $options.IMPORTTYPES.DISTANCEMARKSVIRTUAL"
+      @urlChanged="setUrl"
+      @usernameChanged="setUsername"
+      @passwordChanged="setPassword"
+      :url="url"
+      :username="username"
+      :password="password"
+    />
+    <Distancemarksashore
+      v-if="import_ == $options.IMPORTTYPES.DISTANCEMARKSASHORE"
+      @urlChanged="setUrl"
+      @featureTypeChanged="setFeatureType"
+      @sortByChanged="setSortBy"
+      :url="url"
+      :featureType="featureType"
+      :sortBy="sortBy"
+    />
+    <Faiwaydimensions
+      v-if="import_ == $options.IMPORTTYPES.FAIRWAYDIMENSION"
+      @urlChanged="setUrl"
+      @featureTypeChanged="setFeatureType"
+      @sortByChanged="setSortBy"
+      @LOSChanged="setLOS"
+      @depthChanged="setDepth"
+      @minWidthChanged="setMinWidth"
+      @maxWidthChanged="setMaxWidth"
+      @sourceOrganizationChanged="setSourceOrganization"
+      :url="url"
+      :featureType="featureType"
+      :sortBy="sortBy"
+      :LOS="LOS"
+      :minWidth="minWidth"
+      :maxWidth="maxWidth"
+      :sourceOrganization="sourceOrganization"
+      :depth="depth"
+    />
+    <Gaugemeasurement
+      v-if="import_ == $options.IMPORTTYPES.GAUGEMEASUREMENT && !directImport"
+      @urlChanged="setUrl"
+      :url="url"
+    />
+    <Waterwayarea
+      v-if="import_ == $options.IMPORTTYPES.WATERWAYAREA"
+      @urlChanged="setUrl"
+      @featureTypeChanged="setFeatureType"
+      @sortByChanged="setSortBy"
+      :url="url"
+      :featureType="featureType"
+      :sortBy="sortBy"
+    />
+    <Waterwaygauges
+      v-if="import_ == $options.IMPORTTYPES.WATERWAYGAUGES"
+      @urlChanged="setUrl"
+      @usernameChanged="setUsername"
+      @passwordChanged="setPassword"
+      :url="url"
+      :username="username"
+      :password="password"
+    />
+    <Waterwayaxis
+      v-if="import_ == $options.IMPORTTYPES.WATERWAYAXIS"
+      @urlChanged="setUrl"
+      @featureTypeChanged="setFeatureType"
+      @sortByChanged="setSortBy"
+      :url="url"
+      :featureType="featureType"
+      :sortBy="sortBy"
+    />
+
+    <div class="d-flex p-2">
+      <template v-if="!directImport || !directImportAvailable">
+        <div class="flex-column mr-4">
+          <div class="flex-row text-left">
+            <small class="text-muted">
+              <translate>Scheduled</translate>?
+            </small>
+          </div>
+          <div class="flex-flex-row text-left">
+            <toggle-button
+              :value="scheduled"
+              v-model="scheduled"
+              :sync="true"
+              class="mt-2"
+              :speed="100"
+              :labels="{
+                checked: this.$options.on,
+                unchecked: this.$options.off
+              }"
+              :width="60"
+              :height="30"
+            />
+          </div>
+        </div>
+        <div class="flex-column mr-2">
+          <div class="flex-row text-left">
+            <small class="text-muted">
+              <translate>Simple</translate>
+            </small>
+          </div>
+          <div class="flex-flex-row text-left">
+            <toggle-button
+              :disabled="!scheduled"
+              :value="easyCron"
+              v-model="easyCron"
+              :sync="true"
+              class="mt-2"
+              :speed="100"
+              :labels="{
+                checked: this.$options.on,
+                unchecked: this.$options.off
+              }"
+              :width="60"
+              :height="30"
+            />
+          </div>
+        </div>
+        <div class="ml-auto flex-column">
+          <div class="flex-row text-left">
+            <small class="text-muted">
+              <translate>Tries</translate>
+            </small>
+          </div>
+          <div>
+            <input
+              style="width:120px;"
+              v-model="trys"
+              class="mr-1 form-control form-control-sm"
+              type="number"
+            />
+          </div>
+        </div>
+        <div class="flex-column">
+          <div class="flex-row text-left">
+            <small class="text-muted">
+              <translate>Wait to retry</translate>
+            </small>
+          </div>
+          <div>
+            <input
+              style="width:120px;"
+              v-model="waitRetry"
+              class="ml-1 form-control form-control-sm"
+            />
+          </div>
+        </div>
+      </template>
+    </div>
+    <template v-if="!directImport || !directImportAvailable">
+      <div class="flex-column w-100 px-2 pb-3">
+        <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
+            :disabled="!scheduled"
+            v-model="simple"
+            class="form-control form-control-sm"
+            ><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">
+            <div class="my-auto mr-2">{{ $options.EVERY }}</div>
+            <select
+              :disabled="!scheduled"
+              style="width: 130px;"
+              v-model="cronMode"
+              class="form-control form-control-sm"
+              @change="clearInputs"
+            >
+              <option :value="null"></option>
+              <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">
+              <div class="mt-auto mb-auto">{{ $options.ON }}</div>
+              <input
+                :disabled="!scheduled"
+                v-model="minutes"
+                class="cronfield ml-1 mr-1 form-control form-control-sm"
+                type="number"
+              />
+              <div class="mt-auto mb-auto">{{ $options.MINUTESPAST }}</div>
+            </div>
+            <div v-if="cronMode == 'day'" class="ml-1 d-flex flex-row">
+              <div class="mt-auto mb-auto">{{ $options.AT }}</div>
+              <input
+                :disabled="!scheduled"
+                v-model="hour"
+                class="cronfield ml-1 mr-1 form-control form-control-sm"
+                type="number"
+              />
+              <input
+                :disabled="!scheduled"
+                v-model="minutes"
+                class="cronfield ml-1 mr-1 form-control form-control-sm"
+                type="number"
+              />
+              <div class="mt-auto mb-auto">{{ $options.OCLOCK }}</div>
+            </div>
+            <div v-if="cronMode == 'week'" class="ml-1 d-flex flex-row">
+              <div class="ml-1 mr-1 mt-auto mb-auto">{{ $options.ON }}</div>
+              <select
+                :disabled="!scheduled"
+                v-model="day"
+                class="form-control form-control-sm"
+              >
+                <option
+                  v-for="(option, key) in $options.DAYSOFWEEK"
+                  :key="key"
+                  :value="key"
+                  >{{ option }}</option
+                >
+              </select>
+              <div class="ml-1 mt-auto mb-auto">{{ $options.AT }}</div>
+              <input
+                :disabled="!scheduled"
+                v-model="hour"
+                class="cronfield ml-1 mr-1 form-control form-control-sm"
+                type="number"
+              />
+              <input
+                :disabled="!scheduled"
+                v-model="minutes"
+                class="cronfield ml-1 mr-1 form-control form-control-sm"
+                type="number"
+              />
+            </div>
+            <div v-if="cronMode == 'month'" class="ml-1 d-flex flex-row">
+              <div class="ml-1 mt-auto mb-auto">{{ $options.ON }}</div>
+              <input
+                :disabled="!scheduled"
+                v-model="dayOfMonth"
+                class="cronfield ml-1 mr-1 form-control form-control-sm"
+                type="number"
+              />
+              <div class="mt-auto mb-auto">{{ $options.AT }}</div>
+              <input
+                :disabled="!scheduled"
+                v-model="hour"
+                class="cronfield ml-1 mr-2 form-control form-control-sm"
+                type="number"
+              />
+              <input
+                :disabled="!scheduled"
+                v-model="minutes"
+                class="cronfield ml-1 mr-2 form-control form-control-sm"
+                type="number"
+              />
+              <div class="mt-auto mb-auto">{{ $options.OCLOCK }}</div>
+            </div>
+            <div v-if="cronMode == 'year'" class="ml-1 d-flex flex-row">
+              <div class="ml-1 mt-auto mb-auto">{{ $options.ON }}</div>
+              <input
+                :disabled="!scheduled"
+                v-model="dayOfMonth"
+                class="cronfield ml-1 mr-1 form-control form-control-sm"
+                type="number"
+              />
+              <div class="mt-auto mb-auto">{{ $options.OF }}</div>
+              <select
+                :disabled="!scheduled"
+                v-model="month"
+                class="ml-1 mr-1 form-control form-control-sm"
+              >
+                <option
+                  v-for="(option, key) in $options.MONTHS"
+                  :value="key"
+                  :key="key"
+                  >{{ option }}</option
+                >
+              </select>
+              <div class="mt-auto mb-auto">{{ $options.ON }}</div>
+              <input
+                :disabled="!scheduled"
+                v-model="hour"
+                class="cronfield ml-1 mr-1 form-control form-control-sm"
+                type="number"
+              />
+              <input
+                :disabled="!scheduled"
+                v-model="minutes"
+                class="cronfield ml-1 mr-1 form-control form-control-sm"
+                type="number"
+              />
+            </div>
+          </div>
+          <div class="mt-3 w-50 d-flex">
+            <div class="my-auto mr-2">
+              <translate>Cronstring</translate>
+            </div>
+            <input
+              :disabled="!scheduled"
+              class="form-control form-control-sm"
+              v-model="cronString"
+              type="text"
+            />
+          </div>
+        </div>
+      </div>
+    </template>
+    <div v-else class="d-flex text-left px-2 pb-3">
+      <div class="flex-column w-100">
+        <div class="custom-file">
+          <input
+            accept=".xml"
+            type="file"
+            @change="fileSelected"
+            class="custom-file-input custom-file-input-sm"
+            id="uploadFile"
+          />
+          <label class="pointer custom-file-label" for="uploadFile">
+            {{ uploadLabel }}
+          </label>
+        </div>
+      </div>
+    </div>
+    <div class="d-flex justify-content-between p-2 border-top">
+      <button :key="1" @click="back()" class="btn btn-sm btn-warning">
+        Back
+      </button>
+      <div>
+        <button
+          v-if="!currentSchedule.id"
+          @click="triggerManualImport"
+          type="button"
+          class="btn btn-sm btn-outline-info"
+          :disabled="!triggerActive || !isValid"
+        >
+          <font-awesome-icon fixed-width icon="play" />
+          <translate>Trigger import</translate>
+        </button>
+        <button
+          v-if="!directImport || !directImportAvailable"
+          :disabled="!isValid"
+          type="submit"
+          class="btn btn-sm btn-info ml-3"
+        >
+          <translate>Save</translate>
+        </button>
+      </div>
+    </div>
+  </form>
+</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, 2019 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Tom Gottfried <tom.gottfried@intevation.de>
+ */
+import {
+  IMPORTTYPES,
+  IMPORTTYPEKIND,
+  initializeCurrentSchedule
+} from "@/store/importschedule";
+import { mapState } from "vuex";
+import { displayInfo, displayError } from "@/lib/errors";
+import app from "@/main";
+import { HTTP } from "@/lib/http";
+
+export default {
+  name: "importscheduledetail",
+  components: {
+    Availablefairwaydepth: () => import("./types/Availablefairwaydepth"),
+    Bottleneck: () => import("./types/Bottleneck"),
+    Distancemarksvirtual: () => import("./types/Distancemarksvirtual"),
+    Distancemarksashore: () => import("./types/Distancemarksashore"),
+    Faiwaydimensions: () => import("./types/Fairwaydimensions"),
+    Gaugemeasurement: () => import("./types/Gaugemeasurement"),
+    Waterwayarea: () => import("./types/Waterwayarea"),
+    Waterwaygauges: () => import("./types/Waterwaygauges"),
+    Waterwayaxis: () => import("./types/Waterwayaxis")
+  },
+  data() {
+    return {
+      directImport: false,
+      passwordVisible: false,
+      uploadLabel: this.$gettext("choose file to upload"),
+      uploadFile: null,
+      ...initializeCurrentSchedule()
+    };
+  },
+  mounted() {
+    this.initialize();
+  },
+  watch: {
+    cronMode() {
+      this.cronString = this.calcCronString();
+    },
+    minutes() {
+      this.cronString = this.calcCronString();
+    },
+    hour() {
+      this.cronString = this.calcCronString();
+    },
+    month() {
+      this.cronString = this.calcCronString();
+    },
+    day() {
+      this.cronString = this.calcCronString();
+    },
+    dayOfMonth() {
+      this.cronString = this.calcCronString();
+    },
+    importScheduleDetailVisible() {
+      this.initialize();
+    },
+    cronString() {
+      if (this.isWeekly(this.cronString)) {
+        this.simple = "weekly";
+      }
+      if (this.isMonthly(this.cronString)) {
+        this.simple = "monthly";
+      }
+    }
+  },
+  computed: {
+    ...mapState("importschedule", [
+      "importScheduleDetailVisible",
+      "currentSchedule"
+    ]),
+    import_() {
+      return this.currentSchedule.importType;
+    },
+    directImportAvailable() {
+      switch (this.import_) {
+        case this.$options.IMPORTTYPES.BOTTLENECK:
+        case this.$options.IMPORTTYPES.FAIRWAYAVAILABILITY:
+        case this.$options.IMPORTTYPES.GAUGEMEASUREMENT:
+          return true;
+        default:
+          return false;
+      }
+    },
+    isCredentialsRequired() {
+      switch (this.import_) {
+        case this.$options.IMPORTTYPES.WATERWAYGAUGES:
+        case this.$options.IMPORTTYPES.DISTANCEMARKSVIRTUAL:
+          return true;
+        default:
+          return false;
+      }
+    },
+    isURLRequired() {
+      switch (this.import_) {
+        case this.$options.IMPORTTYPES.BOTTLENECK:
+        case this.$options.IMPORTTYPES.WATERWAYAXIS:
+        case this.$options.IMPORTTYPES.GAUGEMEASUREMENT:
+        case this.$options.IMPORTTYPES.FAIRWAYAVAILABILITY:
+        case this.$options.IMPORTTYPES.WATERWAYAREA:
+        case this.$options.IMPORTTYPES.FAIRWAYDIMENSION:
+        case this.$options.IMPORTTYPES.WATERWAYGAUGES:
+        case this.$options.IMPORTTYPES.DISTANCEMARKSVIRTUAL:
+        case this.$options.IMPORTTYPES.DISTANCEMARKSASHORE:
+          return true;
+        default:
+          return false;
+      }
+    },
+    isFeatureTypeRequired() {
+      switch (this.import_) {
+        case this.$options.IMPORTTYPES.WATERWAYAXIS:
+        case this.$options.IMPORTTYPES.WATERWAYAREA:
+        case this.$options.IMPORTTYPES.FAIRWAYDIMENSION:
+        case this.$options.IMPORTTYPES.DISTANCEMARKSASHORE:
+          return true;
+        default:
+          return false;
+      }
+    },
+    isSortbyRequired() {
+      switch (this.import_) {
+        case this.$options.IMPORTTYPES.WATERWAYAXIS:
+        case this.$options.IMPORTTYPES.WATERWAYAREA:
+        case this.$options.IMPORTTYPES.FAIRWAYDIMENSION:
+        case this.$options.IMPORTTYPES.DISTANCEMARKSASHORE:
+          return true;
+        default:
+          return false;
+      }
+    },
+    isToleranceRequired() {
+      switch (this.import_) {
+        case this.$options.IMPORTTYPES.BOTTLENECK:
+          return true;
+        default:
+          return false;
+      }
+    },
+    usernamePasswordFilled() {
+      if (
+        this.isCredentialsRequired &&
+        this.currentSchedule.id &&
+        this.username
+      )
+        return true;
+      if (
+        this.isCredentialsRequired &&
+        !this.currentSchedule.id &&
+        this.username &&
+        this.password
+      )
+        return true;
+      return false;
+    },
+    isValid() {
+      if (!this.import_) return false;
+      if (this.isToleranceRequired && !this.tolerance) return false;
+      if (this.directImport && !this.uploadFile) return false;
+      else if (!this.directImport) {
+        if (this.isURLRequired && !this.url) return false;
+        if (this.isSortbyRequired && !this.sortBy) return false;
+        if (this.isFeatureTypeRequired && !this.featureType) return false;
+        if (this.isCredentialsRequired && !this.usernamePasswordFilled)
+          return false;
+        if (this.import_ == this.$options.IMPORTTYPES.FAIRWAYDIMENSION) {
+          if (
+            !this.LOS ||
+            !this.minWidth ||
+            !this.maxWidth ||
+            !this.depth ||
+            !this.sourceOrganization
+          )
+            return false;
+        }
+      }
+      return true;
+    }
+  },
+  methods: {
+    back() {
+      this.$store.commit("importschedule/setListMode");
+    },
+    fileSelected(e) {
+      const files = e.target.files || e.dataTransfer.files;
+      if (!files) return;
+      this.uploadLabel = files[0].name;
+      this.uploadFile = files[0];
+    },
+    setUrl(value) {
+      this.url = value;
+    },
+    setFeatureType(value) {
+      this.featureType = value;
+    },
+    setSortBy(value) {
+      this.sortBy = value;
+    },
+    setTolerance(value) {
+      this.tolerance = value;
+    },
+    setUsername(value) {
+      this.username = value;
+    },
+    setPassword(value) {
+      this.password = value;
+    },
+    setLOS(value) {
+      this.LOS = value;
+    },
+    setMinWidth(value) {
+      this.minWidth = value;
+    },
+    setMaxWidth(value) {
+      this.maxWidth = value;
+    },
+    setDepth(value) {
+      this.depth = value;
+    },
+    setSourceOrganization(value) {
+      this.sourceOrganization = value;
+    },
+    calcCronString() {
+      let getValue = value => {
+        return this[value] !== null ? this[value] : "*";
+      };
+
+      const min = getValue("minutes");
+      const h = getValue("hour");
+      const dm = getValue("dayOfMonth");
+      const m = getValue("month");
+      const wd = getValue("day");
+
+      if (this.cronMode === "15minutes") return "0 */15 * * * *";
+      if (this.cronMode === "hour") return `0 ${min} * * * *`;
+      if (this.cronMode === "day") return `0 ${min} ${h} * * *`;
+      if (this.cronMode === "week") return `0 ${min} ${h} * * ${wd}`;
+      if (this.cronMode === "month") return `0 ${min} ${h} ${dm} * *`;
+      if (this.cronMode === "year") return `0 ${min} ${h} ${dm} ${m} *`;
+      return this.cronString;
+    },
+    validateBottleneckfields() {
+      return !!this.url;
+    },
+    initialize() {
+      this.id = this.currentSchedule.id;
+      this.importType = this.currentSchedule.importType;
+      this.schedule = this.currentSchedule.schedule;
+      this.scheduled = this.currentSchedule.scheduled;
+      this.importSource = this.currentSchedule.importSource;
+      this.eMailNotification = this.currentSchedule.eMailNotification;
+      this.easyCron = this.currentSchedule.easyCron;
+      this.cronMode = this.currentSchedule.cronMode;
+      this.minutes = this.currentSchedule.minutes;
+      this.month = this.currentSchedule.month;
+      this.hour = this.currentSchedule.hour;
+      this.day = this.currentSchedule.day;
+      this.dayOfMonth = this.currentSchedule.dayOfMonth;
+      this.simple = this.currentSchedule.simple;
+      this.url = this.currentSchedule.url;
+      this.insecure = this.currentSchedule.insecure;
+      this.cronString = this.currentSchedule.cronString;
+      this.featureType = this.currentSchedule.featureType;
+      this.sortBy = this.currentSchedule.sortBy;
+      this.tolerance = this.currentSchedule.tolerance;
+      this.username = this.currentSchedule.username;
+      this.password = this.currentSchedule.password;
+      this.LOS = this.currentSchedule.LOS;
+      this.minWidth = this.currentSchedule.minWidth;
+      this.maxWidth = this.currentSchedule.maxWidth;
+      this.depth = this.currentSchedule.depth;
+      this.sourceOrganization = this.currentSchedule.sourceOrganization;
+      this.directImport = false;
+      this.trys = this.currentSchedule.trys;
+      this.waitRetry = this.currentSchedule.waitRetry;
+    },
+    isWeekly(cron) {
+      return /0 \d{1,2} \d{1,2} \* \* \d{1}/.test(cron);
+    },
+    isMonthly(cron) {
+      return /0 \d{1,2} \d{1,2} \d{1,2} \* \*/.test(cron);
+    },
+    clearInputs() {
+      this.minutes = this.currentSchedule.minutes;
+      this.month = this.currentSchedule.month;
+      this.hour = this.currentSchedule.hour;
+      this.day = this.currentSchedule.day;
+      this.dayOfMonth = this.currentSchedule.dayOfMonth;
+    },
+    triggerFileUpload() {
+      if (!this.uploadFile) return;
+      let formData = new FormData();
+      let routeParam = "";
+      switch (this.import_) {
+        case this.$options.IMPORTTYPES.BOTTLENECK:
+          formData.append("tolerance", this.tolerance);
+          routeParam = "ubn";
+          break;
+        case this.$options.IMPORTTYPES.FAIRWAYAVAILABILITY:
+          routeParam = "ufa";
+          break;
+        case this.$options.IMPORTTYPES.GAUGEMEASUREMENT:
+          routeParam = "ugm";
+          break;
+        default:
+          throw new Error("invalid importroute");
+      }
+
+      formData.append(routeParam, this.uploadFile);
+      if (this.eMailNotification) {
+        formData.append("send-email", this.eMailNotification);
+      }
+      HTTP.post("/imports/" + routeParam, formData, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-Type": "multipart/form-data"
+        }
+      })
+        .then(response => {
+          const { id } = response.data;
+          displayInfo({
+            title: this.$gettext("File Import"),
+            message: this.$gettext("Import import: #") + id
+          });
+          this.closeDetailview();
+          this.$store.dispatch("importschedule/loadSchedules").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}`
+          });
+        });
+    },
+    triggerManualImport() {
+      if (!this.triggerActive) return;
+      if (!this.import_) return;
+      if (this.directImport) {
+        if (!this.uploadFile) return;
+        this.triggerFileUpload();
+        return;
+      }
+      let data = {};
+      if (this.isURLRequired) {
+        if (!this.url) return;
+        data["url"] = this.url;
+        data["insecure"] = this.insecure;
+      }
+      if (this.isFeatureTypeRequired) {
+        if (!this.featureType) return;
+        data["feature-type"] = this.featureType;
+      }
+      if (this.isSortbyRequired) {
+        if (!this.sortBy) return;
+        data["sort-by"] = this.sortBy;
+      }
+      if (this.isToleranceRequired) {
+        if (!this.tolerance) return;
+        data["tolerance"] = parseFloat(this.tolerance);
+      }
+      if (this.isCredentialsRequired) {
+        if (!this.username || !this.password) return;
+        data["user"] = this.username;
+        data["password"] = this.password;
+      }
+      if (this.import_ == this.$options.IMPORTTYPES.FAIRWAYDIMENSION) {
+        if (
+          !this.LOS ||
+          !this.minWidth ||
+          !this.maxWidth ||
+          !this.depth ||
+          !this.sourceOrganization
+        )
+          return;
+        data["feature-type"] = this.featureType;
+        data["sort-by"] = this.sortBy;
+        data["los"] = this.LOS * 1;
+        data["min-width"] = this.minWidth * 1;
+        data["max-width"] = this.maxWidth * 1;
+        data["depth"] = this.depth * 1;
+        data["source-organization"] = this.sourceOrganization;
+      }
+      data["send-email"] = this.eMailNotification;
+      this.triggerActive = false;
+      this.$store
+        .dispatch("importschedule/triggerImport", {
+          type: IMPORTTYPEKIND[this.import_],
+          data
+        })
+        .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() {
+      if (!this.import_) return;
+      let cron = this.cronString;
+      if (this.easyCron) {
+        if (this.simple === "weekly") cron = "0 0 0 * * 0";
+        if (this.simple === "monthly") cron = "0 0 0 1 * *";
+      }
+      let data = {};
+      let config = {};
+      data["kind"] = IMPORTTYPEKIND[this.import_];
+
+      if (this.isURLRequired) {
+        if (!this.url) return;
+        config["url"] = this.url;
+        config["insecure"] = this.insecure;
+      }
+      if (this.isSortbyRequired) {
+        if (!this.sortBy) return;
+        config["sort-by"] = this.sortBy;
+      }
+      if (this.isFeatureTypeRequired) {
+        if (!this.featureType) return;
+        config["feature-type"] = this.featureType;
+      }
+      if (this.isToleranceRequired) {
+        if (!this.tolerance) return;
+        config["tolerance"] = parseFloat(this.tolerance);
+      }
+      if (this.isCredentialsRequired) {
+        if (!this.usernamePasswordFilled) return;
+        config = {
+          ...config,
+          user: this.username
+        };
+        if (this.password) {
+          config["password"] = this.password;
+        }
+      }
+      if (this.import_ == this.$options.IMPORTTYPES.FAIRWAYDIMENSION) {
+        if (
+          !this.LOS ||
+          !this.minWidth ||
+          !this.maxWidth ||
+          !this.depth ||
+          !this.sourceOrganization
+        )
+          return;
+        config = { ...config, los: this.LOS, depth: this.depth };
+        config["min-width"] = this.minWidth;
+        config["max-width"] = this.maxWidth;
+        config["source-organization"] = this.sourceOrganization;
+      }
+      if (this.scheduled) {
+        config["cron"] = cron;
+      }
+      if (this.waitRetry) config["wait-retry"] = this.waitRetry;
+      if (this.trys) config["trys"] = Number(this.trys);
+      config["send-email"] = this.eMailNotification;
+      if (!this.id) {
+        data["config"] = config;
+        this.$store
+          .dispatch("importschedule/saveCurrentSchedule", data)
+          .then(response => {
+            const { id } = response.data;
+            displayInfo({
+              title: this.$gettext("Import"),
+              message: this.$gettext("Saved import: #") + id
+            });
+            this.closeDetailview();
+            this.$store
+              .dispatch("importschedule/loadSchedules")
+              .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}`
+            });
+          });
+      } else {
+        this.$store
+          .dispatch("importschedule/updateCurrentSchedule", {
+            data: config,
+            id: this.id
+          })
+          .then(response => {
+            const { id } = response.data;
+            displayInfo({
+              title: this.$gettext("Import"),
+              message: this.$gettext("update import: #") + id
+            });
+            this.closeDetailview();
+            this.$store
+              .dispatch("importschedule/loadSchedules")
+              .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}`
+            });
+          });
+      }
+    },
+    closeDetailview() {
+      this.$store.commit("importschedule/clearCurrentSchedule");
+      this.$store.commit("importschedule/setListMode");
+    }
+  },
+  IMPORTTYPES: IMPORTTYPES,
+  on: "on",
+  off: "off",
+  FILE: app.$gettext("File"),
+  URL: app.$gettext("URL"),
+  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")
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.cronfield {
+  width: 55px;
+}
+
+.importscheduledetailscard {
+  min-height: 550px;
+}
+
+.importscheduledetails {
+  width: 100%;
+  margin-top: $offset;
+  margin-right: $offset;
+}
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/ApprovedGaugeMeasurement.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,160 @@
+<template>
+  <div>
+    <div class="d-flex px-2">
+      <div :key="1" class="flex-column mr-4">
+        <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="d-flex flex-column text-left w-25">
+        <label class="text-nowrap" for="originator">
+          <small class="text-muted"
+            >{{ $options.ORIGINATOR }} / {{ $options.FROM }}</small
+          >
+        </label>
+        <input
+          type="text"
+          v-model="originator"
+          class="form-control form-control-sm"
+          id="originator"
+        />
+        <span class="text-left text-danger">
+          <small v-if="!originator">
+            <translate>Please enter an originator</translate>
+          </small>
+        </span>
+      </div>
+    </div>
+    <div class="mt-4 flex-column px-2 w-100">
+      <div class="custom-file">
+        <input
+          accept=".csv"
+          type="file"
+          @change="fileSelected"
+          class="custom-file-input"
+          id="uploadFile"
+        />
+        <label class="pointer custom-file-label" for="uploadFile">
+          {{ uploadLabel }}
+        </label>
+      </div>
+    </div>
+    <div class="d-flex w-100 mt-3 border-top justify-content-between p-2">
+      <button :key="1" @click="back()" class="btn btn-sm btn-warning">
+        Back
+      </button>
+      <button
+        :key="2"
+        type="submit"
+        @click="submit"
+        class="btn btn-sm btn-info"
+      >
+        <translate>Submit</translate>
+      </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>
+ */
+
+import { HTTP } from "@/lib/http";
+import { displayError, displayInfo } from "@/lib/errors";
+import app from "@/main";
+
+export default {
+  name: "importapprovedgaugemeasurements",
+  data() {
+    return {
+      disableUploadButton: false,
+      uploadLabel: this.$gettext("choose file to upload"),
+      uploadFile: null,
+      originator: "viadonau",
+      eMailNotification: false
+    };
+  },
+  computed: {
+    importGaugmeasurmentLabel() {
+      return this.$gettext("Import approved gaugemeasurements");
+    }
+  },
+  methods: {
+    back() {
+      this.uploadLabel = this.$gettext("choose file to upload");
+      this.uploadFile = null;
+      this.originator = "viadonau";
+      this.$store.commit("importschedule/setListMode");
+    },
+    fileSelected(e) {
+      const files = e.target.files || e.dataTransfer.files;
+      if (!files) return;
+      this.uploadLabel = files[0].name;
+      this.uploadFile = files[0];
+    },
+    submit() {
+      if (!this.originator || !this.uploadFile) return;
+      let formData = new FormData();
+      formData.append("agm", this.uploadFile);
+      formData.append("originator", this.originator);
+      if (this.eMailNotification) {
+        formData.append("send-email", this.eMailNotification);
+      }
+      HTTP.post("/imports/agm", formData, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-Type": "multipart/form-data"
+        }
+      })
+        .then(() => {
+          displayInfo({
+            title: this.$gettext("Import"),
+            message: this.$gettext(
+              "Starting import of Approved Gauge Measurements"
+            )
+          });
+          this.back();
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    }
+  },
+  ORIGINATOR: app.$gettext("originator"),
+  FROM: app.$gettext("from"),
+  on: "on",
+  off: "off"
+};
+</script>
+
+<style lang="scss" scoped></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/Availablefairwaydepth.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,51 @@
+<template>
+  <div>
+    <div class="d-flex px-2">
+      <div class="flex-column w-100">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>URL</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="urlChanged"
+            class="url form-control form-control-sm"
+            type="url"
+            :value="url"
+          />
+        </div>
+      </div>
+    </div>
+    <div v-if="!url" class="d-flex px-2">
+      <small
+        ><translate class="text-danger">Please enter a URL</translate></small
+      >
+    </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>
+ */
+export default {
+  name: "availablefairwaydepth",
+  props: ["url"],
+  methods: {
+    urlChanged(e) {
+      this.$emit("urlChanged", e.target.value);
+    }
+  }
+};
+</script>
+
+<style></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/Bottleneck.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,111 @@
+<template>
+  <div class="p-2">
+    <div class="d-flex">
+      <div class="flex-column w-100">
+        <template v-if="!directImport">
+          <div class="flex-row text-left">
+            <small class="text-muted">
+              <translate>URL</translate>
+            </small>
+          </div>
+          <div class="w-100">
+            <input
+              @input="urlChanged"
+              class="url form-control form-control-sm"
+              type="url"
+              :value="url"
+            />
+          </div>
+        </template>
+      </div>
+      <div v-if="false" class="flex-column mt-2 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 v-if="!directImport && !url" class="d-flex">
+      <small
+        ><translate class="text-danger">Please enter a URL</translate></small
+      >
+    </div>
+    <div class="d-flex">
+      <div class="flex-column mt-2 mr-3 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted">
+            <translate>Tolerance for snapping of waterway axis [m]</translate>
+          </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="toleranceChanged"
+            class="tolerance form-control form-control-sm"
+            type="number"
+            min="0"
+            :value="tolerance"
+          />
+        </div>
+        <div v-if="!tolerance" class="d-flex">
+          <small
+            ><translate class="text-danger"
+              >Please enter a tolerance value</translate
+            ></small
+          >
+        </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, 2019 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Tom Gottfried <tom.gottfried@intevation.de>
+ */
+export default {
+  name: "bottleneckimport",
+  props: ["url", "tolerance", "directImport"],
+  methods: {
+    urlChanged(e) {
+      this.$emit("urlChanged", e.target.value);
+    },
+    toleranceChanged(e) {
+      this.$emit("toleranceChanged", e.target.value);
+    }
+  },
+  on: "on",
+  off: "off"
+};
+</script>
+
+<style></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/Distancemarksashore.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,99 @@
+<template>
+  <div>
+    <div class="d-flex px-2">
+      <div class="flex-column w-100">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>URL</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="urlChanged"
+            class="url form-control form-control-sm"
+            type="url"
+            :value="url"
+          />
+        </div>
+      </div>
+    </div>
+    <div v-if="!url" class="d-flex px-2">
+      <small
+        ><translate class="text-danger">Please enter a URL</translate></small
+      >
+    </div>
+    <div class="d-flex px-2">
+      <div class="flex-column mt-2 mr-3 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>Featuretype</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="featureTypeChanged"
+            class="featuretype form-control form-control-sm"
+            type="text"
+            :value="featureType"
+          />
+        </div>
+        <div v-if="!featureType" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter a Featuretype</translate
+            ></small
+          >
+        </div>
+      </div>
+      <div class="flex-column mt-2 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>SortBy</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="sortByChanged"
+            class="sortby form-control form-control-sm"
+            type="text"
+            :value="sortBy"
+          />
+        </div>
+        <div v-if="!sortBy" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter SortBy</translate
+            ></small
+          >
+        </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>
+ */
+export default {
+  name: "distancemarksashore",
+  props: ["url", "featureType", "sortBy"],
+  methods: {
+    urlChanged(e) {
+      this.$emit("urlChanged", e.target.value);
+    },
+    featureTypeChanged(e) {
+      this.$emit("featureTypeChanged", e.target.value);
+    },
+    sortByChanged(e) {
+      this.$emit("sortByChanged", e.target.value);
+    }
+  }
+};
+</script>
+
+<style></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/Distancemarksvirtual.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,125 @@
+<template>
+  <div>
+    <div class="d-flex px-2">
+      <div class="flex-column w-100">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>URL</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="urlChanged"
+            class="url form-control form-control-sm"
+            type="url"
+            :value="url"
+          />
+        </div>
+      </div>
+    </div>
+    <div v-if="!url" class="d-flex px-2">
+      <small
+        ><translate class="text-danger">Please enter a URL</translate></small
+      >
+    </div>
+    <div class="d-flex px-2">
+      <div class="flex-column mt-2 mr-3 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>Username</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="usernameChanged"
+            class="username form-control form-control-sm"
+            type="text"
+            :value="username"
+          />
+        </div>
+        <div v-if="!username" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter a Username</translate
+            ></small
+          >
+        </div>
+      </div>
+      <div class="flex-column mt-2 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>Password</translate> </small>
+        </div>
+        <div class="w-100 d-flex flex-row">
+          <input
+            @input="passwordChanged"
+            class="pasword form-control form-control-sm"
+            :type="showPassword"
+            :value="password"
+          />
+          <span
+            class="input-group-text ml-2"
+            @click="passwordVisible = !passwordVisible"
+          >
+            <font-awesome-icon :icon="passwordVisible ? 'eye-slash' : 'eye'" />
+          </span>
+        </div>
+        <div
+          v-if="!password && !this.currentSchedule.id"
+          class="d-flex flex-row"
+        >
+          <small
+            ><translate class="text-danger"
+              >Please enter a Password</translate
+            ></small
+          >
+        </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 { mapState } from "vuex";
+export default {
+  name: "distancemarksvirtual",
+  props: ["url", "username", "password"],
+  data() {
+    return {
+      passwordVisible: false
+    };
+  },
+  computed: {
+    ...mapState("importschedule", [
+      "importScheduleDetailVisible",
+      "currentSchedule"
+    ]),
+    showPassword() {
+      if (this.passwordVisible) return "text";
+      return "password";
+    }
+  },
+  methods: {
+    urlChanged(e) {
+      this.$emit("urlChanged", e.target.value);
+    },
+    usernameChanged(e) {
+      this.$emit("usernameChanged", e.target.value);
+    },
+    passwordChanged(e) {
+      this.$emit("passwordChanged", e.target.value);
+    }
+  }
+};
+</script>
+
+<style></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/Fairwaydimensions.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,243 @@
+<template>
+  <div>
+    <div class="d-flex px-2">
+      <div class="flex-column w-100">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>URL</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="urlChanged"
+            class="url form-control form-control-sm"
+            type="url"
+            :value="url"
+          />
+        </div>
+      </div>
+    </div>
+    <div v-if="!url" class="d-flex px-2">
+      <small
+        ><translate class="text-danger">Please enter a URL</translate></small
+      >
+    </div>
+    <div class="d-flex px-2">
+      <div class="flex-column mt-2 mr-3 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>Featuretype</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="featureTypeChanged"
+            class="featuretype form-control form-control-sm"
+            type="text"
+            :value="featureType"
+          />
+        </div>
+        <div v-if="!featureType" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter a Featuretype</translate
+            ></small
+          >
+        </div>
+      </div>
+      <div class="flex-column mt-2 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>SortBy</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="sortByChanged"
+            class="sortby form-control form-control-sm"
+            type="text"
+            :value="sortBy"
+          />
+        </div>
+        <div v-if="!sortBy" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter SortBy</translate
+            ></small
+          >
+        </div>
+      </div>
+    </div>
+    <div class="d-flex px-2">
+      <div class="flex-column mt-2 mr-3 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>LOS</translate> </small>
+        </div>
+        <div class="w-100">
+          <select v-model="los" class="form-control form-control-sm">
+            <option>1</option>
+            <option>2</option>
+            <option>3</option>
+          </select>
+        </div>
+        <div v-if="!LOS" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter a level of service</translate
+            ></small
+          >
+        </div>
+      </div>
+      <div class="flex-column mt-2 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>Depth</translate> </small>
+        </div>
+        <div class="d-flex flex-row">
+          <input
+            @input="depthChanged"
+            class="depth form-control form-control-sm"
+            type="number"
+            :value="depth"
+          />
+          <div class="ml-2 my-auto">cm</div>
+        </div>
+        <div v-if="!depth" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter a depth</translate
+            ></small
+          >
+        </div>
+      </div>
+    </div>
+    <div class="d-flex px-2">
+      <div class="flex-column mt-2 mr-3 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>MinWidth</translate> </small>
+        </div>
+        <div class="d-flex flex-row">
+          <input
+            @input="minWidthChanged"
+            class="minwidth form-control form-control-sm"
+            type="number"
+            :value="minWidth"
+          />
+          <div class="ml-2 my-auto">&nbsp;m</div>
+        </div>
+        <div v-if="!minWidth" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter a minimum width</translate
+            ></small
+          >
+        </div>
+      </div>
+      <div class="flex-column mt-2 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>MaxWidth</translate> </small>
+        </div>
+        <div class="d-flex flex-row">
+          <input
+            @input="maxWidthChanged"
+            class="maxwidth form-control form-control-sm"
+            type="number"
+            :value="maxWidth"
+          />
+          <div class="ml-2 my-auto">&nbsp;m</div>
+        </div>
+        <div v-if="!maxWidth" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter a maximum width</translate
+            ></small
+          >
+        </div>
+      </div>
+    </div>
+    <div class="d-flex px-2">
+      <div class="flex-column mt-2 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted">
+            <translate>Source orgranization</translate>
+          </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="sourceOrganizationChanged"
+            class="sourceorganization form-control form-control-sm"
+            type="text"
+            :value="sourceOrganization"
+          />
+        </div>
+        <div v-if="!sourceOrganization" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter a source orgranization</translate
+            ></small
+          >
+        </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>
+ */
+export default {
+  name: "fairwaydimensions",
+  props: [
+    "url",
+    "featureType",
+    "sortBy",
+    "depth",
+    "LOS",
+    "minWidth",
+    "maxWidth",
+    "sourceOrganization"
+  ],
+  methods: {
+    urlChanged(e) {
+      this.$emit("urlChanged", e.target.value);
+    },
+    featureTypeChanged(e) {
+      this.$emit("featureTypeChanged", e.target.value);
+    },
+    sortByChanged(e) {
+      this.$emit("sortByChanged", e.target.value);
+    },
+    depthChanged(e) {
+      this.$emit("depthChanged", e.target.value * 1);
+    },
+    LOSChanged(e) {
+      this.$emit("LOSChanged", e.target.value * 1);
+    },
+    minWidthChanged(e) {
+      this.$emit("minWidthChanged", e.target.value * 1);
+    },
+    maxWidthChanged(e) {
+      this.$emit("maxWidthChanged", e.target.value * 1);
+    },
+    sourceOrganizationChanged(e) {
+      this.$emit("sourceOrganizationChanged", e.target.value);
+    }
+  },
+  computed: {
+    los: {
+      get() {
+        return this.LOS;
+      },
+      set(value) {
+        this.$emit("LOSChanged", value * 1);
+      }
+    }
+  }
+};
+</script>
+
+<style></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/Gaugemeasurement.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,51 @@
+<template>
+  <div>
+    <div class="d-flex px-2">
+      <div class="flex-column w-100">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>URL</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="urlChanged"
+            class="url form-control form-control-sm"
+            type="url"
+            :value="url"
+          />
+        </div>
+      </div>
+    </div>
+    <div v-if="!url" class="d-flex px-2">
+      <small
+        ><translate class="text-danger">Please enter a URL</translate></small
+      >
+    </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>
+ */
+export default {
+  name: "gaugemeasurement",
+  props: ["url"],
+  methods: {
+    urlChanged(e) {
+      this.$emit("urlChanged", e.target.value);
+    }
+  }
+};
+</script>
+
+<style></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/Soundingresults.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,382 @@
+<template>
+  <div>
+    <div v-if="editState" class="mb-2 p-2">
+      <div
+        v-for="(message, index) in messages"
+        :key="index"
+        class="alert alert-warning small"
+      >
+        {{ message }}
+      </div>
+      <div class="d-flex w-100">
+        <div class="w-50 mr-2 text-left">
+          <small class="text-muted">
+            <translate>Bottleneck</translate>
+          </small>
+          <select v-model="bottleneck" class="custom-select custom-select-sm">
+            <option
+              v-for="bottleneck in availableBottlenecks"
+              :value="bottleneck"
+              :key="bottleneck.properties.objnam"
+            >
+              {{ bottleneck.properties.objnam }}
+            </option>
+          </select>
+          <span class="text-danger">
+            <small v-if="!bottleneck">
+              <translate>Please select a bottleneck</translate>
+            </small>
+          </span>
+        </div>
+        <div class="w-50 ml-3 text-left">
+          <small class="text-muted">
+            <translate>Projection</translate>&nbsp;(EPSG)
+          </small>
+          <input
+            class="form-control form-control-sm"
+            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 w-100 mt-3">
+        <div class="w-50 mr-2 text-left">
+          <small class="text-muted">
+            <translate>Depthreference</translate>
+          </small>
+          <select
+            v-model="depthReference"
+            class="custom-select custom-select-sm"
+            id="depthreference"
+          >
+            <option
+              v-for="option in this.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="w-50 ml-3 text-left">
+          <small class="text-muted"> <translate>Date</translate> </small>
+          <input
+            id="importdate"
+            type="date"
+            class="form-control form-control-sm"
+            placeholder="Date of import"
+            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="mt-2">
+      <div v-if="uploadState" class="input-group px-2">
+        <div :key="1" class="flex-column mr-4">
+          <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="custom-file mt-4">
+          <input
+            accept=".zip"
+            type="file"
+            @change="fileSelected"
+            class="custom-file-input"
+            id="uploadFile"
+          />
+          <label class="pointer custom-file-label" for="uploadFile">
+            {{ uploadLabel }}
+          </label>
+        </div>
+      </div>
+      <div
+        class="d-flex justify-content-between mt-2 p-2 border-top"
+        v-if="editState"
+      >
+        <button
+          :key="1"
+          @click="deleteTempData()"
+          class="btn btn-sm btn-warning"
+        >
+          Back
+        </button>
+        <span>
+          <a
+            download="meta.json"
+            :href="dataLink"
+            :class="[
+              'btn btn-sm btn-outline-info',
+              { disabled: !bottleneck || !importDate || !depthReference }
+            ]"
+          >
+            <translate>Download Meta.json</translate>
+          </a>
+          <button
+            :disabled="disableUploadButton"
+            @click="confirm"
+            class="btn btn-sm btn-info ml-2"
+            type="button"
+          >
+            <translate>Confirm</translate>
+          </button>
+        </span>
+      </div>
+      <div v-if="uploadState" class="d-flex mt-2 p-2 border-top">
+        <button :key="2" @click="back()" class="btn btn-sm btn-warning">
+          Back
+        </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";
+import { mapState } from "vuex";
+
+const IMPORTSTATE = { UPLOAD: "UPLOAD", EDIT: "EDIT" };
+
+export default {
+  data() {
+    return {
+      importState: IMPORTSTATE.UPLOAD,
+      depthReference: "",
+      bottleneck: "",
+      projection: "",
+      importDate: "",
+      uploadLabel: this.$gettext("choose .zip- file"),
+      uploadFile: null,
+      disableUpload: false,
+      token: null,
+      messages: [],
+      eMailNotification: false
+    };
+  },
+  methods: {
+    back() {
+      this.$store.commit("importschedule/setListMode");
+    },
+    initialState() {
+      this.importState = IMPORTSTATE.UPLOAD;
+      this.depthReference = "";
+      this.bottleneck = null;
+      this.projection = "";
+      this.importDate = "";
+      this.uploadLabel = this.$gettext("choose .zip- file");
+      this.uploadFile = null;
+      this.disableUpload = false;
+      this.token = null;
+      this.eMailNotification = false;
+      this.messages = [];
+    },
+    fileSelected(e) {
+      const files = e.target.files || e.dataTransfer.files;
+      if (!files) return;
+      this.uploadLabel = files[0].name;
+      this.uploadFile = files[0];
+      this.upload();
+    },
+    deleteTempData() {
+      HTTP.delete("/imports/sr-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}`
+          });
+        });
+    },
+    upload() {
+      let formData = new FormData();
+      formData.append("soundingresult", this.uploadFile);
+      if (this.eMailNotification) {
+        formData.append("send-email", this.eMailNotification);
+      }
+      HTTP.post("/imports/sr-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 = this.bottlenecks.find(
+              bn => bn.properties.objnam === 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.properties.objnam);
+      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/sr", 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.properties.objnam
+          });
+          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"]),
+    importSoundingresultsLabel() {
+      return this.$gettext("Import Soundingresults");
+    },
+    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;
+    },
+    editState() {
+      return this.importState === IMPORTSTATE.EDIT;
+    },
+    uploadState() {
+      return this.importState === IMPORTSTATE.UPLOAD;
+    },
+    Upload() {
+      return this.$gettext("Upload");
+    },
+    Confirm() {
+      return this.$gettext("Confirm");
+    },
+    dataLink() {
+      if (this.bottleneck && this.depthReference && this.import) {
+        return (
+          "data:text/json;charset=utf-8," +
+          encodeURIComponent(
+            JSON.stringify({
+              depthReference: this.depthReference,
+              bottleneck: this.bottleneck.properties.objnam,
+              date: this.importDate
+            })
+          )
+        );
+      }
+    },
+    depthReferenceOptions() {
+      if (this.bottleneck) {
+        const referenceLevels = JSON.parse(
+          this.bottleneck.properties.reference_water_levels
+        );
+        const result = Object.keys(referenceLevels);
+        if (!referenceLevels["ZPG"]) result.push("ZPG"); // ZPG should always be available
+        return result;
+      }
+      return [];
+    }
+  },
+  on: "on",
+  off: "off"
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/WaterwayProfiles.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,208 @@
+<template>
+  <div>
+    <div class="mb-2 px-2">
+      <div :key="1" class="flex-column mr-4">
+        <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="d-flex flex-row">
+        <div class="flex-column w-100">
+          <div class="flex-row text-left">
+            <small class="text-muted"> <translate>URL</translate> </small>
+          </div>
+          <div class="w-100">
+            <input
+              class="form-control form-control-sm"
+              type="url"
+              v-model="url"
+            />
+          </div>
+        </div>
+      </div>
+      <div v-if="!url" class="d-flex flex-row">
+        <small
+          ><translate class="text-danger">Please enter a URL</translate></small
+        >
+      </div>
+      <div class="d-flex flex-row">
+        <div class="flex-column mt-2 mr-3 w-50">
+          <div class="flex-row text-left">
+            <small class="text-muted">
+              <translate>Featuretype</translate>
+            </small>
+          </div>
+          <div class="w-100">
+            <input
+              class="form-control form-control-sm"
+              type="text"
+              v-model="featureType"
+            />
+          </div>
+          <div v-if="!featureType" class="d-flex flex-row">
+            <small
+              ><translate class="text-danger"
+                >Please enter a Featuretype</translate
+              ></small
+            >
+          </div>
+        </div>
+        <div class="flex-column mt-2 w-50">
+          <div class="flex-row text-left">
+            <small class="text-muted">
+              <translate>SortBy</translate>
+            </small>
+          </div>
+          <div class="w-100">
+            <input
+              class="form-control form-control-sm"
+              type="text"
+              v-model="sortBy"
+            />
+          </div>
+          <div v-if="!sortBy" class="d-flex flex-row">
+            <small
+              ><translate class="text-danger"
+                >Please enter SortBy</translate
+              ></small
+            >
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="d-flex text-left px-2">
+      <div class="mt-3 mb-3 flex-column w-100">
+        <div class="custom-file">
+          <input
+            accept=".csv"
+            type="file"
+            @change="fileSelected"
+            class="custom-file-input"
+            id="uploadFile"
+          />
+          <label class="pointer custom-file-label" for="uploadFile">
+            {{ uploadLabel }}
+          </label>
+        </div>
+      </div>
+    </div>
+    <div class="d-flex justify-content-between w-100 p-2 border-top">
+      <button :key="1" @click="back()" class="btn btn-sm btn-warning">
+        Back
+      </button>
+      <button
+        :key="2"
+        type="submit"
+        @click="submit"
+        class="btn btn-sm btn-info submit-button"
+      >
+        <translate>Submit</translate>
+      </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>
+ */
+
+import { displayError, displayInfo } from "@/lib/errors";
+import { HTTP } from "@/lib/http";
+
+export default {
+  data() {
+    return {
+      url: "https://service.d4d-portal.info/wamos/wfs/",
+      sortBy: "hydro_scamin",
+      featureType: "ws-wamos:ienc_wtwprf",
+      disableUploadButton: false,
+      uploadLabel: this.$gettext("choose file to upload"),
+      uploadFile: null,
+      eMailNotification: false
+    };
+  },
+  computed: {
+    importWaterwayProfilesLabel() {
+      return this.$gettext("Import Waterway Profiles");
+    }
+  },
+  methods: {
+    back() {
+      this.url = "https://service.d4d-portal.info/wamos/wfs/";
+      this.uploadFile = null;
+      this.uploadLabel = this.$gettext("choose file to upload");
+      this.$store.commit("importschedule/setListMode");
+    },
+    fileSelected(e) {
+      const files = e.target.files || e.dataTransfer.files;
+      if (!files) return;
+      this.uploadLabel = files[0].name;
+      this.uploadFile = files[0];
+    },
+    submit() {
+      if (!this.url || !this.featureType || !this.sortBy || !this.uploadFile)
+        return;
+      let formData = new FormData();
+      formData.append("wp", this.uploadFile);
+      formData.append("url", this.url);
+      formData.append("feature-type", this.featureType);
+      formData.append("sort-by", this.sortBy);
+      if (this.eMailNotification) {
+        formData.append("send-email", this.eMailNotification);
+      }
+      HTTP.post("/imports/wp", formData, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-Type": "multipart/form-data"
+        }
+      })
+        .then(() => {
+          displayInfo({
+            title: this.$gettext("Import"),
+            message:
+              this.uploadLabel + this.$gettext(" was successfully uploaded.")
+          });
+          this.back();
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          const messages = data.messages ? data.messages.join(", ") : "";
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${messages}`
+          });
+        });
+    }
+  },
+  on: "on",
+  off: "off"
+};
+</script>
+
+<style lang="scss" scoped></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/Waterwayarea.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,99 @@
+<template>
+  <div>
+    <div class="d-flex px-2">
+      <div class="flex-column w-100">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>URL</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="urlChanged"
+            class="url form-control form-control-sm"
+            type="url"
+            :value="url"
+          />
+        </div>
+      </div>
+    </div>
+    <div v-if="!url" class="d-flex px-2">
+      <small
+        ><translate class="text-danger">Please enter a URL</translate></small
+      >
+    </div>
+    <div class="d-flex px-2">
+      <div class="flex-column mt-2 mr-3 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>Featuretype</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="featureTypeChanged"
+            class="featuretype form-control form-control-sm"
+            type="text"
+            :value="featureType"
+          />
+        </div>
+        <div v-if="!featureType" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter a Featuretype</translate
+            ></small
+          >
+        </div>
+      </div>
+      <div class="flex-column mt-2 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>SortBy</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="sortByChanged"
+            class="sortby form-control form-control-sm"
+            type="text"
+            :value="sortBy"
+          />
+        </div>
+        <div v-if="!sortBy" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter SortBy</translate
+            ></small
+          >
+        </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>
+ */
+export default {
+  name: "waterwayarea",
+  props: ["url", "featureType", "sortBy"],
+  methods: {
+    urlChanged(e) {
+      this.$emit("urlChanged", e.target.value);
+    },
+    featureTypeChanged(e) {
+      this.$emit("featureTypeChanged", e.target.value);
+    },
+    sortByChanged(e) {
+      this.$emit("sortByChanged", e.target.value);
+    }
+  }
+};
+</script>
+
+<style></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/Waterwayaxis.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,99 @@
+<template>
+  <div>
+    <div class="d-flex px-2">
+      <div class="flex-column w-100">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>URL</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="urlChanged"
+            class="url form-control form-control-sm"
+            type="url"
+            :value="url"
+          />
+        </div>
+      </div>
+    </div>
+    <div v-if="!url" class="d-flex px-2">
+      <small
+        ><translate class="text-danger">Please enter a URL</translate></small
+      >
+    </div>
+    <div class="d-flex px-2">
+      <div class="flex-column mt-2 mr-3 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>Featuretype</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="featureTypeChanged"
+            class="featuretype form-control form-control-sm"
+            type="text"
+            :value="featureType"
+          />
+        </div>
+        <div v-if="!featureType" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter a Featuretype</translate
+            ></small
+          >
+        </div>
+      </div>
+      <div class="flex-column mt-2 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>SortBy</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="sortByChanged"
+            class="sortby form-control form-control-sm"
+            type="text"
+            :value="sortBy"
+          />
+        </div>
+        <div v-if="!sortBy" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter SortBy</translate
+            ></small
+          >
+        </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>
+ */
+export default {
+  name: "waterwayaxis",
+  props: ["url", "featureType", "sortBy"],
+  methods: {
+    urlChanged(e) {
+      this.$emit("urlChanged", e.target.value);
+    },
+    featureTypeChanged(e) {
+      this.$emit("featureTypeChanged", e.target.value);
+    },
+    sortByChanged(e) {
+      this.$emit("sortByChanged", e.target.value);
+    }
+  }
+};
+</script>
+
+<style></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importconfiguration/types/Waterwaygauges.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,126 @@
+<template>
+  <div>
+    <div class="d-flex px-2">
+      <div class="flex-column w-100">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>URL</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="urlChanged"
+            class="url form-control form-control-sm"
+            type="url"
+            :value="url"
+          />
+        </div>
+      </div>
+    </div>
+    <div v-if="!url" class="d-flex px-2">
+      <small
+        ><translate class="text-danger">Please enter a URL</translate></small
+      >
+    </div>
+    <div class="d-flex px-2">
+      <div class="flex-column mt-2 mr-3 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>Username</translate> </small>
+        </div>
+        <div class="w-100">
+          <input
+            @input="usernameChanged"
+            class="username form-control form-control-sm"
+            type="text"
+            :value="username"
+          />
+        </div>
+        <div v-if="!username" class="d-flex flex-row">
+          <small
+            ><translate class="text-danger"
+              >Please enter a Username</translate
+            ></small
+          >
+        </div>
+      </div>
+      <div class="flex-column mt-2 w-50">
+        <div class="flex-row text-left">
+          <small class="text-muted"> <translate>Password</translate> </small>
+        </div>
+        <div class="w-100 d-flex flex-row">
+          <input
+            @input="passwordChanged"
+            class="password form-control form-control-sm"
+            :type="showPassword"
+            :value="password"
+          />
+          <span
+            class="input-group-text ml-2"
+            @click="passwordVisible = !passwordVisible"
+          >
+            <font-awesome-icon :icon="passwordVisible ? 'eye-slash' : 'eye'" />
+          </span>
+        </div>
+        <div
+          v-if="!password && !this.currentSchedule.id"
+          class="d-flex flex-row"
+        >
+          <small
+            ><translate class="text-danger"
+              >Please enter a Password</translate
+            ></small
+          >
+        </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 { mapState } from "vuex";
+
+export default {
+  name: "waterwaygauges",
+  props: ["username", "password", "url"],
+  data() {
+    return {
+      passwordVisible: false
+    };
+  },
+  computed: {
+    ...mapState("importschedule", [
+      "importScheduleDetailVisible",
+      "currentSchedule"
+    ]),
+    showPassword() {
+      if (this.passwordVisible) return "text";
+      return "password";
+    }
+  },
+  methods: {
+    urlChanged(e) {
+      this.$emit("urlChanged", e.target.value);
+    },
+    usernameChanged(e) {
+      this.$emit("usernameChanged", e.target.value);
+    },
+    passwordChanged(e) {
+      this.$emit("passwordChanged", e.target.value);
+    }
+  }
+};
+</script>
+
+<style></style>
--- a/client/src/components/importoverview/AdditionalDetail.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-<template>
-  <div>
-    <FairwayDimensionDetail
-      :entry="entry"
-      :details="details"
-      v-if="isFairwayDimension"
-    ></FairwayDimensionDetail>
-    <ApprovedGaugeMeasurementDetail
-      :entry="entry"
-      :details="details"
-      v-if="isApprovedGaugeMeasurement"
-    ></ApprovedGaugeMeasurementDetail>
-    <BottleneckDetail
-      :details="details"
-      :entry="entry"
-      v-if="isBottleneck"
-    ></BottleneckDetail>
-  </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: "additionaldetail",
-  props: ["entry"],
-  components: {
-    BottleneckDetail: () => import("./BottleneckDetail.vue"),
-    ApprovedGaugeMeasurementDetail: () =>
-      import("./ApprovedGaugeMeasurementDetail.vue"),
-    FairwayDimensionDetail: () => import("./FairwayDimension.vue")
-  },
-  computed: {
-    ...mapState("imports", ["showLogs", "details"]),
-    kind() {
-      return this.entry.kind.toUpperCase();
-    },
-    isFairwayDimension() {
-      return this.kind === "FD";
-    },
-    isApprovedGaugeMeasurement() {
-      return this.kind === "AGM";
-    },
-    isBottleneck() {
-      return this.kind === "BN" || this.kind === "UBN";
-    }
-  }
-};
-</script>
-
-<style lang="scss" scoped></style>
--- a/client/src/components/importoverview/AdditionalLog.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/importoverview/AdditionalLog.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,18 +1,12 @@
 <template>
   <div
     :class="[
-      'additionallog',
-      'd-flex',
-      'flex-column',
-      'text-left',
-      {
-        full: showAdditional === $options.NODETAILS,
-        split: showAdditional !== $options.NODETAILS
-      }
+      'additionallog d-flex flex-column text-left',
+      { split: showAdditional }
     ]"
   >
     <div
-      class="d-flex flex-row"
+      class="d-flex flex-row px-2 border-top"
       v-for="(line, index) in details.entries"
       :key="index"
     >
@@ -36,7 +30,7 @@
             'font-weight-bold': /warn|error/.test(line.kind)
           }
         ]"
-        >{{ line.time }}</span
+        >{{ line.time | dateTime }}</span
       >
       <span
         :class="[
@@ -53,6 +47,30 @@
   </div>
 </template>
 
+<style lang="sass" scoped>
+.additionallog
+  overflow-y: auto
+  &.split
+    max-height: 35vh
+
+  > div
+    &:not(:first-child)
+      border-top-style: dashed !important
+
+    &:hover
+      background-color: #fcfcfc
+
+  .kind
+    width: 9%
+
+  .time
+    width: 26%
+
+  .message
+    width: 65%
+    word-wrap: break-word
+</style>
+
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -73,32 +91,6 @@
   name: "additionallogs",
   computed: {
     ...mapState("imports", ["showAdditional", "details"])
-  },
-  NODETAILS: -1
+  }
 };
 </script>
-
-<style lang="scss" scoped>
-.additionallog {
-  overflow-y: auto;
-}
-
-.split {
-  max-height: 35vh;
-}
-
-.full {
-  max-height: 70vh;
-}
-
-.kind {
-  width: 9%;
-}
-.time {
-  width: 26%;
-}
-.message {
-  width: 65%;
-  word-wrap: break-word;
-}
-</style>
--- a/client/src/components/importoverview/ApprovedGaugeMeasurementDetail.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/importoverview/ApprovedGaugeMeasurementDetail.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -2,89 +2,58 @@
   <div
     :class="{
       diffs: true,
-      full: showLogs === $options.NODETAILS,
-      split: showLogs !== $options.NODETAILS
+      full: !showLogs,
+      split: showLogs
     }"
   >
     <div v-for="(result, index) in details.summary" :key="index">
-      <div class="pl-2 d-flex flex-row">
+      <div class="px-2 d-flex justify-content-between">
+        <div class="d-flex">
+          <div @click="toggleDiff(index)" class="my-auto text-left">
+            <UISpinnerButton
+              :state="showDiff === index"
+              :icons="['angle-right', 'angle-down']"
+              classes="text-info"
+            />
+          </div>
+          <div>
+            {{ result["fk-gauge-id"] }}
+            <sup v-if="isNew(result)" class="text-success">
+              (<translate>New</translate>)
+            </sup>
+          </div>
+        </div>
+        <div>{{ result["measure-date"] | dateTime }}</div>
+      </div>
+      <div v-if="showDiff === index" class="compare-table">
+        <div class="row no-gutters px-4 text-left font-weight-bold">
+          <div :class="isNew(result) ? 'col-6' : 'col-4'">
+            <translate>Value</translate>
+          </div>
+          <div v-if="isOld(result)" class="col-4">
+            <translate>Old</translate>
+          </div>
+          <div :class="isNew(result) ? 'col-6' : 'col-4'">
+            <translate>New</translate>
+          </div>
+        </div>
         <div
-          @click="toggleDiff(index)"
-          class="small mt-auto mb-auto text-info text-left"
-        >
-          <font-awesome-icon
-            class="pointer"
-            v-if="showDiff == index"
-            icon="angle-down"
-            fixed-width
-          ></font-awesome-icon>
-          <font-awesome-icon
-            class="pointer"
-            v-if="showDiff != index"
-            icon="angle-right"
-            fixed-width
-          ></font-awesome-icon>
-        </div>
-        <span v-if="result.versions.length == 1" class="agmcode text-left"
-          ><div>
-            {{ result["fk-gauge-id"] }} <translate>( New )</translate>
-          </div></span
+          class="row no-gutters px-4 text-left"
+          v-for="(entry, index) in Object.keys(result.versions[0])"
+          :key="index"
+          v-if="isNew(result) || isDifferent(result, entry)"
         >
-        <span v-if="result.versions.length == 2" class="agmcode text-left"
-          ><div>{{ result["fk-gauge-id"] }}</div></span
-        >
-        <span class="text-left"
-          ><div>{{ result["measure-date"] | dateTime }}</div></span
-        >
-      </div>
-      <div v-if="showDiff == index" class="pl-3 d-flex flex-row">
-        <div class="w-100">
-          <div class="d-flex flex-row pl-3 text-left">
-            <div class="header border-bottom agmdetailskeys">
-              <small><translate>Value</translate></small>
-            </div>
-            <div
-              v-if="result.versions.length == 2"
-              class="header border-bottom agmdetailsvalues"
-            >
-              <small><translate>Old</translate></small>
-            </div>
-            <div class="header border-bottom agmdetailsvalues">
-              <small><translate>New</translate></small>
-            </div>
+          <div :class="isNew(result) ? 'col-6' : 'col-4'">
+            {{ entry }}
+          </div>
+          <div :class="isNew(result) ? 'col-6' : 'col-4'">
+            {{ result.versions[0][entry] }}
           </div>
           <div
-            class="d-flex flex-row pl-3 text-left"
-            v-for="(entry, index) in Object.keys(result.versions[0])"
-            :key="index"
+            v-if="isOld(result) && isDifferent(result, entry)"
+            :class="isNew(result) ? 'col-6' : 'col-4'"
           >
-            <div
-              v-if="
-                result.versions.length == 1 ||
-                  result.versions[0][entry] != result.versions[1][entry]
-              "
-              class="agmdetailskeys"
-            >
-              <small>{{ entry }}</small>
-            </div>
-            <div
-              v-if="
-                result.versions.length == 1 ||
-                  result.versions[0][entry] != result.versions[1][entry]
-              "
-              class="agmdetailsvalues"
-            >
-              <small>{{ result.versions[0][entry] }}</small>
-            </div>
-            <div
-              v-if="
-                result.versions.length == 2 &&
-                  result.versions[0][entry] != result.versions[1][entry]
-              "
-              class="agmdetailsvalues"
-            >
-              <small>{{ result.versions[1][entry] }}</small>
-            </div>
+            {{ result.versions[1][entry] }}
           </div>
         </div>
       </div>
@@ -92,6 +61,37 @@
   </div>
 </template>
 
+<style lang="sass" scoped>
+.diffs
+  width: 100%
+  overflow-y: auto
+  > div
+    border-top: dashed 1px #dee2e6
+    &:first-child
+      border-top: none
+    .compare-table
+      position: relative
+      overflow: hidden
+      &::after
+        content: ''
+        position: absolute
+        top: 0
+        right: -5px
+        bottom: 0
+        left: -5px
+        box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.4)
+      > div
+        font-size: 0.7rem
+        &:nth-child(odd)
+          background-color: #f8f9fa
+
+.split
+  max-height: 35vh
+
+.full
+  max-height: 70vh
+</style>
+
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -108,14 +108,10 @@
  */
 import { mapState } from "vuex";
 
-const NODIFF = -1;
-
 export default {
-  name: "agmdetails",
-  props: ["entry"],
   data() {
     return {
-      showDiff: NODIFF
+      showDiff: 0 // open first item by default
     };
   },
   computed: {
@@ -123,41 +119,24 @@
   },
   methods: {
     toggleDiff(number) {
-      if (this.showDiff !== number || this.showDiff == NODIFF) {
+      if (this.showDiff !== number) {
         this.showDiff = number;
       } else {
-        this.showDiff = NODIFF;
+        this.showDiff = false;
       }
+    },
+    isNew(result) {
+      return result && result.versions && result.versions.length === 1;
+    },
+    isOld(result) {
+      return !this.isNew(result);
+    },
+    isDifferent(result, entry) {
+      return (
+        this.isOld(result) &&
+        result.versions[0][entry] != result.versions[1][entry]
+      );
     }
-  },
-  NODETAILS: -1
+  }
 };
 </script>
-
-<style lang="scss" scoped>
-.diffs {
-  width: 615px;
-  max-height: 20vh;
-  overflow-y: auto;
-}
-
-.agmcode {
-  width: 35%;
-}
-
-.agmdetailskeys {
-  width: 33%;
-}
-
-.agmdetailsvalues {
-  width: 33%;
-}
-
-.split {
-  max-height: 35vh;
-}
-
-.full {
-  max-height: 70vh;
-}
-</style>
--- a/client/src/components/importoverview/BottleneckDetail.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/importoverview/BottleneckDetail.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -2,58 +2,72 @@
   <div
     :class="{
       bottleneckdetails: true,
-      full: showLogs === $options.NODETAILS,
-      split: showLogs !== $options.NODETAILS
+      full: !showLogs,
+      split: showLogs
     }"
   >
-    <div
-      v-for="(bottleneck, index) in bottlenecks"
-      :key="index"
-      class="d-flex flex-row"
-    >
-      <div class="d-flex flex-column">
-        <div class="d-flex flex-row">
-          <div
-            @click="showBottleneckDetails(index)"
-            class="mt-auto mb-auto text-info text-left"
+    <div v-for="(bottleneck, index) in bottlenecks" :key="index">
+      <div class="d-flex pl-2">
+        <div
+          @click="showBottleneckDetails(index)"
+          class="mt-auto mb-auto text-info text-left"
+        >
+          <UISpinnerButton
+            :state="showBottleneckDetail === index"
+            :icons="['angle-right', 'angle-down']"
+            class="text-info"
+          />
+        </div>
+        <a @click="moveToBottleneck(index)" href="#">
+          {{ bottleneck.properties.objnam }}
+        </a>
+      </div>
+
+      <div class="d-flex properties" v-if="showBottleneckDetail === index">
+        <table class="w-100">
+          <tr
+            v-for="(info, index) in Object.keys(bottleneck.properties)"
+            :key="index"
           >
-            <font-awesome-icon
-              class="pointer"
-              v-if="showBottleneckDetail === index"
-              icon="angle-down"
-              fixed-width
-            ></font-awesome-icon>
-            <font-awesome-icon
-              class="pointer"
-              v-if="!(showBottleneckDetail === index)"
-              icon="angle-right"
-              fixed-width
-            ></font-awesome-icon>
-          </div>
-          <a @click="moveToBottleneck(index)" class="" href="#">{{
-            bottleneck.properties.objnam
-          }}</a>
-        </div>
-
-        <div class="ml-3 d-flex flex-row" v-if="showBottleneckDetail === index">
-          <table>
-            <tr
-              v-for="(info, index) in Object.keys(bottleneck.properties)"
-              :key="index"
-              class="mr-1 condensed  text-muted"
-            >
-              <td class="text-left">{{ info }}</td>
-              <td class="pl-3 text-left">
-                {{ bottleneck.properties[info] }}
-              </td>
-            </tr>
-          </table>
-        </div>
+            <td class="pl-4">{{ info }}</td>
+            <td>
+              {{ bottleneck.properties[info] }}
+            </td>
+          </tr>
+        </table>
       </div>
     </div>
   </div>
 </template>
 
+<style lang="sass" scoped>
+.bottleneckdetails
+  width: 100%
+  overflow-y: auto
+  > div
+    border-top: dashed 1px #dee2e6
+    &:first-child
+      border-top: none
+    .properties
+      position: relative
+      overflow: hidden
+      &::after
+        content: ''
+        position: absolute
+        top: 0
+        right: -5px
+        bottom: 0
+        left: -5px
+        box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.4)
+      tr
+        font-size: 0.7rem
+        &:nth-child(odd)
+          background-color: #f8f9fa
+
+.split
+  max-height: 35vh
+</style>
+
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -69,29 +83,25 @@
  * Thomas Junk <thomas.junk@intevation.de>
  */
 
-import { LAYERS } from "@/store/map.js";
 import { HTTP } from "@/lib/http";
-import { WFS } from "ol/format.js";
-import { or as orFilter, equalTo as equalToFilter } from "ol/format/filter.js";
-import { displayError } from "@/lib/errors.js";
-import { mapState } from "vuex";
-
-const NO_BOTTLENECK = -1;
+import { WFS } from "ol/format";
+import { or as orFilter, equalTo as equalToFilter } from "ol/format/filter";
+import { displayError } from "@/lib/errors";
+import { mapState, mapGetters } from "vuex";
 
 export default {
-  name: "bottleneckdetails",
-  props: ["entry"],
   data() {
     return {
       bottlenecks: [],
-      showBottleneckDetail: NO_BOTTLENECK
+      showBottleneckDetail: null
     };
   },
   mounted() {
     this.loadBottlenecks();
   },
   computed: {
-    ...mapState("imports", ["showLogs", "details"])
+    ...mapState("imports", ["showLogs", "details"]),
+    ...mapGetters("map", ["openLayersMap"])
   },
   methods: {
     loadBottlenecks() {
@@ -137,39 +147,22 @@
         });
     },
     moveToBottleneck(index) {
-      this.$store.commit("map/setLayerVisible", LAYERS.BOTTLENECKS);
-      this.moveToExtent(this.bottlenecks[index]);
-    },
-    moveToExtent(feature) {
-      this.$store.commit("map/moveToExtent", {
-        feature: feature,
+      this.openLayersMap()
+        .getLayer("BOTTLENECKS")
+        .setVisible(true);
+      this.$store.dispatch("map/moveToFeauture", {
+        feature: this.bottlenecks[index],
         zoom: 17,
         preventZoomOut: true
       });
     },
     showBottleneckDetails(index) {
-      if (index == this.showBottleneckDetail) {
-        this.showBottleneckDetail = NO_BOTTLENECK;
-        return;
+      if (index === this.showBottleneckDetail) {
+        this.showBottleneckDetail = null;
+      } else {
+        this.showBottleneckDetail = index;
       }
-      this.showBottleneckDetail = index;
     }
-  },
-  NODETAILS: -1
+  }
 };
 </script>
-
-<style lang="scss" scoped>
-.bottleneckdetails {
-  width: 615px;
-  overflow-y: auto;
-}
-
-.split {
-  max-height: 35vh;
-}
-
-.full {
-  max-height: 70vh;
-}
-</style>
--- a/client/src/components/importoverview/FairwayDimension.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-<template>
-  <div>Fairwaydimension</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: "fairwaydimensiondetails"
-};
-</script>
-
-<style></style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importoverview/FairwayDimensionDetail.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,20 @@
+<template>
+  <div>Fairwaydimension</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 {};
+</script>
--- a/client/src/components/importoverview/Filters.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/importoverview/Filters.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,18 +1,33 @@
 <template>
   <div>
-    <button @click="setFilter('pending')" :class="pendingStyle">
+    <button
+      @click="setFilter('pending')"
+      :class="'mr-1 btn btn-xs btn-' + (this.pending ? 'secondary' : 'light')"
+    >
       <translate>pending</translate>
     </button>
-    <button @click="setFilter('failed')" :class="failedStyle">
+    <button
+      @click="setFilter('failed')"
+      :class="'mr-1 btn btn-xs btn-' + (this.failed ? 'secondary' : 'light')"
+    >
       <translate>failed</translate>
     </button>
-    <button @click="setFilter('accepted')" :class="acceptedStyle">
+    <button
+      @click="setFilter('accepted')"
+      :class="'mr-1 btn btn-xs btn-' + (this.accepted ? 'secondary' : 'light')"
+    >
       <translate>accepted</translate>
     </button>
-    <button @click="setFilter('declined')" :class="declinedStyle">
+    <button
+      @click="setFilter('declined')"
+      :class="'mr-1 btn btn-xs btn-' + (this.declined ? 'secondary' : 'light')"
+    >
       <translate>declined</translate>
     </button>
-    <button @click="setFilter('warning')" :class="warningStyle">
+    <button
+      @click="setFilter('warning')"
+      :class="'btn btn-xs btn-' + (this.warning ? 'secondary' : 'light')"
+    >
       <translate>warning</translate>
     </button>
   </div>
@@ -49,53 +64,7 @@
       "accepted",
       "warning",
       "declined"
-    ]),
-    pendingStyle() {
-      return {
-        btn: true,
-        "btn-sm": true,
-        "btn-light": !this.pending,
-        "btn-secondary": this.pending
-      };
-    },
-    failedStyle() {
-      return {
-        "ml-2": true,
-        btn: true,
-        "btn-sm": true,
-        "btn-light": !this.failed,
-        "btn-secondary": this.failed
-      };
-    },
-    declinedStyle() {
-      return {
-        "ml-2": true,
-        btn: true,
-        "btn-sm": true,
-        "btn-light": !this.declined,
-        "btn-secondary": this.declined
-      };
-    },
-    acceptedStyle() {
-      return {
-        "ml-2": true,
-        btn: true,
-        "btn-sm": true,
-        "btn-light": !this.accepted,
-        "btn-secondary": this.accepted
-      };
-    },
-    warningStyle() {
-      return {
-        "ml-2": true,
-        btn: true,
-        "btn-sm": true,
-        "btn-light": !this.warning,
-        "btn-secondary": this.warning
-      };
-    }
+    ])
   }
 };
 </script>
-
-<style lang="scss" scoped></style>
--- a/client/src/components/importoverview/ImportOverview.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/importoverview/ImportOverview.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -4,90 +4,82 @@
       icon="clipboard-check"
       :title="importReviewLabel"
       :closeCallback="$parent.close"
-      :actions="[{ callback: loadLogs, icon: 'redo' }]"
+      :actions="[{ callback: loadUpdatedLogs, icon: 'sync' }]"
     />
     <div class="position-relative">
-      <transition name="fade">
-        <div class="loading" v-if="loading">
-          <font-awesome-icon icon="spinner" spin />
-        </div>
-      </transition>
-      <div class="p-2 mb-1 d-flex flex-row flex-fill justify-content-between">
+      <UISpinnerOverlay v-if="loading" />
+      <div class="border-bottom p-2 d-flex justify-content-between">
         <Filters></Filters>
-        <div>
-          <button
-            class="btn btn-sm btn-info"
-            :disabled="!reviewed.length"
-            @click="save"
-          >
-            <translate>Commit</translate> {{ reviewed.length }}
-          </button>
-        </div>
+        <button
+          class="btn btn-xs btn-info"
+          :disabled="!reviewed.length"
+          @click="save"
+        >
+          <translate>Commit</translate> {{ reviewed.length }}
+        </button>
       </div>
       <div
-        class="ml-2 mr-2 mb-2 datefilter d-flex flex-row justify-content-between"
+        class="p-2 d-flex align-items-center justify-content-between border-bottom"
       >
-        <div class="mr-3 my-auto pointer">
-          <button
-            :disabled="!this.prev"
-            @click="earlier"
-            class="btn btn-sm btn-outline-light text-dark"
+        <button
+          :disabled="!this.prev"
+          @click="earlier"
+          class="btn btn-xs btn-outline-secondary"
+        >
+          <font-awesome-icon icon="angle-left" fixed-width />
+          <translate>Earlier</translate>
+        </button>
+        <div class="d-flex align-items-center small">
+          {{ interval[0] | dateTime(selectedInterval !== $options.LAST_HOUR) }}
+          <template v-if="selectedInterval !== $options.TODAY">
+            <span class="mx-2">&ndash;</span>
+            {{
+              interval[1] | dateTime(selectedInterval !== $options.LAST_HOUR)
+            }}
+          </template>
+          <select
+            style="width: 75px; height: 24px"
+            class="form-control form-control-sm small ml-2"
+            v-model="selectedInterval"
           >
-            <translate>Earlier</translate>
-            <font-awesome-icon class="ml-2" icon="angle-left" />
-          </button>
+            <option :value="$options.LAST_HOUR">
+              <translate>Hour</translate>
+            </option>
+            <option :value="$options.TODAY"><translate>Day</translate></option>
+            <option :value="$options.LAST_7_DAYS">
+              <translate>7 days</translate>
+            </option>
+            <option :value="$options.LAST_30_DAYS">
+              <translate>30 Days</translate>
+            </option>
+          </select>
         </div>
-        <div class="selected-interval my-auto">
-          <span class="date">{{ interval[0] | dateTime }}</span>
-          <span class="ml-3 mr-3">-</span>
-          <span class="date">{{ interval[1] | dateTime }}</span>
-        </div>
-        <div class="ml-3 my-auto pointer">
+        <div class="btn-group">
           <button
             :disabled="!this.next"
             @click="later"
-            class="btn btn-sm btn-outline-light text-dark"
-          >
-            <font-awesome-icon class="mr-2" icon="angle-right" /><translate
-              >Later</translate
-            >
-          </button>
-        </div>
-        <div class="d-flex flex-row">
-          <select
-            class="my-auto btn btn-outline-light text-dark form-control interval-select"
-            v-model="selectedInterval"
+            class="btn btn-xs btn-outline-secondary"
           >
-            <option
-              :selected="selectedInterval === $options.LAST_HOUR"
-              :value="$options.LAST_HOUR"
-              ><translate>Hour</translate></option
-            >
-            <option
-              :selected="selectedInterval === $options.TODAY"
-              :value="$options.TODAY"
-              ><translate>Day</translate></option
-            >
-            <option
-              :selected="selectedInterval === $options.LAST_7_DAYS"
-              :value="$options.LAST_7_DAYS"
-              ><translate>7 days</translate></option
-            >
-            <option
-              :selected="selectedInterval === $options.LAST_30_DAYS"
-              :value="$options.LAST_30_DAYS"
-              ><translate>30 Days</translate></option
-            >
-          </select>
+            <translate>Later</translate>
+            <font-awesome-icon icon="angle-right" fixed-width />
+          </button>
+          <button
+            :disabled="!this.next"
+            @click="now"
+            class="btn btn-xs btn-outline-secondary"
+          >
+            <font-awesome-icon icon="angle-double-right" fixed-width />
+          </button>
         </div>
       </div>
       <UITableHeader
         :columns="[
-          { id: 'id', title: `${idLabel}`, width: '79px' },
+          { id: 'id', title: `${idLabel}`, width: '75px' },
           { id: 'kind', title: `${kindLabel}`, width: '53px' },
           { id: 'enqueued', title: `${enqueuedLabel}`, width: '138px' },
-          { id: 'user', title: `${userLabel}`, width: '105px' },
-          { id: 'signer', title: `${signerLabel}`, width: '105px' },
+          { id: 'user', title: `${ownerLabel}`, width: '80px' },
+          { id: 'country', title: `${countryLabel}`, width: '55px' },
+          { id: 'signer', title: `${signerLabel}`, width: '80px' },
           { id: 'state', title: `${statusLabel}`, width: '72px' },
           { id: 'warnings', icon: 'exclamation-triangle', width: '44px' }
         ]"
@@ -99,15 +91,25 @@
      -->
       <UITableBody
         :data="filteredImports() | sortTable(sortColumn, sortDirection)"
-        maxHeight="80vh"
-        v-slot="{ item: entry }"
+        :isActive="item => item.id === this.show"
+        maxHeight="70vh"
       >
-        <LogEntry :entry="entry"></LogEntry>
+        <template v-slot:row="{ item: entry }">
+          <LogEntry :entry="entry"></LogEntry>
+        </template>
+        <template v-slot:expand="{ item: entry }">
+          <LogDetail :entry="entry"></LogDetail>
+        </template>
       </UITableBody>
     </div>
   </div>
 </template>
 
+<style lang="sass" scoped>
+.spinner-overlay
+  top: 110px
+</style>
+
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -128,8 +130,7 @@
 import { displayError, displayInfo } from "@/lib/errors";
 import { STATES } from "@/store/imports";
 import { sortTable } from "@/lib/mixins";
-import { HTTP } from "@/lib/http.js";
-
+import { HTTP } from "@/lib/http";
 import {
   startOfDay,
   startOfHour,
@@ -141,22 +142,22 @@
 } from "date-fns";
 
 export default {
-  name: "importoverview",
+  components: {
+    Filters: () => import("./Filters"),
+    LogEntry: () => import("./LogEntry"),
+    LogDetail: () => import("./LogDetail")
+  },
   mixins: [sortTable],
-  components: {
-    Filters: () => import("./Filters.vue"),
-    LogEntry: () => import("./LogEntry.vue")
-  },
+  LAST_HOUR: "lasthour",
+  TODAY: "today",
+  LAST_7_DAYS: "lastsevendays",
+  LAST_30_DAYS: "lastthirtydays",
   data() {
     return {
       loading: false,
       selectedInterval: this.$options.LAST_HOUR
     };
   },
-  LAST_HOUR: "lasthour",
-  TODAY: "today",
-  LAST_7_DAYS: "lastsevendays",
-  LAST_30_DAYS: "lastthirtydays",
   computed: {
     ...mapState("application", ["searchQuery"]),
     ...mapState("imports", [
@@ -169,6 +170,9 @@
       "next"
     ]),
     ...mapGetters("imports", ["filters"]),
+    countryLabel() {
+      return this.$gettext("Country");
+    },
     importReviewLabel() {
       return this.$gettext("Import review");
     },
@@ -181,8 +185,8 @@
     enqueuedLabel() {
       return this.$gettext("Enqueued");
     },
-    userLabel() {
-      return this.$gettext("User");
+    ownerLabel() {
+      return this.$gettext("Owner");
     },
     signerLabel() {
       return this.$gettext("Signer");
@@ -194,7 +198,68 @@
       return [this.startDate, this.endDate];
     }
   },
+  watch: {
+    $route() {
+      const { id } = this.$route.params;
+      if (id) this.showSingleRessource(id);
+    },
+    selectedInterval() {
+      this.loadUpdatedLogs();
+    },
+    imports() {
+      if (this.imports.length == 0) {
+        if (this.next) {
+          const [start, end] = this.determineInterval(this.next);
+          this.$store.commit("imports/setStartDate", start);
+          this.$store.commit("imports/setEndDate", end);
+          this.loadLogs();
+        } else if (this.prev) {
+          const [start, end] = this.determineInterval(this.prev);
+          this.$store.commit("imports/setStartDate", start);
+          this.$store.commit("imports/setEndDate", end);
+          this.loadLogs();
+        }
+      }
+    },
+    filters() {
+      this.loadLogs();
+    }
+  },
   methods: {
+    showSingleRessource(id) {
+      id = id * 1;
+      this.loadDetails(id)
+        .then(response => {
+          this.$store.commit("imports/setCurrentDetails", response.data);
+          const { enqueued } = response.data;
+          this.$store.commit("imports/setStartDate", startOfHour(enqueued));
+          this.$store.commit("imports/setEndDate", endOfHour(enqueued));
+          this.$store.commit("imports/showDetailsFor", id);
+          this.loadLogs();
+        })
+        .catch(error => {
+          this.loading = false;
+          this.$store.commit("imports/setCurrentDetails", {});
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    },
+    loadDetails(id) {
+      return new Promise((resolve, reject) => {
+        HTTP.get("/imports/" + id, {
+          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+        })
+          .then(response => {
+            resolve(response);
+          })
+          .catch(error => {
+            reject(error);
+          });
+      });
+    },
     determineInterval(pointInTime) {
       let start, end;
       switch (this.selectedInterval) {
@@ -208,11 +273,11 @@
           break;
         case this.$options.LAST_7_DAYS:
           start = startOfDay(pointInTime);
-          end = endOfDay(addDays(7, start));
+          end = endOfDay(addDays(start, 7));
           break;
         case this.$options.LAST_30_DAYS:
           start = startOfDay(pointInTime);
-          end = endOfDay(addDays(30, start));
+          end = endOfDay(addDays(start, 30));
           break;
       }
       return [start, end];
@@ -231,9 +296,44 @@
       this.$store.commit("imports/setEndDate", end);
       this.loadLogs();
     },
+    now() {
+      if (!this.next) return;
+      const [start, end] = this.determineInterval(new Date());
+      this.$store.commit("imports/setStartDate", start);
+      this.$store.commit("imports/setEndDate", end);
+      this.loadLogs();
+    },
     filteredImports() {
       return this.imports;
     },
+    loadUpdatedLogs() {
+      const now = new Date();
+      switch (this.selectedInterval) {
+        case this.$options.LAST_HOUR:
+          this.$store.commit("imports/setStartDate", startOfHour(now));
+          this.$store.commit("imports/setEndDate", now);
+          break;
+        case this.$options.TODAY:
+          this.$store.commit("imports/setStartDate", startOfDay(now));
+          this.$store.commit("imports/setEndDate", now);
+          break;
+        case this.$options.LAST_7_DAYS:
+          this.$store.commit(
+            "imports/setStartDate",
+            subDays(startOfDay(now), 7)
+          );
+          this.$store.commit("imports/setEndDate", now);
+          break;
+        case this.$options.LAST_30_DAYS:
+          this.$store.commit(
+            "imports/setStartDate",
+            subDays(startOfDay(now), 30)
+          );
+          this.$store.commit("imports/setEndDate", now);
+          break;
+      }
+      this.loadLogs();
+    },
     loadLogs() {
       this.loading = true;
       this.$store
@@ -244,15 +344,15 @@
           query: this.searchQuery
         })
         .then(() => {
-          if (this.show != -1) {
-            HTTP.get("/imports/" + this.show, {
-              headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-            })
+          if (this.show) {
+            this.loadDetails(this.show)
               .then(response => {
                 this.$store.commit("imports/setCurrentDetails", response.data);
                 this.loading = false;
               })
               .catch(error => {
+                this.loading = false;
+                this.$store.commit("imports/setCurrentDetails", {});
                 const { status, data } = error.response;
                 displayError({
                   title: this.$gettext("Backend Error"),
@@ -265,6 +365,7 @@
         })
         .catch(error => {
           const { status, data } = error.response;
+          this.loading = false;
           displayError({
             title: this.$gettext("Backend Error"),
             message: `${status}: ${data.message || data}`
@@ -307,6 +408,7 @@
               .then(response => {
                 this.loadLogs();
                 this.$store.commit("imports/setReviewed", []);
+                this.$store.dispatch("map/refreshLayers");
                 const messages = response.data
                   .map(x => {
                     if (x.message) return x.message;
@@ -338,68 +440,21 @@
       });
     }
   },
-  watch: {
-    selectedInterval() {
-      const now = new Date();
-      switch (this.selectedInterval) {
-        case this.$options.LAST_HOUR:
-          this.$store.commit("imports/setStartDate", startOfHour(now));
-          this.$store.commit("imports/setEndDate", now);
-          break;
-        case this.$options.TODAY:
-          this.$store.commit("imports/setStartDate", startOfDay(now));
-          this.$store.commit("imports/setEndDate", now);
-          break;
-        case this.$options.LAST_7_DAYS:
-          this.$store.commit(
-            "imports/setStartDate",
-            subDays(startOfDay(now), 7)
-          );
-          this.$store.commit("imports/setEndDate", now);
-          break;
-        case this.$options.LAST_30_DAYS:
-          this.$store.commit(
-            "imports/setStartDate",
-            subDays(startOfDay(now), 30)
-          );
-          this.$store.commit("imports/setEndDate", now);
-          break;
-      }
-      this.loadLogs();
-    },
-    imports() {
-      if (this.imports.length == 0) {
-        if (this.next) {
-          const [start, end] = this.determineInterval(this.next);
-          this.$store.commit("imports/setStartDate", start);
-          this.$store.commit("imports/setEndDate", end);
-          this.loadLogs();
-        } else if (this.prev) {
-          const [start, end] = this.determineInterval(this.prev);
-          this.$store.commit("imports/setStartDate", start);
-          this.$store.commit("imports/setEndDate", end);
-          this.loadLogs();
-        }
-      }
-    },
-    filters() {
+  mounted() {
+    this.$store.dispatch("usermanagement/loadUsers").catch(error => {
+      const { status, data } = error.response;
+      displayError({
+        title: this.$gettext("Backend Error"),
+        message: `${status}: ${data.message || data}`
+      });
+    });
+    const { id } = this.$route.params;
+    if (id) {
+      this.showSingleRessource(id);
+    } else {
+      this.$store.commit("application/searchQuery", "");
       this.loadLogs();
     }
-  },
-  mounted() {
-    this.$store.commit("application/searchQuery", "");
-    this.loadLogs();
   }
 };
 </script>
-
-<style lang="scss" scoped>
-.date {
-  font-stretch: condensed;
-}
-.interval-select {
-  padding: 0px;
-  margin: 0px;
-  font-size: 80%;
-}
-</style>
--- a/client/src/components/importoverview/LogDetail.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/importoverview/LogDetail.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,78 +1,54 @@
 <template>
-  <div class="border-top">
+  <div>
     <div
-      class="d-flex fex-row"
+      class="d-flex border-bottom"
       style="padding-left: 3px;"
-      v-if="hasAdditionalInfo || isStretch || isSoundingResult"
+      v-if="hasAdditionalInfo || isST || isSEC || isSR"
     >
       <div v-if="hasAdditionalInfo">
-        <font-awesome-icon
-          v-if="entry.id === showAdditional"
-          @click="toggleAdditionalInfo"
-          class="my-auto mr-1 text-info pointer"
-          icon="angle-down"
-          fixed-width
-        ></font-awesome-icon>
-        <font-awesome-icon
-          v-if="entry.id !== showAdditional"
+        <UISpinnerButton
           @click="toggleAdditionalInfo"
-          class="my-auto mr-1 text-info pointer"
-          icon="angle-right"
-          fixed-width
-        ></font-awesome-icon>
+          :state="entry.id === showAdditional"
+          :icons="['angle-right', 'angle-down']"
+          class="text-info d-inline-block"
+        />
         <span class="text-info"><translate>Additional Info</translate></span>
+        <span class="text-info" v-if="isAGM && details.summary">
+          ({{ details.summary.length }})
+        </span>
         <span
-          class="text-info"
-          v-if="isApprovedGaugeMeasurement && details.summary"
-        >
-          ({{ details.summary.length }})</span
-        >
-        <span
-          v-if="isBottleneck && details.summary && details.summary.bottlenecks"
+          v-if="isBN && details.summary && details.summary.bottlenecks"
           class="text-info text-left"
         >
-          ({{ details.summary.bottlenecks.length }})</span
-        >
-        <span class="text-left" v-if="isFairwayDimension"
-          >{{ details.summary["source-organization"] }} (LOS:
-          {{ details.summary.los }})</span
-        >
+          ({{ details.summary.bottlenecks.length }})
+        </span>
+        <span class="text-left" v-if="isFD">
+          {{ details.summary["source-organization"] }}
+          (LOS: {{ details.summary.los }})
+        </span>
       </div>
-      <StretchDetail
-        v-if="isStretch && isPending"
-        :entry="entry"
-      ></StretchDetail>
-      <SoundingResultDetail
-        :entry="entry"
-        v-if="isSoundingResult && isPending"
-      ></SoundingResultDetail>
+      <SectionDetails v-if="isSEC && isPending" :entry="entry" />
+      <StretchDetails v-if="isST && isPending" :entry="entry" />
+      <SoundingResultDetail :entry="entry" v-if="isSR && isPending" />
     </div>
-    <AdditionalDetail
-      v-if="entry.id === showAdditional && isPending"
-      class="ml-2 d-flex flex-row"
-      :entry="entry"
-    ></AdditionalDetail>
-    <div class="d-flex fex-row" style="padding-left: 3px;">
-      <font-awesome-icon
-        v-if="entry.id === showLogs"
+    <div
+      v-if="entry.id === showAdditional && isPending && (isFD || isAGM || isBN)"
+      class="d-flex border-bottom"
+    >
+      <FairwayDimensionDetail v-if="isFD" />
+      <ApprovedGaugeMeasurementDetail v-if="isAGM" />
+      <BottleneckDetail :entry="entry" v-if="isBN" />
+    </div>
+    <div class="d-flex" style="padding-left: 3px;">
+      <UISpinnerButton
         @click="toggleAdditionalLogging"
-        class="my-auto mr-1 text-info pointer"
-        icon="angle-down"
-        fixed-width
-      ></font-awesome-icon>
-      <font-awesome-icon
-        v-if="entry.id !== showLogs"
-        @click="toggleAdditionalLogging"
-        class="my-auto mr-1 text-info pointer"
-        icon="angle-right"
-        fixed-width
-      ></font-awesome-icon>
+        :state="entry.id === showLogs"
+        :icons="['angle-right', 'angle-down']"
+        classes="text-info"
+      />
       <span class="text-info"><translate>Logs</translate></span>
     </div>
-    <AdditionalLog
-      v-if="entry.id === showLogs"
-      class="mx-4 pb-1 d-flex flex-row"
-    ></AdditionalLog>
+    <AdditionalLog v-if="entry.id === showLogs" class="d-flex flex-row" />
   </div>
 </template>
 
@@ -92,36 +68,48 @@
  */
 
 import { mapState } from "vuex";
-import { displayError } from "@/lib/errors.js";
-import { HTTP } from "@/lib/http.js";
 
 export default {
-  name: "logdetail",
-  props: ["entry"],
   components: {
-    SoundingResultDetail: () => import("./SoundingResultDetail.vue"),
-    StretchDetail: () => import("./StretchDetails.vue"),
-    AdditionalDetail: () => import("./AdditionalDetail.vue"),
-    AdditionalLog: () => import("./AdditionalLog.vue")
+    SoundingResultDetail: () => import("./SoundingResultDetail"),
+    StretchDetails: () => import("./StretchDetails"),
+    SectionDetails: () => import("./SectionDetails"),
+    FairwayDimensionDetail: () => import("./FairwayDimensionDetail"),
+    ApprovedGaugeMeasurementDetail: () =>
+      import("./ApprovedGaugeMeasurementDetail"),
+    BottleneckDetail: () => import("./BottleneckDetail"),
+    AdditionalLog: () => import("./AdditionalLog")
   },
-  mounted() {
-    HTTP.get("/imports/" + this.entry.id, {
-      headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-    })
-      .then(response => {
-        this.$store.commit("imports/setCurrentDetails", response.data);
-        if (this.entry.state === "pending") {
-          this.$store.commit("imports/showAdditionalInfoFor", this.entry.id);
-        }
-        this.$store.commit("imports/showAdditionalLogsFor", this.entry.id);
-      })
-      .catch(error => {
-        const { status, data } = error.response;
-        displayError({
-          title: this.$gettext("Backend Error"),
-          message: `${status}: ${data.message || data}`
-        });
-      });
+  props: ["entry"],
+  computed: {
+    ...mapState("imports", ["showAdditional", "showLogs", "details"]),
+    kind() {
+      return this.entry.kind.toUpperCase();
+    },
+    isPending() {
+      return this.entry.state == "pending";
+    },
+    hasAdditionalInfo() {
+      return this.isPending && (this.isAGM || this.isBN);
+    },
+    isFD() {
+      return this.kind === "FD";
+    },
+    isAGM() {
+      return this.kind === "AGM";
+    },
+    isBN() {
+      return this.kind === "BN" || this.kind === "UBN";
+    },
+    isST() {
+      return this.kind === "ST";
+    },
+    isSEC() {
+      return this.kind === "SEC";
+    },
+    isSR() {
+      return this.kind === "SR";
+    }
   },
   methods: {
     toggleAdditionalInfo() {
@@ -139,34 +127,11 @@
       }
     }
   },
-  computed: {
-    ...mapState("imports", ["showAdditional", "showLogs", "details"]),
-    kind() {
-      return this.entry.kind.toUpperCase();
-    },
-    isPending() {
-      return this.entry.state == "pending";
-    },
-    hasAdditionalInfo() {
-      return (
-        this.isPending && (this.isApprovedGaugeMeasurement || this.isBottleneck)
-      );
-    },
-    isFairwayDimension() {
-      return this.kind === "FD";
-    },
-    isApprovedGaugeMeasurement() {
-      return this.kind === "AGM";
-    },
-    isBottleneck() {
-      return this.kind === "BN" || this.kind === "UBN";
-    },
-    isStretch() {
-      return this.kind === "ST";
-    },
-    isSoundingResult() {
-      return this.kind === "SR";
+  mounted() {
+    if (this.entry.state === "pending") {
+      this.$store.commit("imports/showAdditionalInfoFor", this.entry.id);
     }
+    this.$store.commit("imports/showAdditionalLogsFor", this.entry.id);
   }
 };
 </script>
--- a/client/src/components/importoverview/LogEntry.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/importoverview/LogEntry.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,104 +1,84 @@
 <template>
-  <div class="logentry">
-    <div class="row no-gutters text-left">
-      <div
-        style="width: 79px;"
-        class="table-cell d-flex justify-content-between"
+  <div class="row w-100 no-gutters text-left">
+    <div style="width: 75px;" class="table-cell d-flex justify-content-between">
+      <UISpinnerButton
+        @click="toggleDetails"
+        :loading="loading"
+        :state="entry.id === show"
+        :icons="['angle-right', 'angle-down']"
+      />
+      {{ entry.id }}
+    </div>
+    <div style="width: 53px;" class="table-cell center">
+      {{ entry.kind.toUpperCase() }}
+    </div>
+    <div style="width: 138px;" class="table-cell center">
+      {{ entry.enqueued | dateTime }}
+    </div>
+    <div style="width: 80px;" class="table-cell truncate">
+      {{ entry.user }}
+    </div>
+    <div style="width: 55px;" class="table-cell center">
+      {{ userCountries[entry.user] }}
+    </div>
+    <div style="width: 80px;" class="table-cell truncate">
+      {{ entry.signer }}
+    </div>
+    <div style="width: 72px;" :class="stateStyle">
+      {{ entry.state }}
+    </div>
+    <div style="width: 44px;" class="table-cell center">
+      <font-awesome-icon
+        v-if="entry.warnings"
+        class="text-warning"
+        icon="exclamation-triangle"
+        fixed-width
+      />
+    </div>
+    <div style="flex-grow: 1; padding: 0;" class="table-cell text-right">
+      <button
+        :class="['action approved', { active: isApproved }]"
+        @click="toggleApproval($options.STATES.APPROVED)"
+        v-if="entry.state === 'pending'"
       >
-        <font-awesome-icon
-          @click="toggleDetails"
-          class="my-auto text-info pointer"
-          :icon="entry.id === show ? 'angle-down' : 'angle-right'"
-          fixed-width
-        ></font-awesome-icon>
-        {{ entry.id }}
-      </div>
-      <div style="width: 53px;" class="table-cell text-center">
-        {{ entry.kind.toUpperCase() }}
-      </div>
-      <div style="width: 138px;" class="table-cell text-center">
-        {{ entry.enqueued | dateTime }}
-      </div>
-      <div style="width: 105px;" class="table-cell truncate">
-        {{ entry.user }}
-      </div>
-      <div style="width: 105px;" class="table-cell truncate">
-        {{ entry.signer }}
-      </div>
-      <div style="width: 72px;" class="table-cell text-center">
-        <span v-if="entry.state === 'failed'" class="text-danger">{{
-          entry.state
-        }}</span>
-        <span v-else>{{ entry.state }}</span>
-      </div>
-      <div style="width: 44px;" class="table-cell text-center">
-        <font-awesome-icon
-          v-if="entry.warnings"
-          class="text-warning"
-          icon="exclamation-triangle"
-          fixed-width
-        ></font-awesome-icon>
-      </div>
-      <div style="flex-grow: 1; padding: 0;" class="table-cell text-right">
-        <div v-if="entry.state === 'pending'">
-          <button
-            :class="['actions approved', { active: isApproved }]"
-            @click="toggleApproval($options.STATES.APPROVED)"
-          >
-            <font-awesome-icon
-              class="small pointer"
-              icon="check"
-            ></font-awesome-icon>
-          </button>
-          <button
-            :class="['actions rejected', { active: isRejected }]"
-            @click="toggleApproval($options.STATES.REJECTED)"
-          >
-            <font-awesome-icon
-              icon="times"
-              class="small pointer"
-            ></font-awesome-icon>
-          </button>
-        </div>
-      </div>
+        <font-awesome-icon class="small pointer" icon="check" />
+      </button>
+      <button
+        :class="['action rejected', { active: isRejected }]"
+        @click="toggleApproval($options.STATES.REJECTED)"
+        v-if="entry.state === 'pending'"
+      >
+        <font-awesome-icon icon="times" class="small pointer" />
+      </button>
     </div>
-    <LogDetail
-      :entry="entry"
-      :details="details"
-      v-if="show === entry.id"
-    ></LogDetail>
   </div>
 </template>
 
 <style lang="sass" scoped>
-.logentry
-  width: 100%
-  &:hover
-    background: #fafafa
-  .actions
-    height: 100%
-    width: 50%
-    border: 0
-    background: transparent
-    outline: none
-    &.approved
-      color: green
-      &.active,
-      &:hover
-        color: white
-        background: green
+.action
+  height: 100%
+  width: 50%
+  border: 0
+  background: white
+  outline: none
+  &.approved
+    color: green
+    &.active,
+    &:hover
+      color: white
+      background: green
+  &.rejected
+    border-left: 1px solid #dee2e6
+    color: red
+    &.active,
+    &:hover
+      color: white
+      background: red
+.active
+  .action
+    background-color: #d2eaee
     &.rejected
-      border-left: 1px solid #dee2e6
-      color: red
-      &.active,
-      &:hover
-        color: white
-        background: red
-.table-cell
-  padding: 0 3px
-  border-right: solid 1px #dee2e6
-  &:last-child
-    border-right: none
+      border-left: solid 1px rgba(255, 255, 255, 0.3)
 </style>
 
 <script>
@@ -108,26 +88,49 @@
  * SPDX-License-Identifier: AGPL-3.0-or-later
  * License-Filename: LICENSES/AGPL-3.0.txt
  *
- * Copyright (C) 2018 by via donau
+ * Copyright (C) 2018, 2019 by via donau
  *   – Österreichische Wasserstraßen-Gesellschaft mbH
  * Software engineering by Intevation GmbH
  *
  * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
+ * * Thomas Junk <thomas.junk@intevation.de>
+ * * Markus Kottländer <markus.kottlaender@intevation.de>
  */
-import { mapState } from "vuex";
-import { STATES } from "@/store/imports.js";
+import { mapState, mapGetters } from "vuex";
+import { STATES } from "@/store/imports";
+import { displayError } from "@/lib/errors";
+import { HTTP } from "@/lib/http";
 
 export default {
-  name: "importlogentry",
+  STATES,
   props: ["entry"],
   data() {
     return {
-      details: null
+      loading: false
     };
   },
-  components: {
-    LogDetail: () => import("./LogDetail.vue")
+  computed: {
+    ...mapState("imports", ["show"]),
+    ...mapGetters("usermanagement", ["userCountries"]),
+    stateStyle() {
+      return [
+        "table-cell",
+        "center",
+        {
+          "text-danger": this.entry.state === "failed",
+          "font-weight-bolder": this.entry.state === "running"
+        }
+      ];
+    },
+    needsApproval() {
+      return this.entry.status === STATES.NEEDSAPPROVAL;
+    },
+    isRejected() {
+      return this.entry.status === STATES.REJECTED;
+    },
+    isApproved() {
+      return this.entry.status === STATES.APPROVED;
+    }
   },
   methods: {
     toggleApproval(state) {
@@ -143,22 +146,24 @@
         this.$store.commit("imports/hideAdditionalInfo");
         this.$store.commit("imports/hideAdditionalLogs");
       } else {
-        this.$store.commit("imports/showDetailsFor", id);
+        this.loading = true;
+        HTTP.get("/imports/" + this.entry.id, {
+          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+        })
+          .then(response => {
+            this.$store.commit("imports/showDetailsFor", id);
+            this.$store.commit("imports/setCurrentDetails", response.data);
+          })
+          .catch(error => {
+            const { status, data } = error.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          })
+          .finally(() => (this.loading = false));
       }
     }
-  },
-  computed: {
-    ...mapState("imports", ["show"]),
-    needsApproval() {
-      return this.entry.status === STATES.NEEDSAPPROVAL;
-    },
-    isRejected() {
-      return this.entry.status === STATES.REJECTED;
-    },
-    isApproved() {
-      return this.entry.status === STATES.APPROVED;
-    }
-  },
-  STATES: STATES
+  }
 };
 </script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importoverview/SectionDetails.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,116 @@
+<template>
+  <div
+    :class="{
+      full: !showLogs,
+      split: showLogs
+    }"
+  >
+    <div v-if="!details.summary.section" class="d-flex">
+      <UISpinnerButton
+        @click="showDetails = !showDetails"
+        :state="showDetails"
+        :icons="['angle-right', 'angle-down']"
+        classes="text-info"
+      />
+      <a @click="zoomToSection()" class="text-info pointer">{{
+        details.summary.objnam
+      }}</a>
+    </div>
+    <div>
+      <div v-if="showDetails">
+        <div
+          v-for="(entry, index) in Object.keys(details.summary)"
+          :key="index"
+          class="comparison row no-gutters px-4 text-left"
+        >
+          <span class="col-4">{{ entry }}</span>
+          <span class="col-4">{{ details.summary[entry] }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.comparison {
+  width: 668px;
+  border-top: dashed 1px #dee2e6;
+}
+
+.comparison:nth-child(odd) {
+  background-color: #f8f9fa;
+}
+
+.split {
+  max-height: 35vh;
+}
+
+.full {
+  max-height: 70vh;
+}
+</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 { displayError } from "@/lib/errors";
+import { mapState, mapGetters } from "vuex";
+
+export default {
+  props: ["entry"],
+  data() {
+    return {
+      showDetails: true
+    };
+  },
+  mounted() {
+    this.$store.commit("imports/hideAdditionalInfo");
+  },
+  computed: {
+    ...mapState("imports", ["showAdditional", "showLogs", "details"]),
+    ...mapGetters("map", ["openLayersMap"])
+  },
+  methods: {
+    zoomToSection() {
+      const { name } = this.details.summary;
+      this.openLayersMap()
+        .getLayer("SECTIONS")
+        .setVisible(true);
+      this.$store
+        .dispatch("imports/loadSection", name)
+        .then(response => {
+          if (response.data.features.length < 1)
+            throw new Error("no features found for: " + name);
+          this.$store.commit(
+            "imports/selectedSectionId",
+            response.data.features[0].id
+          );
+          this.$store.dispatch("map/moveToFeauture", {
+            feature: response.data.features[0],
+            zoom: 17,
+            preventZoomOut: true
+          });
+        })
+        .catch(error => {
+          console.log(error);
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    }
+  }
+};
+</script>
--- a/client/src/components/importoverview/SoundingResultDetail.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/importoverview/SoundingResultDetail.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,6 +1,5 @@
 <template>
   <div>
-    <span class="empty"></span>
     <a @click="zoomTo()" class="text-info pointer">
       {{ details.summary.bottleneck }}
     </a>
@@ -33,17 +32,13 @@
     ...mapState("imports", ["showAdditional", "details"])
   },
   methods: {
-    moveMap(coordinates) {
-      this.$store.commit("map/moveMap", {
-        coordinates: coordinates,
+    zoomTo() {
+      const { lat, lon, bottleneck, date } = this.details.summary;
+      this.$store.dispatch("map/moveMap", {
+        coordinates: [lat, lon],
         zoom: 17,
         preventZoomOut: true
       });
-    },
-    zoomTo() {
-      const { lat, lon, bottleneck, date } = this.details.summary;
-      const coordinates = [lat, lon];
-      this.moveMap(coordinates);
       this.$store
         .dispatch("bottlenecks/setSelectedBottleneck", bottleneck)
         .then(() => {
--- a/client/src/components/importoverview/StretchDetails.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/importoverview/StretchDetails.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,12 +1,59 @@
 <template>
-  <div>
-    <span class="empty">&nbsp;</span>
-    <a @click="zoomToStretch()" class="text-info pointer">{{
-      details.summary.stretch
-    }}</a>
+  <div
+    :class="{
+      full: !showLogs,
+      split: showLogs
+    }"
+  >
+    <div v-if="!details.summary.stretch" class="d-flex">
+      <UISpinnerButton
+        @click="showDetails = !showDetails"
+        :state="showDetails"
+        :icons="['angle-right', 'angle-down']"
+        classes="text-info"
+      />
+      <a @click="zoomToStretch()" class="text-info pointer"
+        >{{ details.summary.objnam }} (
+        {{ details.summary.countries.join(", ") }} )</a
+      >
+    </div>
+    <div>
+      <div v-if="showDetails">
+        <div
+          v-for="(entry, index) in Object.keys(details.summary)"
+          :key="index"
+          class="comparison row no-gutters px-4 text-left"
+        >
+          <span class="col-4">{{ entry }}</span>
+          <span v-if="entry === 'countries'" class="col-4">{{
+            details.summary[entry].join(", ")
+          }}</span>
+          <span v-else class="col-4">{{ details.summary[entry] }}</span>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
+<style lang="scss" scoped>
+.comparison {
+  width: 668px;
+  border-top: dashed 1px #dee2e6;
+}
+
+.comparison:nth-child(odd) {
+  background-color: #f8f9fa;
+}
+
+.split {
+  max-height: 35vh;
+}
+
+.full {
+  max-height: 70vh;
+}
+</style>
+
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -21,36 +68,43 @@
  * Author(s):
  * Thomas Junk <thomas.junk@intevation.de>
  */
-import { displayError } from "@/lib/errors.js";
-import { LAYERS } from "@/store/map.js";
-import { mapState } from "vuex";
+import { displayError } from "@/lib/errors";
+import { mapState, mapGetters } from "vuex";
 
 export default {
-  name: "stretchdetails",
+  data() {
+    return {
+      showDetails: true
+    };
+  },
   props: ["entry"],
   mounted() {
     this.$store.commit("imports/hideAdditionalInfo");
   },
   computed: {
-    ...mapState("imports", ["showAdditional", "details"])
+    ...mapState("imports", ["showAdditional", "showLogs", "details"]),
+    ...mapGetters("map", ["openLayersMap"])
   },
   methods: {
-    moveToExtent(feature) {
-      this.$store.commit("map/moveToExtent", {
-        feature: feature,
-        zoom: 17,
-        preventZoomOut: true
-      });
-    },
     zoomToStretch() {
-      const name = this.details.summary.stretch;
-      this.$store.commit("map/setLayerVisible", LAYERS.STRETCHES);
+      const { name } = this.details.summary;
+      this.openLayersMap()
+        .getLayer("STRETCHES")
+        .setVisible(true);
       this.$store
         .dispatch("imports/loadStretch", name)
         .then(response => {
           if (response.data.features.length < 1)
             throw new Error("no feaures found for: " + name);
-          this.moveToExtent(response.data.features[0]);
+          this.$store.commit(
+            "imports/selectedStretchId",
+            response.data.features[0].id
+          );
+          this.$store.dispatch("map/moveToFeauture", {
+            feature: response.data.features[0],
+            zoom: 17,
+            preventZoomOut: true
+          });
         })
         .catch(error => {
           console.log(error);
@@ -64,5 +118,3 @@
   }
 };
 </script>
-
-<style></style>
--- a/client/src/components/importschedule/Importschedule.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,268 +0,0 @@
-<template>
-  <div class="d-flex flex-row">
-    <Spacer></Spacer>
-    <div class="mt-2 w-100">
-      <div class="card flex-grow-1 schedulecard shadow-xs">
-        <UIBoxHeader icon="clock" :title="importScheduleLabel" />
-        <div class="searchandfilter p-3 w-50 mx-auto">
-          <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>
-        <UITableHeader
-          :columns="[
-            { id: 'id', title: `${idLabel}`, class: 'col-1' },
-            { id: 'kind', title: `${typeLabel}`, class: 'col-2' },
-            { id: 'user', title: `${authorLabel}`, class: 'col-2' },
-            { id: 'config.cron', title: `${scheduleLabel}`, class: 'col-2' },
-            { id: 'config.send-email', title: `${emailLabel}`, class: 'col-2' }
-          ]"
-        />
-        <UITableBody
-          :data="filteredSchedules() | sortTable(sortColumn, sortDirection)"
-          v-slot="{ item: schedule }"
-        >
-          <div class="py-1 col-1">{{ schedule.id }}</div>
-          <div class="py-1 col-2">{{ schedule.kind.toUpperCase() }}</div>
-          <div class="py-1 col-2">{{ schedule.user }}</div>
-          <div class="py-1 col-2">{{ schedule.config.cron }}</div>
-          <div class="py-1 col-2 text-center">
-            <font-awesome-icon
-              v-if="schedule.config['send-email']"
-              class="fa-fw mr-2"
-              fixed-width
-              icon="check"
-            ></font-awesome-icon>
-          </div>
-          <div class="py-1 col text-right">
-            <button
-              @click="editSchedule(schedule.id)"
-              class="btn btn-xs btn-dark mr-1"
-              :disabled="importScheduleDetailVisible"
-            >
-              <font-awesome-icon
-                icon="pencil-alt"
-                fixed-width
-              ></font-awesome-icon>
-            </button>
-            <button
-              @click="deleteSchedule(schedule)"
-              class="btn btn-xs btn-dark mr-1"
-              :disabled="importScheduleDetailVisible"
-            >
-              <font-awesome-icon icon="trash" fixed-width></font-awesome-icon>
-            </button>
-            <button
-              @click="triggerManualImport(schedule.id)"
-              class="btn btn-xs btn-dark"
-              :disabled="importScheduleDetailVisible"
-            >
-              <font-awesome-icon icon="play" fixed-width></font-awesome-icon>
-            </button>
-          </div>
-        </UITableBody>
-        <div class="p-3 text-right">
-          <button
-            :disabled="importScheduleDetailVisible"
-            @click="newImport"
-            class="btn btn-info newbutton"
-          >
-            <translate>New Import</translate>
-          </button>
-        </div>
-      </div>
-    </div>
-    <Importscheduledetail></Importscheduledetail>
-  </div>
-</template>
-
-<style lang="sass" scoped>
-th
-  border-top: 0px
-
-.card-body
-  padding-bottom: $small-offset
-
-.schedulecard
-  margin-right: $small-offset
-  min-height: 20rem
-
-.schedulecard-body
-  width: 100%
-  margin-left: auto
-  margin-right: auto
-</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>
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-
-import { mapState } from "vuex";
-import { HTTP } from "@/lib/http";
-import { displayInfo, displayError } from "@/lib/errors";
-import { sortTable } from "@/lib/mixins";
-
-export default {
-  name: "importschedule",
-  mixins: [sortTable],
-  components: {
-    Importscheduledetail: () => import("./Importscheduledetail"),
-    Spacer: () => import("@/components/Spacer")
-  },
-  data() {
-    return {
-      searchQuery: ""
-    };
-  },
-  computed: {
-    ...mapState("application", ["showSidebar"]),
-    ...mapState("importschedule", ["schedules", "importScheduleDetailVisible"]),
-    importScheduleLabel() {
-      return this.$gettext("Import Schedule");
-    },
-    idLabel() {
-      return this.$gettext("ID");
-    },
-    typeLabel() {
-      return this.$gettext("Type");
-    },
-    authorLabel() {
-      return this.$gettext("Author");
-    },
-    scheduleLabel() {
-      return this.$gettext("Schedule");
-    },
-    emailLabel() {
-      return this.$gettext("Email");
-    },
-    spacerStyle() {
-      return [
-        "spacer ml-3",
-        {
-          "spacer-expanded": this.showSidebar,
-          "spacer-collapsed": !this.showSidebar
-        }
-      ];
-    }
-  },
-  methods: {
-    filteredSchedules() {
-      return this.schedules.filter(s => {
-        return (s.id + s.kind)
-          .toLowerCase()
-          .includes(this.searchQuery.toLowerCase());
-      });
-    },
-    editSchedule(id) {
-      this.$store
-        .dispatch("importschedule/loadSchedule", id)
-        .then(() => {
-          this.$store.commit("importschedule/setImportScheduleDetailVisible");
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    },
-    triggerManualImport(id) {
-      HTTP.get("/imports/config/" + id + "/run", {
-        headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-      })
-        .then(response => {
-          const { id } = response.data;
-          displayInfo({
-            title: this.$gettext("Imports"),
-            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}`
-          });
-        });
-    },
-    getSchedules() {
-      this.$store.dispatch("importschedule/loadSchedules").catch(error => {
-        const { status, data } = error.response;
-        displayError({
-          title: this.$gettext("Backend Error"),
-          message: `${status}: ${data.message || data}`
-        });
-      });
-    },
-    newImport() {
-      this.$store.commit("importschedule/setImportScheduleDetailVisible");
-    },
-    deleteSchedule(schedule) {
-      console.log(schedule);
-      this.$store.commit("application/popup", {
-        icon: "trash",
-        title: this.$gettext("Delete Import"),
-        content:
-          this.$gettext("Do you really want to delete the import with ID") +
-          `<b>${schedule.id}</b>` +
-          this.$gettext("of type") +
-          `<b>${schedule.kind.toUpperCase()}</b>?`,
-        confirm: {
-          label: this.$gettext("Delete"),
-          icon: "trash",
-          callback: () => {
-            this.$store
-              .dispatch("importschedule/deleteSchedule", schedule.id)
-              .then(() => {
-                this.getSchedules();
-                displayInfo({
-                  title: this.$gettext("Imports"),
-                  message: this.$gettext("Deleted import: #") + schedule.id
-                });
-              })
-              .catch(error => {
-                const { status, data } = error.response;
-                displayError({
-                  title: this.$gettext("Backend Error"),
-                  message: `${status}: ${data.message || data}`
-                });
-              });
-          }
-        },
-        cancel: {
-          label: this.$gettext("Cancel"),
-          icon: "times"
-        }
-      });
-    }
-  },
-  mounted() {
-    this.getSchedules();
-  }
-};
-</script>
--- a/client/src/components/importschedule/Importscheduledetail.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1031 +0,0 @@
-<template>
-  <div
-    class="importscheduledetails fadeIn animated"
-    v-if="importScheduleDetailVisible"
-  >
-    <div class="card shadow-xs importscheduledetailscard pb-5">
-      <UIBoxHeader :title="dialogLabel" :closeCallback="closeDetailview" />
-      <div class="card-body">
-        <form @submit.prevent="save" class="ml-2 mr-2">
-          <div class="d-flex flex-row">
-            <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
-                >
-                <option :value="$options.IMPORTTYPES.WATERWAYAXIS"
-                  ><translate>Waterway axis</translate></option
-                >
-                <option :value="$options.IMPORTTYPES.GAUGEMEASUREMENT"
-                  ><translate>Gauge measurement</translate></option
-                >
-                <option :value="$options.IMPORTTYPES.FAIRWAYAVAILABILITY"
-                  ><translate>Available fairway depths</translate></option
-                >
-                <option :value="$options.IMPORTTYPES.WATERWAYAREA"
-                  ><translate>Waterway area</translate></option
-                >
-                <option :value="$options.IMPORTTYPES.FAIRWAYDIMENSION"
-                  ><translate>Fairway dimension</translate></option
-                >
-                <option :value="$options.IMPORTTYPES.WATERWAYGAUGES"
-                  ><translate>Waterway gauges</translate></option
-                >
-                <option :value="$options.IMPORTTYPES.DISTANCEMARKSVIRTUAL"
-                  ><translate>Distance marks virtual</translate></option
-                >
-                <option :value="$options.IMPORTTYPES.DISTANCEMARKSASHORE"
-                  ><translate>Distance marks ashore</translate></option
-                >
-              </select>
-            </div>
-            <div class="flex-column ml-4">
-              <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>
-          <div v-if="directImportAvailable" class="flex-column">
-            <div class="flex-row text-left">
-              <small class="text-muted">
-                <translate>Import via</translate>
-              </small>
-            </div>
-            <div class="flex-flex-row text-left">
-              <!-- '#75c791' is the DEFAULT_COLOR_CHECKED
-                   from vue-js-toggle-button as here both states are active -->
-              <toggle-button
-                :color="{ unchecked: '#75c791' }"
-                v-model="directImport"
-                class="mt-2"
-                :speed="100"
-                :labels="{
-                  checked: this.$options.FILE,
-                  unchecked: this.$options.URL
-                }"
-                :width="60"
-                :height="30"
-              />
-            </div>
-          </div>
-          <Availablefairwaydepth
-            v-if="
-              import_ == $options.IMPORTTYPES.FAIRWAYAVAILABILITY &&
-                !directImport
-            "
-            @urlChanged="setUrl"
-            :url="url"
-          ></Availablefairwaydepth>
-          <Bottleneck
-            v-if="import_ == $options.IMPORTTYPES.BOTTLENECK"
-            @urlChanged="setUrl"
-            @toleranceChanged="setTolerance"
-            :url="url"
-            :tolerance="tolerance"
-            :directImport="directImport"
-          ></Bottleneck>
-          <Distancemarksvirtual
-            v-if="import_ == $options.IMPORTTYPES.DISTANCEMARKSVIRTUAL"
-            @urlChanged="setUrl"
-            @usernameChanged="setUsername"
-            @passwordChanged="setPassword"
-            :url="url"
-            :username="username"
-            :password="password"
-          ></Distancemarksvirtual>
-          <Distancemarksashore
-            v-if="import_ == $options.IMPORTTYPES.DISTANCEMARKSASHORE"
-            @urlChanged="setUrl"
-            @featureTypeChanged="setFeatureType"
-            @sortByChanged="setSortBy"
-            :url="url"
-            :featureType="featureType"
-            :sortBy="sortBy"
-          ></Distancemarksashore>
-          <Faiwaydimensions
-            v-if="import_ == $options.IMPORTTYPES.FAIRWAYDIMENSION"
-            @urlChanged="setUrl"
-            @featureTypeChanged="setFeatureType"
-            @sortByChanged="setSortBy"
-            @LOSChanged="setLOS"
-            @depthChanged="setDepth"
-            @minWidthChanged="setMinWidth"
-            @maxWidthChanged="setMaxWidth"
-            @sourceOrganizationChanged="setSourceOrganization"
-            :url="url"
-            :featureType="featureType"
-            :sortBy="sortBy"
-            :LOS="LOS"
-            :minWidth="minWidth"
-            :maxWidth="maxWidth"
-            :sourceOrganization="sourceOrganization"
-            :depth="depth"
-          ></Faiwaydimensions>
-          <Gaugemeasurement
-            v-if="
-              import_ == $options.IMPORTTYPES.GAUGEMEASUREMENT && !directImport
-            "
-            @urlChanged="setUrl"
-            :url="url"
-          ></Gaugemeasurement>
-          <Waterwayarea
-            v-if="import_ == $options.IMPORTTYPES.WATERWAYAREA"
-            @urlChanged="setUrl"
-            @featureTypeChanged="setFeatureType"
-            @sortByChanged="setSortBy"
-            :url="url"
-            :featureType="featureType"
-            :sortBy="sortBy"
-          ></Waterwayarea>
-          <Waterwaygauges
-            v-if="import_ == $options.IMPORTTYPES.WATERWAYGAUGES"
-            @urlChanged="setUrl"
-            @usernameChanged="setUsername"
-            @passwordChanged="setPassword"
-            :url="url"
-            :username="username"
-            :password="password"
-          ></Waterwaygauges>
-          <Waterwayaxis
-            v-if="import_ == $options.IMPORTTYPES.WATERWAYAXIS"
-            @urlChanged="setUrl"
-            @featureTypeChanged="setFeatureType"
-            @sortByChanged="setSortBy"
-            :url="url"
-            :featureType="featureType"
-            :sortBy="sortBy"
-          ></Waterwayaxis>
-
-          <template v-if="!directImport || !directImportAvailable">
-            <div class="d-flex flex-row">
-              <div class="flex-column mt-3 mr-4">
-                <div class="flex-row text-left">
-                  <small class="text-muted">
-                    <translate>Scheduled</translate>?
-                  </small>
-                </div>
-                <div class="flex-flex-row text-left">
-                  <toggle-button
-                    v-model="scheduled"
-                    class="mt-2"
-                    :speed="100"
-                    :labels="{
-                      checked: this.$options.on,
-                      unchecked: this.$options.off
-                    }"
-                    :width="60"
-                    :height="30"
-                  />
-                </div>
-              </div>
-              <div class="flex-column mt-3 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
-                    :disabled="!scheduled"
-                    v-model="easyCron"
-                    class="mt-2"
-                    :speed="100"
-                    :labels="{
-                      checked: this.$options.on,
-                      unchecked: this.$options.off
-                    }"
-                    :width="60"
-                    :height="30"
-                  />
-                </div>
-              </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
-                  :disabled="!scheduled"
-                  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
-                    :disabled="!scheduled"
-                    style="width: 130px;"
-                    v-model="cronMode"
-                    class="form-control"
-                    @change="clearInputs"
-                  >
-                    <option :value="null"></option>
-                    <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
-                      :disabled="!scheduled"
-                      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
-                      :disabled="!scheduled"
-                      v-model="hour"
-                      class="cronfield ml-1 mr-1 form-control"
-                      type="number"
-                    />
-                    <input
-                      :disabled="!scheduled"
-                      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
-                      :disabled="!scheduled"
-                      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
-                      :disabled="!scheduled"
-                      v-model="hour"
-                      class="cronfield ml-1 mr-1 form-control"
-                      type="number"
-                    />
-                    <input
-                      :disabled="!scheduled"
-                      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
-                      :disabled="!scheduled"
-                      v-model="dayOfMonth"
-                      class="cronfield ml-1 mr-1 form-control"
-                      type="number"
-                    />
-                    <h4 class="mt-auto mb-auto">{{ $options.AT }}</h4>
-                    <input
-                      :disabled="!scheduled"
-                      v-model="hour"
-                      class="cronfield ml-1 mr-2 form-control"
-                      type="number"
-                    />
-                    <input
-                      :disabled="!scheduled"
-                      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
-                      :disabled="!scheduled"
-                      v-model="dayOfMonth"
-                      class="cronfield ml-1 mr-1 form-control"
-                      type="number"
-                    />
-                    <h4 class="mt-auto mb-auto">{{ $options.OF }}</h4>
-                    <select
-                      :disabled="!scheduled"
-                      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
-                      :disabled="!scheduled"
-                      v-model="hour"
-                      class="cronfield ml-1 mr-1 form-control"
-                      type="number"
-                    />
-                    <input
-                      :disabled="!scheduled"
-                      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
-                    :disabled="!scheduled"
-                    class="form-control"
-                    v-model="cronString"
-                    type="text"
-                  />
-                </div>
-              </div>
-            </div>
-            <button
-              :disabled="!isValid"
-              type="submit"
-              class="shadow-sm btn btn-info submit-button"
-            >
-              <translate>Save</translate>
-            </button>
-          </template>
-          <div v-else class="d-flex flex-row text-left">
-            <div class="mt-3 mb-3 flex-column w-100">
-              <div class="custom-file">
-                <input
-                  accept=".xml"
-                  type="file"
-                  @change="fileSelected"
-                  class="custom-file-input"
-                  id="uploadFile"
-                />
-                <label class="pointer custom-file-label" for="uploadFile">
-                  {{ uploadLabel }}
-                </label>
-              </div>
-            </div>
-          </div>
-          <button
-            @click="triggerManualImport"
-            type="button"
-            class="shadow-sm btn btn-outline-info trigger"
-            :disabled="!triggerActive || !isValid"
-          >
-            <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>
-/* 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, 2019 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- * Tom Gottfried <tom.gottfried@intevation.de>
- */
-import {
-  IMPORTTYPES,
-  IMPORTTYPEKIND,
-  initializeCurrentSchedule
-} from "@/store/importschedule";
-import { mapState } from "vuex";
-import { displayInfo, displayError } from "@/lib/errors.js";
-import app from "@/main.js";
-import { HTTP } from "@/lib/http";
-
-export default {
-  name: "importscheduledetail",
-  components: {
-    Availablefairwaydepth: () =>
-      import("@/components/importschedule/importtypes/Availablefairwaydepth"),
-    Bottleneck: () =>
-      import("@/components/importschedule/importtypes/Bottleneck"),
-    Distancemarksvirtual: () =>
-      import("@/components/importschedule/importtypes/Distancemarksvirtual"),
-    Distancemarksashore: () =>
-      import("@/components/importschedule/importtypes/Distancemarksashore"),
-    Faiwaydimensions: () =>
-      import("@/components/importschedule/importtypes/Fairwaydimensions"),
-    Gaugemeasurement: () =>
-      import("@/components/importschedule/importtypes/Gaugemeasurement"),
-    Waterwayarea: () =>
-      import("@/components/importschedule/importtypes/Waterwayarea"),
-    Waterwaygauges: () =>
-      import("@/components/importschedule/importtypes/Waterwaygauges"),
-    Waterwayaxis: () =>
-      import("@/components/importschedule/importtypes/Waterwayaxis")
-  },
-  data() {
-    return {
-      directImport: false,
-      passwordVisible: false,
-      uploadLabel: this.$gettext("choose file to upload"),
-      uploadFile: null,
-      ...initializeCurrentSchedule()
-    };
-  },
-  mounted() {
-    this.initialize();
-  },
-  watch: {
-    cronMode() {
-      this.cronString = this.calcCronString();
-    },
-    minutes() {
-      this.cronString = this.calcCronString();
-    },
-    hour() {
-      this.cronString = this.calcCronString();
-    },
-    month() {
-      this.cronString = this.calcCronString();
-    },
-    day() {
-      this.cronString = this.calcCronString();
-    },
-    dayOfMonth() {
-      this.cronString = this.calcCronString();
-    },
-    importScheduleDetailVisible() {
-      this.initialize();
-    },
-    cronString() {
-      if (this.isWeekly(this.cronString)) {
-        this.simple = "weekly";
-      }
-      if (this.isMonthly(this.cronString)) {
-        this.simple = "monthly";
-      }
-    }
-  },
-  computed: {
-    ...mapState("importschedule", [
-      "importScheduleDetailVisible",
-      "currentSchedule"
-    ]),
-    dialogLabel() {
-      if (this.id) return this.$gettext("Import") + " " + this.id;
-      return this.$gettext("New Import");
-    },
-    directImportAvailable() {
-      switch (this.import_) {
-        case this.$options.IMPORTTYPES.BOTTLENECK:
-        case this.$options.IMPORTTYPES.FAIRWAYAVAILABILITY:
-        case this.$options.IMPORTTYPES.GAUGEMEASUREMENT:
-          return true;
-        default:
-          return false;
-      }
-    },
-    isCredentialsRequired() {
-      switch (this.import_) {
-        case this.$options.IMPORTTYPES.WATERWAYGAUGES:
-        case this.$options.IMPORTTYPES.DISTANCEMARKSVIRTUAL:
-          return true;
-        default:
-          return false;
-      }
-    },
-    isURLRequired() {
-      switch (this.import_) {
-        case this.$options.IMPORTTYPES.BOTTLENECK:
-        case this.$options.IMPORTTYPES.WATERWAYAXIS:
-        case this.$options.IMPORTTYPES.GAUGEMEASUREMENT:
-        case this.$options.IMPORTTYPES.FAIRWAYAVAILABILITY:
-        case this.$options.IMPORTTYPES.WATERWAYAREA:
-        case this.$options.IMPORTTYPES.FAIRWAYDIMENSION:
-        case this.$options.IMPORTTYPES.WATERWAYGAUGES:
-        case this.$options.IMPORTTYPES.DISTANCEMARKSVIRTUAL:
-        case this.$options.IMPORTTYPES.DISTANCEMARKSASHORE:
-          return true;
-        default:
-          return false;
-      }
-    },
-    isFeatureTypeRequired() {
-      switch (this.import_) {
-        case this.$options.IMPORTTYPES.WATERWAYAXIS:
-        case this.$options.IMPORTTYPES.WATERWAYAREA:
-        case this.$options.IMPORTTYPES.FAIRWAYDIMENSION:
-        case this.$options.IMPORTTYPES.DISTANCEMARKSASHORE:
-          return true;
-        default:
-          return false;
-      }
-    },
-    isSortbyRequired() {
-      switch (this.import_) {
-        case this.$options.IMPORTTYPES.WATERWAYAXIS:
-        case this.$options.IMPORTTYPES.WATERWAYAREA:
-        case this.$options.IMPORTTYPES.FAIRWAYDIMENSION:
-        case this.$options.IMPORTTYPES.DISTANCEMARKSASHORE:
-          return true;
-        default:
-          return false;
-      }
-    },
-    isToleranceRequired() {
-      switch (this.import_) {
-        case this.$options.IMPORTTYPES.BOTTLENECK:
-          return true;
-        default:
-          return false;
-      }
-    },
-    isValid() {
-      if (!this.import_) return false;
-      if (this.isToleranceRequired && !this.tolerance) return false;
-      if (this.directImport && !this.uploadFile) return false;
-      else if (!this.directImport) {
-        if (this.isURLRequired && !this.url) return false;
-        if (this.isSortbyRequired && !this.sortBy) return false;
-        if (this.isFeatureTypeRequired && !this.featureType) return false;
-        if (this.isCredentialsRequired && (!this.username || !this.password))
-          return false;
-        if (this.import_ == this.$options.IMPORTTYPES.FAIRWAYDIMENSION) {
-          if (
-            !this.LOS ||
-            !this.minWidth ||
-            !this.maxWidth ||
-            !this.depth ||
-            !this.sourceOrganization
-          )
-            return false;
-        }
-      }
-      return true;
-    }
-  },
-  methods: {
-    fileSelected(e) {
-      const files = e.target.files || e.dataTransfer.files;
-      if (!files) return;
-      this.uploadLabel = files[0].name;
-      this.uploadFile = files[0];
-    },
-    setUrl(value) {
-      this.url = value;
-    },
-    setFeatureType(value) {
-      this.featureType = value;
-    },
-    setSortBy(value) {
-      this.sortBy = value;
-    },
-    setTolerance(value) {
-      this.tolerance = value;
-    },
-    setUsername(value) {
-      this.username = value;
-    },
-    setPassword(value) {
-      this.password = value;
-    },
-    setLOS(value) {
-      this.LOS = value;
-    },
-    setMinWidth(value) {
-      this.minWidth = value;
-    },
-    setMaxWidth(value) {
-      this.maxWidth = value;
-    },
-    setDepth(value) {
-      this.depth = value;
-    },
-    setSourceOrganization(value) {
-      this.sourceOrganization = value;
-    },
-    calcCronString() {
-      let getValue = value => {
-        return this[value] !== null ? this[value] : "*";
-      };
-
-      const min = getValue("minutes");
-      const h = getValue("hour");
-      const dm = getValue("dayOfMonth");
-      const m = getValue("month");
-      const wd = getValue("day");
-
-      if (this.cronMode === "15minutes") return "0 */15 * * * *";
-      if (this.cronMode === "hour") return `0 ${min} * * * *`;
-      if (this.cronMode === "day") return `0 ${min} ${h} * * *`;
-      if (this.cronMode === "week") return `0 ${min} ${h} * * ${wd}`;
-      if (this.cronMode === "month") return `0 ${min} ${h} ${dm} * *`;
-      if (this.cronMode === "year") return `0 ${min} ${h} ${dm} ${m} *`;
-      return this.cronString;
-    },
-    validateBottleneckfields() {
-      return !!this.url;
-    },
-    initialize() {
-      this.id = this.currentSchedule.id;
-      this.importType = this.currentSchedule.importType;
-      this.schedule = this.currentSchedule.schedule;
-      this.scheduled = this.currentSchedule.scheduled;
-      this.import_ = this.currentSchedule.import_;
-      this.importSource = this.currentSchedule.importSource;
-      this.eMailNotification = this.currentSchedule.eMailNotification;
-      this.easyCron = this.currentSchedule.easyCron;
-      this.cronMode = this.currentSchedule.cronMode;
-      this.minutes = this.currentSchedule.minutes;
-      this.month = this.currentSchedule.month;
-      this.hour = this.currentSchedule.hour;
-      this.day = this.currentSchedule.day;
-      this.dayOfMonth = this.currentSchedule.dayOfMonth;
-      this.simple = this.currentSchedule.simple;
-      this.url = this.currentSchedule.url;
-      this.insecure = this.currentSchedule.insecure;
-      this.cronString = this.currentSchedule.cronString;
-      this.featureType = this.currentSchedule.featureType;
-      this.sortBy = this.currentSchedule.sortBy;
-      this.tolerance = this.currentSchedule.tolerance;
-      this.username = this.currentSchedule.username;
-      this.password = this.currentSchedule.password;
-      this.LOS = this.currentSchedule.LOS;
-      this.minWidth = this.currentSchedule.minWidth;
-      this.maxWidth = this.currentSchedule.maxWidth;
-      this.depth = this.currentSchedule.depth;
-      this.sourceOrganization = this.currentSchedule.sourceOrganization;
-      this.directImport = false;
-    },
-    isWeekly(cron) {
-      return /0 \d{1,2} \d{1,2} \* \* \d{1}/.test(cron);
-    },
-    isMonthly(cron) {
-      return /0 \d{1,2} \d{1,2} \d{1,2} \* \*/.test(cron);
-    },
-    clearInputs() {
-      this.minutes = this.currentSchedule.minutes;
-      this.month = this.currentSchedule.month;
-      this.hour = this.currentSchedule.hour;
-      this.day = this.currentSchedule.day;
-      this.dayOfMonth = this.currentSchedule.dayOfMonth;
-    },
-    triggerFileUpload() {
-      if (!this.uploadFile) return;
-      let formData = new FormData();
-      let routeParam = "";
-      switch (this.import_) {
-        case this.$options.IMPORTTYPES.BOTTLENECK:
-          formData.append("tolerance", this.tolerance);
-          routeParam = "ubn";
-          break;
-        case this.$options.IMPORTTYPES.FAIRWAYAVAILABILITY:
-          routeParam = "ufa";
-          break;
-        case this.$options.IMPORTTYPES.GAUGEMEASUREMENT:
-          routeParam = "ugm";
-          break;
-        default:
-          throw new Error("invalid importroute");
-      }
-
-      formData.append(routeParam, this.uploadFile);
-      HTTP.post("/imports/" + routeParam, formData, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-Type": "multipart/form-data"
-        }
-      })
-        .then(response => {
-          const { id } = response.data;
-          displayInfo({
-            title: this.$gettext("File Import"),
-            message: this.$gettext("Import import: #") + id
-          });
-          this.closeDetailview();
-          this.$store.dispatch("importschedule/loadSchedules").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}`
-          });
-        });
-    },
-    triggerManualImport() {
-      if (!this.triggerActive) return;
-      if (!this.import_) return;
-      if (this.directImport) {
-        if (!this.uploadFile) return;
-        this.triggerFileUpload();
-        return;
-      }
-      let data = {};
-      if (this.isURLRequired) {
-        if (!this.url) return;
-        data["url"] = this.url;
-        data["insecure"] = this.insecure;
-      }
-      if (this.isFeatureTypeRequired) {
-        if (!this.featureType) return;
-        data["feature-type"] = this.featureType;
-      }
-      if (this.isSortbyRequired) {
-        if (!this.sortBy) return;
-        data["sort-by"] = this.sortBy;
-      }
-      if (this.isToleranceRequired) {
-        if (!this.tolerance) return;
-        data["tolerance"] = parseFloat(this.tolerance);
-      }
-      if (this.isCredentialsRequired) {
-        if (!this.username || !this.password) return;
-        data["user"] = this.username;
-        data["password"] = this.password;
-      }
-      if (this.import_ == this.$options.IMPORTTYPES.FAIRWAYDIMENSION) {
-        if (
-          !this.LOS ||
-          !this.minWidth ||
-          !this.maxWidth ||
-          !this.depth ||
-          !this.sourceOrganization
-        )
-          return;
-        data["feature-type"] = this.featureType;
-        data["sort-by"] = this.sortBy;
-        data["los"] = this.LOS * 1;
-        data["min-width"] = this.minWidth * 1;
-        data["max-width"] = this.maxWidth * 1;
-        data["depth"] = this.depth * 1;
-        data["source-organization"] = this.sourceOrganization;
-      }
-      data["send-email"] = this.eMailNotification;
-      this.triggerActive = false;
-      this.$store
-        .dispatch("importschedule/triggerImport", {
-          type: IMPORTTYPEKIND[this.import_],
-          data
-        })
-        .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() {
-      if (!this.import_) return;
-      let cron = this.cronString;
-      if (this.easyCron) {
-        if (this.simple === "weekly") cron = "0 0 0 * * 0";
-        if (this.simple === "monthly") cron = "0 0 0 1 * *";
-      }
-      let data = {};
-      let config = {};
-      data["kind"] = IMPORTTYPEKIND[this.import_];
-
-      if (this.isURLRequired) {
-        if (!this.url) return;
-        config["url"] = this.url;
-        config["insecure"] = this.insecure;
-      }
-      if (this.isSortbyRequired) {
-        if (!this.sortBy) return;
-        config["sort-by"] = this.sortBy;
-      }
-      if (this.isFeatureTypeRequired) {
-        if (!this.featureType) return;
-        config["feature-type"] = this.featureType;
-      }
-      if (this.isToleranceRequired) {
-        if (!this.tolerance) return;
-        config["tolerance"] = parseFloat(this.tolerance);
-      }
-      if (this.isCredentialsRequired) {
-        if (!this.username || !this.password) return;
-        config = {
-          ...config,
-          user: this.username,
-          password: this.password
-        };
-      }
-      if (this.import_ == this.$options.IMPORTTYPES.FAIRWAYDIMENSION) {
-        if (
-          !this.LOS ||
-          !this.minWidth ||
-          !this.maxWidth ||
-          !this.depth ||
-          !this.sourceOrganization
-        )
-          return;
-        config = { ...config, los: this.LOS, depth: this.depth };
-        config["min-width"] = this.minWidth;
-        config["max-width"] = this.maxWidth;
-        config["source-organization"] = this.sourceOrganization;
-      }
-      if (this.scheduled) config["cron"] = cron;
-      config["send-email"] = this.eMailNotification;
-      if (!this.id) {
-        data["config"] = config;
-        this.$store
-          .dispatch("importschedule/saveCurrentSchedule", data)
-          .then(response => {
-            const { id } = response.data;
-            displayInfo({
-              title: this.$gettext("Import"),
-              message: this.$gettext("Saved import: #") + id
-            });
-            this.closeDetailview();
-            this.$store
-              .dispatch("importschedule/loadSchedules")
-              .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}`
-            });
-          });
-      } else {
-        this.$store
-          .dispatch("importschedule/updateCurrentSchedule", {
-            data: config,
-            id: this.id
-          })
-          .then(response => {
-            const { id } = response.data;
-            displayInfo({
-              title: this.$gettext("Import"),
-              message: this.$gettext("update import: #") + id
-            });
-            this.closeDetailview();
-            this.$store
-              .dispatch("importschedule/loadSchedules")
-              .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}`
-            });
-          });
-      }
-    },
-    closeDetailview() {
-      this.$store.commit("importschedule/clearCurrentSchedule");
-      this.$store.commit("importschedule/setImportScheduleDetailInvisible");
-    }
-  },
-  IMPORTTYPES: IMPORTTYPES,
-  on: "on",
-  off: "off",
-  FILE: app.$gettext("File"),
-  URL: app.$gettext("URL"),
-  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")
-  }
-};
-</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>
--- a/client/src/components/importschedule/importtypes/Availablefairwaydepth.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-<template>
-  <div>
-    <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
-            @input="urlChanged"
-            class="url form-control"
-            type="url"
-            :value="url"
-          />
-        </div>
-      </div>
-    </div>
-    <div v-if="!url" class="d-flex flex-row">
-      <small
-        ><translate class="text-danger">Please enter a URL</translate></small
-      >
-    </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>
- */
-export default {
-  name: "availablefairwaydepth",
-  props: ["url"],
-  methods: {
-    urlChanged(e) {
-      this.$emit("urlChanged", e.target.value);
-    }
-  }
-};
-</script>
-
-<style></style>
--- a/client/src/components/importschedule/importtypes/Bottleneck.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-<template>
-  <div>
-    <div class="d-flex flex-row">
-      <div class="flex-column mt-3 mr-3 w-100">
-        <template v-if="!directImport">
-          <div class="flex-row text-left">
-            <small class="text-muted"> <translate>URL</translate> </small>
-          </div>
-          <div class="w-100">
-            <input
-              @input="urlChanged"
-              class="url form-control"
-              type="url"
-              :value="url"
-            />
-          </div>
-        </template>
-      </div>
-      <div v-if="false" 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 v-if="!url" class="d-flex flex-row">
-      <small
-        ><translate class="text-danger">Please enter a URL</translate></small
-      >
-    </div>
-    <div class="d-flex flex-row">
-      <div class="flex-column mt-3 mr-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted">
-            <translate>Tolerance for snapping of waterway axis [m]</translate>
-          </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="toleranceChanged"
-            class="tolerance form-control"
-            type="number"
-            min="0"
-            :value="tolerance"
-          />
-        </div>
-        <div v-if="!tolerance" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a tolerance value</translate
-            ></small
-          >
-        </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, 2019 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- * Tom Gottfried <tom.gottfried@intevation.de>
- */
-export default {
-  name: "bottleneckimport",
-  props: ["url", "tolerance", "directImport"],
-  methods: {
-    urlChanged(e) {
-      this.$emit("urlChanged", e.target.value);
-    },
-    toleranceChanged(e) {
-      this.$emit("toleranceChanged", e.target.value);
-    }
-  }
-};
-</script>
-
-<style></style>
--- a/client/src/components/importschedule/importtypes/Distancemarksashore.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-<template>
-  <div>
-    <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
-            @input="urlChanged"
-            class="url form-control"
-            type="url"
-            :value="url"
-          />
-        </div>
-      </div>
-    </div>
-    <div v-if="!url" class="d-flex flex-row">
-      <small
-        ><translate class="text-danger">Please enter a URL</translate></small
-      >
-    </div>
-    <div class="d-flex flex-row">
-      <div class="flex-column mt-3 mr-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>Featuretype</translate> </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="featureTypeChanged"
-            class="featuretype form-control"
-            type="text"
-            :value="featureType"
-          />
-        </div>
-        <div v-if="!featureType" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a Featuretype</translate
-            ></small
-          >
-        </div>
-      </div>
-      <div class="flex-column mt-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>SortBy</translate> </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="sortByChanged"
-            class="sortby form-control"
-            type="text"
-            :value="sortBy"
-          />
-        </div>
-        <div v-if="!sortBy" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter SortBy</translate
-            ></small
-          >
-        </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>
- */
-export default {
-  name: "distancemarksashore",
-  props: ["url", "featureType", "sortBy"],
-  methods: {
-    urlChanged(e) {
-      this.$emit("urlChanged", e.target.value);
-    },
-    featureTypeChanged(e) {
-      this.$emit("featureTypeChanged", e.target.value);
-    },
-    sortByChanged(e) {
-      this.$emit("sortByChanged", e.target.value);
-    }
-  }
-};
-</script>
-
-<style></style>
--- a/client/src/components/importschedule/importtypes/Distancemarksvirtual.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-<template>
-  <div>
-    <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
-            @input="urlChanged"
-            class="url form-control"
-            type="url"
-            :value="url"
-          />
-        </div>
-      </div>
-    </div>
-    <div v-if="!url" class="d-flex flex-row">
-      <small
-        ><translate class="text-danger">Please enter a URL</translate></small
-      >
-    </div>
-    <div class="d-flex flex-row">
-      <div class="flex-column mt-3 mr-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>Username</translate> </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="usernameChanged"
-            class="username form-control"
-            type="text"
-            :value="username"
-          />
-        </div>
-        <div v-if="!username" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a Username</translate
-            ></small
-          >
-        </div>
-      </div>
-      <div class="flex-column mt-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>Password</translate> </small>
-        </div>
-        <div class="w-100 d-flex flex-row">
-          <input
-            @input="passwordChanged"
-            class="pasword form-control"
-            :type="showPassword"
-            :value="password"
-          />
-          <span
-            class="input-group-text ml-2"
-            @click="passwordVisible = !passwordVisible"
-          >
-            <font-awesome-icon
-              :icon="passwordVisible ? 'eye-slash' : 'eye'"
-            ></font-awesome-icon>
-          </span>
-        </div>
-        <div v-if="!password" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a Password</translate
-            ></small
-          >
-        </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>
- */
-export default {
-  name: "distancemarksvirtual",
-  props: ["url", "username", "password"],
-  data() {
-    return {
-      passwordVisible: false
-    };
-  },
-  computed: {
-    showPassword() {
-      if (this.passwordVisible) return "text";
-      return "password";
-    }
-  },
-  methods: {
-    urlChanged(e) {
-      this.$emit("urlChanged", e.target.value);
-    },
-    usernameChanged(e) {
-      this.$emit("usernameChanged", e.target.value);
-    },
-    passwordChanged(e) {
-      this.$emit("passwordChanged", e.target.value);
-    }
-  }
-};
-</script>
-
-<style></style>
--- a/client/src/components/importschedule/importtypes/Fairwaydimensions.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,243 +0,0 @@
-<template>
-  <div>
-    <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
-            @input="urlChanged"
-            class="url form-control"
-            type="url"
-            :value="url"
-          />
-        </div>
-      </div>
-    </div>
-    <div v-if="!url" class="d-flex flex-row">
-      <small
-        ><translate class="text-danger">Please enter a URL</translate></small
-      >
-    </div>
-    <div class="d-flex flex-row">
-      <div class="flex-column mt-3 mr-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>Featuretype</translate> </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="featureTypeChanged"
-            class="featuretype form-control"
-            type="text"
-            :value="featureType"
-          />
-        </div>
-        <div v-if="!featureType" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a Featuretype</translate
-            ></small
-          >
-        </div>
-      </div>
-      <div class="flex-column mt-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>SortBy</translate> </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="sortByChanged"
-            class="sortby form-control"
-            type="text"
-            :value="sortBy"
-          />
-        </div>
-        <div v-if="!sortBy" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter SortBy</translate
-            ></small
-          >
-        </div>
-      </div>
-    </div>
-    <div class="d-flex flex-row">
-      <div class="flex-column mt-3 mr-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>LOS</translate> </small>
-        </div>
-        <div class="w-100">
-          <select v-model="los" class="form-control">
-            <option>1</option>
-            <option>2</option>
-            <option>3</option>
-          </select>
-        </div>
-        <div v-if="!LOS" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a level of service</translate
-            ></small
-          >
-        </div>
-      </div>
-      <div class="flex-column mt-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>Depth</translate> </small>
-        </div>
-        <div class="d-flex flex-row">
-          <input
-            @input="depthChanged"
-            class="depth form-control"
-            type="number"
-            :value="depth"
-          />
-          <div class="ml-2 my-auto">cm</div>
-        </div>
-        <div v-if="!depth" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a depth</translate
-            ></small
-          >
-        </div>
-      </div>
-    </div>
-    <div class="d-flex flex-row">
-      <div class="flex-column mt-3 mr-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>MinWidth</translate> </small>
-        </div>
-        <div class="d-flex flex-row">
-          <input
-            @input="minWidthChanged"
-            class="minwidth form-control"
-            type="number"
-            :value="minWidth"
-          />
-          <div class="ml-2 my-auto">&nbsp;m</div>
-        </div>
-        <div v-if="!minWidth" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a minimum width</translate
-            ></small
-          >
-        </div>
-      </div>
-      <div class="flex-column mt-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>MaxWidth</translate> </small>
-        </div>
-        <div class="d-flex flex-row">
-          <input
-            @input="maxWidthChanged"
-            class="maxwidth form-control"
-            type="number"
-            :value="maxWidth"
-          />
-          <div class="ml-2 my-auto">&nbsp;m</div>
-        </div>
-        <div v-if="!maxWidth" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a maximum width</translate
-            ></small
-          >
-        </div>
-      </div>
-    </div>
-    <div class="d-flex flex-row">
-      <div class="flex-column mt-3 mr-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted">
-            <translate>Source orgranization</translate>
-          </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="sourceOrganizationChanged"
-            class="sourceorganization form-control"
-            type="text"
-            :value="sourceOrganization"
-          />
-        </div>
-        <div v-if="!sourceOrganization" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a source orgranization</translate
-            ></small
-          >
-        </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>
- */
-export default {
-  name: "fairwaydimensions",
-  props: [
-    "url",
-    "featureType",
-    "sortBy",
-    "depth",
-    "LOS",
-    "minWidth",
-    "maxWidth",
-    "sourceOrganization"
-  ],
-  methods: {
-    urlChanged(e) {
-      this.$emit("urlChanged", e.target.value);
-    },
-    featureTypeChanged(e) {
-      this.$emit("featureTypeChanged", e.target.value);
-    },
-    sortByChanged(e) {
-      this.$emit("sortByChanged", e.target.value);
-    },
-    depthChanged(e) {
-      this.$emit("depthChanged", e.target.value * 1);
-    },
-    LOSChanged(e) {
-      this.$emit("LOSChanged", e.target.value * 1);
-    },
-    minWidthChanged(e) {
-      this.$emit("minWidthChanged", e.target.value * 1);
-    },
-    maxWidthChanged(e) {
-      this.$emit("maxWidthChanged", e.target.value * 1);
-    },
-    sourceOrganizationChanged(e) {
-      this.$emit("sourceOrganizationChanged", e.target.value);
-    }
-  },
-  computed: {
-    los: {
-      get() {
-        return this.LOS;
-      },
-      set(value) {
-        this.$emit("LOSChanged", value * 1);
-      }
-    }
-  }
-};
-</script>
-
-<style></style>
--- a/client/src/components/importschedule/importtypes/Gaugemeasurement.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-<template>
-  <div>
-    <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
-            @input="urlChanged"
-            class="url form-control"
-            type="url"
-            :value="url"
-          />
-        </div>
-      </div>
-    </div>
-    <div v-if="!url" class="d-flex flex-row">
-      <small
-        ><translate class="text-danger">Please enter a URL</translate></small
-      >
-    </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>
- */
-export default {
-  name: "gaugemeasurement",
-  props: ["url"],
-  methods: {
-    urlChanged(e) {
-      this.$emit("urlChanged", e.target.value);
-    }
-  }
-};
-</script>
-
-<style></style>
--- a/client/src/components/importschedule/importtypes/Waterwayarea.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-<template>
-  <div>
-    <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
-            @input="urlChanged"
-            class="url form-control"
-            type="url"
-            :value="url"
-          />
-        </div>
-      </div>
-    </div>
-    <div v-if="!url" class="d-flex flex-row">
-      <small
-        ><translate class="text-danger">Please enter a URL</translate></small
-      >
-    </div>
-    <div class="d-flex flex-row">
-      <div class="flex-column mt-3 mr-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>Featuretype</translate> </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="featureTypeChanged"
-            class="featuretype form-control"
-            type="text"
-            :value="featureType"
-          />
-        </div>
-        <div v-if="!featureType" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a Featuretype</translate
-            ></small
-          >
-        </div>
-      </div>
-      <div class="flex-column mt-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>SortBy</translate> </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="sortByChanged"
-            class="sortby form-control"
-            type="text"
-            :value="sortBy"
-          />
-        </div>
-        <div v-if="!sortBy" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter SortBy</translate
-            ></small
-          >
-        </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>
- */
-export default {
-  name: "waterwayarea",
-  props: ["url", "featureType", "sortBy"],
-  methods: {
-    urlChanged(e) {
-      this.$emit("urlChanged", e.target.value);
-    },
-    featureTypeChanged(e) {
-      this.$emit("featureTypeChanged", e.target.value);
-    },
-    sortByChanged(e) {
-      this.$emit("sortByChanged", e.target.value);
-    }
-  }
-};
-</script>
-
-<style></style>
--- a/client/src/components/importschedule/importtypes/Waterwayaxis.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-<template>
-  <div>
-    <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
-            @input="urlChanged"
-            class="url form-control"
-            type="url"
-            :value="url"
-          />
-        </div>
-      </div>
-    </div>
-    <div v-if="!url" class="d-flex flex-row">
-      <small
-        ><translate class="text-danger">Please enter a URL</translate></small
-      >
-    </div>
-    <div class="d-flex flex-row">
-      <div class="flex-column mt-3 mr-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>Featuretype</translate> </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="featureTypeChanged"
-            class="featuretype form-control"
-            type="text"
-            :value="featureType"
-          />
-        </div>
-        <div v-if="!featureType" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a Featuretype</translate
-            ></small
-          >
-        </div>
-      </div>
-      <div class="flex-column mt-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>SortBy</translate> </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="sortByChanged"
-            class="sortby form-control"
-            type="text"
-            :value="sortBy"
-          />
-        </div>
-        <div v-if="!sortBy" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter SortBy</translate
-            ></small
-          >
-        </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>
- */
-export default {
-  name: "waterwayaxis",
-  props: ["url", "featureType", "sortBy"],
-  methods: {
-    urlChanged(e) {
-      this.$emit("urlChanged", e.target.value);
-    },
-    featureTypeChanged(e) {
-      this.$emit("featureTypeChanged", e.target.value);
-    },
-    sortByChanged(e) {
-      this.$emit("sortByChanged", e.target.value);
-    }
-  }
-};
-</script>
-
-<style></style>
--- a/client/src/components/importschedule/importtypes/Waterwaygauges.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-<template>
-  <div>
-    <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
-            @input="urlChanged"
-            class="url form-control"
-            type="url"
-            :value="url"
-          />
-        </div>
-      </div>
-    </div>
-    <div v-if="!url" class="d-flex flex-row">
-      <small
-        ><translate class="text-danger">Please enter a URL</translate></small
-      >
-    </div>
-    <div class="d-flex flex-row">
-      <div class="flex-column mt-3 mr-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>Username</translate> </small>
-        </div>
-        <div class="w-100">
-          <input
-            @input="usernameChanged"
-            class="username form-control"
-            type="text"
-            :value="username"
-          />
-        </div>
-        <div v-if="!username" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a Username</translate
-            ></small
-          >
-        </div>
-      </div>
-      <div class="flex-column mt-3 w-50">
-        <div class="flex-row text-left">
-          <small class="text-muted"> <translate>Password</translate> </small>
-        </div>
-        <div class="w-100 d-flex flex-row">
-          <input
-            @input="passwordChanged"
-            class="password form-control"
-            :type="showPassword"
-            :value="password"
-          />
-          <span
-            class="input-group-text ml-2"
-            @click="passwordVisible = !passwordVisible"
-          >
-            <font-awesome-icon
-              :icon="passwordVisible ? 'eye-slash' : 'eye'"
-            ></font-awesome-icon>
-          </span>
-        </div>
-        <div v-if="!password" class="d-flex flex-row">
-          <small
-            ><translate class="text-danger"
-              >Please enter a Password</translate
-            ></small
-          >
-        </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>
- */
-export default {
-  name: "waterwaygauges",
-  props: ["username", "password", "url"],
-  data() {
-    return {
-      passwordVisible: false
-    };
-  },
-  computed: {
-    showPassword() {
-      if (this.passwordVisible) return "text";
-      return "password";
-    }
-  },
-  methods: {
-    urlChanged(e) {
-      this.$emit("urlChanged", e.target.value);
-    },
-    usernameChanged(e) {
-      this.$emit("usernameChanged", e.target.value);
-    },
-    passwordChanged(e) {
-      this.$emit("passwordChanged", e.target.value);
-    }
-  }
-};
-</script>
-
-<style></style>
--- a/client/src/components/layers/Layers.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/layers/Layers.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -5,22 +5,40 @@
       { expanded: showLayers }
     ]"
   >
-    <div style="width: 18rem">
+    <div class="position-relative" style="width: 18rem; min-height: 350px;">
       <UIBoxHeader
         icon="layer-group"
-        :title="layersLabel"
+        :title="label"
         :closeCallback="close"
+        :actions="[
+          {
+            callback: refreshLayers,
+            icon: sourcesLoading ? 'spinner' : 'sync'
+          }
+        ]"
       />
-      <div class="box-body small">
-        <Layerselect
-          v-for="(layer, index) in layersForLegend"
-          :layerindex="index"
-          :layername="layer.name"
-          :key="layer.name"
-          :isVisible="layer.isVisible"
-          @visibilityToggled="visibilityToggled"
-        ></Layerselect>
+      <div class="small" v-if="openLayersMaps.length">
+        <Layerselect layerId="OPENSTREETMAP" />
+        <Layerselect layerId="INLANDECDIS" />
+        <Layerselect layerId="WATERWAYAREA" />
+        <Layerselect layerId="STRETCHES" />
+        <Layerselect layerId="SECTIONS" />
+        <Layerselect layerId="FAIRWAYDIMENSIONSLOS3" />
+        <Layerselect layerId="FAIRWAYDIMENSIONSLOS2" />
+        <Layerselect layerId="FAIRWAYDIMENSIONSLOS1" />
+        <Layerselect layerId="WATERWAYAXIS" />
+        <Layerselect layerId="WATERWAYPROFILES" />
+        <Layerselect layerId="BOTTLENECKS" />
+        <Layerselect layerId="BOTTLENECKISOLINE" />
+        <Layerselect layerId="DIFFERENCES" />
+        <Layerselect layerId="BOTTLENECKSTATUS" />
+        <Layerselect layerId="BOTTLENECKFAIRWAYAVAILABILITY" />
+        <Layerselect layerId="DATAAVAILABILITY" />
+        <Layerselect layerId="DISTANCEMARKS" />
+        <Layerselect layerId="DISTANCEMARKSAXIS" />
+        <Layerselect layerId="GAUGES" />
       </div>
+      <UISpinnerOverlay v-else style="top: 34px;" />
     </div>
   </div>
 </template>
@@ -40,25 +58,35 @@
  * Thomas Junk <thomas.junk@intevation.de>
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
-import { mapGetters, mapState } from "vuex";
+import { mapState } from "vuex";
+
 export default {
-  name: "layers",
   components: {
     Layerselect: () => import("./Layerselect")
   },
   computed: {
-    ...mapGetters("map", ["layersForLegend"]),
     ...mapState("application", ["showLayers"]),
-    layersLabel() {
-      return this.$gettext("Layers");
+    ...mapState("map", ["openLayersMaps"]),
+    label() {
+      return this.$gettext("Map Layers");
+    },
+    sourcesLoading() {
+      let counter = 0;
+      this.openLayersMaps.forEach(map => {
+        let layers = map.getLayers().getArray();
+        for (let i = 0; i < layers.length; i++) {
+          if (layers[i].getSource().loading) counter++;
+        }
+      });
+      return counter;
     }
   },
   methods: {
     close() {
       this.$store.commit("application/showLayers", false);
     },
-    visibilityToggled(layer) {
-      this.$store.commit("map/toggleVisibility", layer);
+    refreshLayers() {
+      this.$store.dispatch("map/refreshLayers");
     }
   }
 };
--- a/client/src/components/layers/Layerselect.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/layers/Layerselect.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,38 +1,33 @@
 <template>
-  <div>
-    <div class="form-check d-flex flex-row flex-start selection">
+  <div
+    class="d-flex flex-column flex-start px-2 border-bottom"
+    style="border-bottom-style: dashed !important;"
+  >
+    <div class="d-flex">
       <input
-        class="form-check-input"
-        @change="visibilityToggled"
-        :id="layername"
+        v-for="map in openLayersMaps"
+        :key="map.getTarget()"
+        class="mt-1 mr-1"
         type="checkbox"
-        :checked="isVisible"
+        @change="toggle(map)"
+        :checked="isVisible(map)"
       />
-      <LegendElement
-        :layername="layername"
-        :layerindex="layerindex"
-      ></LegendElement>
-      <label
-        class="pointer layername form-check-label"
-        @click="visibilityToggled"
-        >{{ layername }}</label
-      >
+      <LegendElement class="pointer" :layer="layer" @click.native="toggle()" />
+      <label class="pointer layername form-check-label ml-1" @click="toggle()">
+        {{ label }}
+      </label>
     </div>
-    <div v-if="isVisible && isBottleneckIsolineLayer">
-      <img class="rounded my-1 d-block" :src="isolinesLegendImgDataURL" />
+    <div>
+      <div v-if="isVisible() && layer.get('id') === 'BOTTLENECKISOLINE'">
+        <img class="rounded my-1 d-block" :src="isolinesLegendImgDataURL" />
+      </div>
+      <div v-if="isVisible() && layer.get('id') === 'DIFFERENCES'">
+        <img class="rounded my-1 d-block" :src="differencesLegendImgDataURL" />
+      </div>
     </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.
@@ -49,45 +44,101 @@
  * * Bernhard Reiter <bernhard.reiter@intevation.de>
  */
 import { HTTP } from "@/lib/http";
-import { mapState } from "vuex";
-import { LAYERS } from "@/store/map.js";
+import { displayError } from "@/lib/errors";
+import { mapState, mapGetters } from "vuex";
+
 export default {
-  props: ["layername", "layerindex", "isVisible"],
-  name: "layerselect",
   components: {
-    LegendElement: () => import("./LegendElement.vue")
+    LegendElement: () => import("./LegendElement")
   },
+  props: ["layerId"],
   computed: {
-    ...mapState("map", ["isolinesLegendImgDataURL"]),
-    isBottleneckIsolineLayer() {
-      return this.layername == LAYERS.BOTTLENECKISOLINE;
+    ...mapState("map", [
+      "openLayersMaps",
+      "isolinesLegendImgDataURL",
+      "differencesLegendImgDataURL"
+    ]),
+    ...mapGetters("map", ["openLayersMap"]),
+    layer() {
+      return this.openLayersMap().getLayer(this.layerId);
+    },
+    label() {
+      return this.$gettext(this.layer.get("label"));
     }
   },
   methods: {
-    visibilityToggled() {
-      this.$emit("visibilityToggled", this.layerindex);
+    toggle(map) {
+      if (map) {
+        map
+          .getLayer(this.layerId)
+          .setVisible(!map.getLayer(this.layerId).getVisible());
+        if (
+          this.layerId === "GAUGES" ||
+          this.layerId === "STRETCHES" ||
+          this.layerId === "BOTTLENECKS" ||
+          this.layerId === "SECTIONS"
+        ) {
+          map.getLayer("DATAAVAILABILITY").changed();
+        }
+      } else {
+        this.openLayersMaps.forEach(m => {
+          m.getLayer(this.layerId).setVisible(
+            !m.getLayer(this.layerId).getVisible()
+          );
+        });
+      }
+    },
+    isVisible(map) {
+      if (map) {
+        return map.getLayer(this.layerId).getVisible();
+      } else {
+        let isVisible = false;
+        this.openLayersMaps.forEach(m => {
+          if (m.getLayer(this.layerId).getVisible()) {
+            isVisible = true;
+          }
+        });
+        return isVisible;
+      }
+    },
+    loadLegendImage(layer, storeTarget) {
+      HTTP.get(
+        `/internal/wms?REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH=20&HEIGHT=20&LAYER=${layer}&legend_options=columns:4;fontAntiAliasing:true`,
+        {
+          headers: {
+            Accept: "image/png",
+            "X-Gemma-Auth": localStorage.getItem("token")
+          },
+          responseType: "blob"
+        }
+      )
+        .then(response => {
+          const reader = new FileReader();
+          reader.onload = event => {
+            this.$store.commit("map/" + storeTarget, event.target.result);
+          };
+          reader.readAsDataURL(response.data);
+        })
+        .catch(error => {
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${error.response.status}: ${error.response.statusText}`
+          });
+        });
     }
   },
   created() {
-    // fetch legend image for bottleneck isolines
-    // directly read it as dataURL so it is reusable later
-    if (this.isBottleneckIsolineLayer) {
-      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 that = this;
-        const reader = new FileReader();
-        reader.onload = function() {
-          that.$store.commit("map/isolinesLegendImgDataURL", this.result);
-        };
-        reader.readAsDataURL(response.data);
-      });
+    if (this.layer.get("id") === "BOTTLENECKISOLINE") {
+      this.loadLegendImage(
+        "sounding_results_contour_lines_geoserver",
+        "isolinesLegendImgDataURL"
+      );
+    }
+    if (this.layer.get("id") === "DIFFERENCES") {
+      this.loadLegendImage(
+        "sounding_differences",
+        "differencesLegendImgDataURL"
+      );
     }
   }
 };
--- a/client/src/components/layers/LegendElement.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/layers/LegendElement.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -16,32 +16,37 @@
  * Author(s):
  * Thomas Junk <thomas.junk@intevation.de>
  */
-import { mapGetters } from "vuex";
+import { mapState } from "vuex";
 
 import { Map, View } from "ol";
 import Feature from "ol/Feature";
-import { Vector as VectorLayer } from "ol/layer.js";
-import { Vector as VectorSource } from "ol/source.js";
-import LineString from "ol/geom/LineString.js";
+import { Vector as VectorLayer } from "ol/layer";
+import { Vector as VectorSource } from "ol/source";
+import LineString from "ol/geom/LineString";
 import Point from "ol/geom/Point";
+import { HTTP } from "@/lib/http";
 
 export default {
-  name: "legendelement",
-  props: ["layername", "layerindex"],
+  props: ["layer"],
   data: function() {
     return {
-      myMap: null,
-      mapLayer: null
+      myMap: null
     };
   },
   computed: {
-    ...mapGetters("map", ["getLayerByName"]),
+    ...mapState("map", ["layers"]),
     id() {
-      return "legendelement" + this.layerindex;
+      return (
+        "legendelement-" +
+        this.layer
+          .get("label")
+          .toLowerCase()
+          .replace(/\s/g, "")
+      );
     },
     mstyle() {
-      if (this.mapLayer && this.mapLayer.data.getStyle) {
-        return this.mapLayer.data.getStyle();
+      if (this.layer && this.layer.getStyle) {
+        return this.layer.getStyle();
       }
     }
   },
@@ -57,11 +62,38 @@
     }
   },
   mounted() {
-    this.mapLayer = this.getLayerByName(this.layername);
-    if (this.mapLayer.data.getType() == "VECTOR") {
+    if (this.layer.getType() == "VECTOR") {
       this.initMap();
     } else {
-      // TODO other tiles
+      if (
+        this.layer.get("id") === "OPENSTREETMAP" ||
+        this.layer.get("id") === "INLANDECDIS" ||
+        this.layer.get("id") === "BOTTLENECKISOLINE" ||
+        this.layer.get("id") === "DIFFERENCES"
+      ) {
+        // TODO: Do something useful?
+        return;
+      }
+      let img = document.createElement("img");
+      img.setAttribute("style", "margin: 0 auto;display: flex;");
+      if (this.layer.get("id") === "DISTANCEMARKSAXIS") {
+        img.setAttribute("src", require("@/assets/distancemarks-axis.png"));
+      } else {
+        // for simple WMS legends.
+        let url =
+          `/internal/wms?REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH=20&HEIGHT=20&LAYER=` +
+          this.layer.getSource().getParams().LAYERS +
+          `&legend_options=columns:4;fontAntiAliasing:true`;
+        HTTP.get(url, {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token")
+          },
+          responseType: "blob"
+        }).then(response => {
+          img.setAttribute("src", URL.createObjectURL(response.data));
+        });
+      }
+      this.$el.appendChild(img);
     }
   },
   methods: {
@@ -81,28 +113,29 @@
       });
     },
     createVectorLayer() {
-      let mapStyle = this.mapLayer.data.getStyle();
+      let mapStyle = this.layer.getStyle();
 
       let feature = new Feature({
-        geometry: new LineString([[-1, 0.5], [0, 0], [0.7, 0], [1.3, -0.7]])
+        geometry: new LineString([[-1, -1], [0, 0], [1, 1]])
       });
 
       // special case if we need to call the style function with a special
       // parameter or to detect a point layer
-      if (this.mapLayer["forLegendStyle"]) {
-        if (this.mapLayer.forLegendStyle.point) {
+      let legendStyle = this.layer.get("forLegendStyle");
+      if (legendStyle) {
+        if (legendStyle) {
           feature.setGeometry(new Point([0, 0]));
         }
-        mapStyle = this.mapLayer.data.getStyleFunction()(
+        mapStyle = this.layer.getStyleFunction()(
           feature,
-          this.mapLayer.forLegendStyle.resolution,
+          legendStyle.resolution,
           true
         );
       }
 
       // we could add extra properties here, if they are needed for
       // the styling function in the future. An idea is to extend the
-      // this.mapLayer["forLegendStyle"] for it.
+      // this.layer["forLegendStyle"] for it.
       // FIXME, this is a special case for the Fairway Dimensions style
       feature.set("level_of_service", "");
       return new VectorLayer({
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/map/Map.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,308 @@
+<template>
+  <div
+    :id="'map-' + paneId"
+    :class="['map', { nocursor: this.hasActiveInteractions }]"
+  >
+    <Zoom :map="map" />
+  </div>
+</template>
+
+<style lang="sass" scoped>
+.map
+  width: 100%
+  height: 100%
+  background-color: #eee
+  background-image: linear-gradient(45deg, #e8e8e8 25%, transparent 25%, transparent 75%, #e8e8e8 75%, #e8e8e8), linear-gradient(45deg, #e8e8e8 25%, transparent 25%, transparent 75%, #e8e8e8 75%, #e8e8e8)
+  background-size: 20px 20px
+  background-position: 0 0, 10px 10px
+
+  &.nocursor
+    cursor: 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, 2019 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 { mapState } from "vuex";
+import { Map, View } from "ol";
+import { Stroke, Style, Fill } from "ol/style";
+import { displayError } from "@/lib/errors";
+import { pane } from "@/lib/mixins";
+import layers from "@/components/map/layers";
+import "ol/ol.css";
+
+/* for the sake of debugging */
+/* eslint-disable no-console */
+export default {
+  mixins: [pane],
+  components: {
+    Zoom: () => import("@/components/map/Zoom")
+  },
+  data() {
+    return {
+      map: null
+    };
+  },
+  computed: {
+    ...mapState("map", ["initialLoad", "extent", "syncedMaps", "syncedView"]),
+    ...mapState("bottlenecks", ["selectedSurvey"]),
+    ...mapState("fairwayprofile", ["additionalSurvey"]),
+    ...mapState("application", ["paneSetup", "paneRotate"]),
+    ...mapState("imports", ["selectedStretchId", "selectedSectionId"]),
+    layers() {
+      return layers(this.paneId);
+    },
+    hasActiveInteractions() {
+      return (
+        this.map &&
+        this.map
+          .getInteractions()
+          .getArray()
+          .filter(
+            i =>
+              ["linetool", "polygontool", "cuttool"].includes(i.get("id")) &&
+              i.getActive()
+          ).length
+      );
+    }
+  },
+  watch: {
+    paneSetup() {
+      this.$nextTick(() => this.map.updateSize());
+    },
+    paneRotate() {
+      this.$nextTick(() => this.map.updateSize());
+    },
+    syncedMaps(syncedMaps) {
+      if (syncedMaps.includes(this.paneId) || this.paneId === "main") {
+        this.map.setView(this.syncedView);
+      } else {
+        this.map.setView(
+          new View({
+            center: [this.extent.lon, this.extent.lat],
+            minZoom: 5, // restrict zooming out to ~size of Europe for width 1000px
+            zoom: this.extent.zoom,
+            projection: "EPSG:3857"
+          })
+        );
+      }
+    },
+    selectedSurvey(survey) {
+      if (this.paneId === "main") {
+        if (survey) {
+          this.updateBottleneckFilter(survey.bottleneck_id, survey.date_info);
+        } else {
+          this.updateBottleneckFilter("does_not_exist", "1999-10-01");
+        }
+      }
+    },
+    additionalSurvey(survey) {
+      if (this.paneId === "compare-survey") {
+        if (survey) {
+          this.updateBottleneckFilter(survey.bottleneck_id, survey.date_info);
+        } else {
+          this.updateBottleneckFilter("does_not_exist", "1999-10-01");
+        }
+      }
+    },
+    selectedStretchId(id) {
+      this.layers
+        .get("STRETCHES")
+        .getSource()
+        .getFeatures()
+        .forEach(f => {
+          f.set("highlighted", false);
+          if (id === f.getId()) {
+            f.set("highlighted", true);
+          }
+        });
+    },
+    selectedSectionId(id) {
+      this.layers
+        .get("SECTIONS")
+        .getSource()
+        .getFeatures()
+        .forEach(f => {
+          f.set("highlighted", false);
+          if (id === f.getId()) {
+            f.set("highlighted", true);
+          }
+        });
+    }
+  },
+  methods: {
+    updateBottleneckFilter(bottleneck_id, datestr) {
+      const exists = bottleneck_id != "does_not_exist";
+
+      if (exists) {
+        this.layers
+          .get("BOTTLENECKISOLINE")
+          .getSource()
+          .updateParams({
+            cql_filter: `date_info='${datestr}' AND bottleneck_id='${bottleneck_id}'`
+          });
+      }
+      this.layers.get("BOTTLENECKISOLINE").setVisible(exists);
+    },
+    initMap() {
+      if (!this.syncedView) {
+        this.$store.commit(
+          "map/syncedView",
+          new View({
+            center: [this.extent.lon, this.extent.lat],
+            minZoom: 5, // restrict zooming out to ~size of Europe for width 1000px
+            zoom: this.extent.zoom,
+            projection: "EPSG:3857"
+          })
+        );
+      }
+
+      // move to user specific default extent if map loads for the first time
+      // checking initialLoad will be obsolete once we abandoned the separated admin context
+      if (this.initialLoad) {
+        this.$store.commit("map/initialLoad", false);
+        var currentUser = this.$store.state.user.user;
+        HTTP.get("/users/" + currentUser, {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token"),
+            "Content-type": "text/xml; charset=UTF-8"
+          }
+        })
+          .then(response => {
+            this.mountMap();
+            this.$store.dispatch("map/moveToBoundingBox", {
+              boundingBox: [
+                response.data.extent.x1,
+                response.data.extent.y1,
+                response.data.extent.x2,
+                response.data.extent.y2
+              ],
+              zoom: 17,
+              preventZoomOut: true,
+              duration: 0
+            });
+          })
+          .catch(error => {
+            const { status, data } = error.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          });
+      } else {
+        this.mountMap();
+      }
+    },
+    mountMap() {
+      this.map = new Map({
+        layers: this.layers.config,
+        target: "map-" + this.paneId,
+        controls: [],
+        view:
+          this.syncedMaps.includes(this.paneId) || this.paneId === "main"
+            ? this.syncedView
+            : new View({
+                center: [this.extent.lon, this.extent.lat],
+                minZoom: 5,
+                zoom: this.extent.zoom,
+                projection: "EPSG:3857"
+              })
+      });
+      this.map.getLayer = id => this.layers.get(id);
+
+      // store map position on every move
+      // will be obsolete once we abandoned the separated admin context
+      this.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", this.map);
+      this.$store.dispatch("map/initIdentifyTool", this.map);
+    }
+  },
+  mounted() {
+    // ToDo set path to correct endpoint in order to retrieve an OSM URL
+    HTTP.get("/system/config", {
+      headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+    })
+      .then(response => {
+        if (response.data["osm-url"]) {
+          this.layers
+            .get("OPENSTREETMAP")
+            .getSource()
+            .setUrl(response.data["osm-url"]);
+        }
+        this.initMap();
+
+        if (this.selectedSurvey && this.paneId === "main") {
+          this.updateBottleneckFilter(
+            this.selectedSurvey.bottleneck_id,
+            this.selectedSurvey.date_info
+          );
+        }
+        if (this.additionalSurvey && this.paneId === "compare-survey") {
+          this.updateBottleneckFilter(
+            this.additionalSurvey.bottleneck_id,
+            this.additionalSurvey.date_info
+          );
+        }
+        // load configured bottleneck colors
+        HTTP.get("/system/style/Bottlenecks/stroke", {
+          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+        })
+          .then(response => {
+            let btlnStrokeC = response.data.code;
+            HTTP.get("/system/style/Bottlenecks/fill", {
+              headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+            })
+              .then(response => {
+                let btlnFillC = response.data.code;
+                var newStyle = new Style({
+                  stroke: new Stroke({
+                    color: btlnStrokeC,
+                    width: 4
+                  }),
+                  fill: new Fill({
+                    color: btlnFillC
+                  })
+                });
+                this.layers.get("BOTTLENECKS").setStyle(newStyle);
+              })
+              .catch(error => {
+                console.log(error);
+              });
+          })
+          .catch(error => {
+            console.log(error);
+          });
+      })
+      .catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: "Backend Error",
+          message: `${status}: ${data.message || data}`
+        });
+      });
+  },
+  destroyed() {
+    this.$store.commit("map/removeOpenLayersMap", this.map);
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/map/MapPopup.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,255 @@
+<template>
+  <div class="map-popup rounded" ref="map-popup">
+    <UIBoxHeader :title="title" :closeCallback="close" small />
+    <div class="p-1 small text-nowrap" style="margin-top: -0.25rem">
+      <div
+        class="d-flex flex-nowrap justify-content-between align-items-center mt-1"
+        v-if="bottlenecks.length"
+        v-for="bottleneck in bottlenecks"
+        :key="bottleneck.get('objnam')"
+      >
+        <div class="mr-2">
+          <font-awesome-icon icon="ship" class="mr-1" fixed-width />
+          {{ bottleneck.get("objnam") }}
+        </div>
+        <div>
+          <button
+            class="btn btn-xs btn-info"
+            v-tooltip="surveysLabel"
+            @click="openSurveys(bottleneck)"
+          >
+            <font-awesome-icon icon="chart-area" fixed-width />
+          </button>
+          <button
+            class="btn btn-xs btn-info ml-1"
+            v-tooltip="fairwayAvailabilityLabel"
+            @click="openFairwayAvailabilityForBottleneck(bottleneck)"
+          >
+            <font-awesome-icon icon="chart-line" fixed-width />
+          </button>
+        </div>
+      </div>
+
+      <div
+        class="d-flex flex-nowrap justify-content-between align-items-center mt-1"
+        v-if="gauges.length"
+        v-for="gauge in gauges"
+        :key="gauge.get('objname')"
+      >
+        <div class="mr-2">
+          <font-awesome-icon icon="ruler-vertical" class="mr-1" fixed-width />
+          {{ gauge.get("objname") }}
+        </div>
+        <button
+          class="btn btn-xs btn-info"
+          v-tooltip="waterlevelsLabel"
+          @click="openGauges(gauge)"
+        >
+          <font-awesome-icon icon="ruler-vertical" fixed-width />
+        </button>
+      </div>
+
+      <div
+        class="d-flex flex-nowrap justify-content-between align-items-center mt-1"
+        v-if="stretches.length"
+        v-for="stretch in stretches"
+        :key="stretch.get('objnam')"
+      >
+        <div class="mr-2">
+          <font-awesome-icon icon="road" class="mr-1" fixed-width />
+          {{ stretch.get("objnam") }}
+        </div>
+        <button
+          class="btn btn-xs btn-info"
+          v-tooltip="fairwayAvailabilityLabel"
+          @click="openFairwayAvailabilityForStretch(stretch)"
+        >
+          <font-awesome-icon icon="chart-line" fixed-width />
+        </button>
+      </div>
+
+      <div
+        class="d-flex flex-nowrap justify-content-between align-items-center mt-1"
+        v-if="sections.length"
+        v-for="section in sections"
+        :key="section.get('objnam')"
+      >
+        <div class="mr-2">
+          <font-awesome-icon icon="road" class="mr-1" fixed-width />
+          {{ section.get("objnam") }}
+        </div>
+        <button
+          class="btn btn-xs btn-info"
+          v-tooltip="fairwayAvailabilityLabel"
+          @click="openFairwayAvailabilityForSection(section)"
+        >
+          <font-awesome-icon icon="chart-line" fixed-width />
+        </button>
+      </div>
+    </div>
+    <div
+      v-if="identifiedCoordinates"
+      class="border-top text-muted p-1 coordinates"
+    >
+      Lat: {{ identifiedCoordinates[1].toFixed(8) }}, Lon:
+      {{ identifiedCoordinates[0].toFixed(8) }}
+    </div>
+  </div>
+</template>
+
+<style lang="sass">
+.map-popup
+  position: absolute
+  background: #fff
+  min-width: 200px
+  min-height: 85px
+  box-shadow: 0 0.1rem 0.5rem rgba(0, 0, 0, 0.2)
+  border-top-left-radius: 0 !important
+  margin-left: 10px
+  &::before
+    content: ""
+    position: absolute
+    top: 0
+    left: -10px
+    border: 5px solid transparent
+    border-top: 5px solid white
+    border-right: 5px solid white
+  .coordinates
+    font-size: 70%
+</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>
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState } from "vuex";
+import Overlay from "ol/Overlay.js";
+import { getCenter } from "ol/extent";
+
+export default {
+  computed: {
+    ...mapState("map", [
+      "mapPopup",
+      "identifiedFeatures",
+      "identifiedCoordinates"
+    ]),
+    title() {
+      return this.$gettext("Identified Features");
+    },
+    bottlenecks() {
+      return this.identifiedFeatures.filter(f =>
+        /^bottlenecks/.test(f.getId())
+      );
+    },
+    gauges() {
+      return this.identifiedFeatures.filter(f => /^gauges/.test(f.getId()));
+    },
+    stretches() {
+      return this.identifiedFeatures.filter(f => /^stretches/.test(f.getId()));
+    },
+    sections() {
+      return this.identifiedFeatures.filter(f => /^sections/.test(f.getId()));
+    },
+    surveysLabel() {
+      return this.$gettext("Surveys");
+    },
+    fairwayAvailabilityLabel() {
+      return this.$gettext("Fairway Availability");
+    },
+    waterlevelsLabel() {
+      return this.$gettext("Waterlevels");
+    }
+  },
+  methods: {
+    close() {
+      this.mapPopup.setPosition(undefined);
+    },
+    openSurveys(bottleneck) {
+      this.$store.commit("application/showProfiles", true);
+      this.$store.dispatch(
+        "bottlenecks/setSelectedBottleneck",
+        bottleneck.get("objnam")
+      );
+      this.$store.dispatch("map/moveMap", {
+        coordinates: getCenter(
+          bottleneck
+            .getGeometry()
+            .clone()
+            .transform("EPSG:3857", "EPSG:4326")
+            .getExtent()
+        ),
+        zoom: 17,
+        preventZoomOut: true
+      });
+      this.close();
+    },
+    openGauges(gauge) {
+      this.$store.commit("application/showGauges", true);
+      this.$store.dispatch("gauges/selectedGaugeISRS", gauge.get("isrs_code"));
+      this.close();
+    },
+    openFairwayAvailability() {
+      this.$store.commit("application/showFairwayDepth", true);
+      this.close();
+    },
+    openFairwayAvailabilityForBottleneck(bottleneck) {
+      this.$store.commit("fairwayavailability/type", "bottlenecks");
+      this.$store.dispatch(
+        "bottlenecks/setSelectedBottleneck",
+        bottleneck.get("objnam")
+      );
+      this.$store.dispatch("map/moveMap", {
+        coordinates: getCenter(
+          bottleneck
+            .getGeometry()
+            .clone()
+            .transform("EPSG:3857", "EPSG:4326")
+            .getExtent()
+        ),
+        zoom: 17,
+        preventZoomOut: true
+      });
+      this.openFairwayAvailability();
+    },
+    openFairwayAvailabilityForStretch(stretch) {
+      this.$store.commit("fairwayavailability/type", "stretches");
+      this.$store.commit("imports/selectedStretchId", stretch.getId());
+      this.$store.dispatch("map/moveToFeauture", {
+        feature: stretch,
+        zoom: 17
+      });
+      this.openFairwayAvailability();
+    },
+    openFairwayAvailabilityForSection(section) {
+      this.$store.commit("fairwayavailability/type", "sections");
+      this.$store.commit("imports/selectedSectionId", section.getId());
+      this.$store.dispatch("map/moveToFeauture", {
+        feature: section,
+        zoom: 17
+      });
+      this.openFairwayAvailability();
+    }
+  },
+  mounted() {
+    const mapPopup = new Overlay({
+      element: this.$refs["map-popup"],
+      autoPan: true,
+      autoPanAnimation: {
+        duration: 250
+      }
+    });
+    this.$store.commit("map/mapPopup", mapPopup);
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/map/Zoom.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,72 @@
+<template>
+  <div class="zoom-buttons shadow-xs">
+    <button
+      class="zoom-button border-0 bg-white rounded-left ui-element"
+      @click="zoomOut"
+    >
+      <font-awesome-icon icon="minus" />
+    </button>
+    <button
+      class="zoom-button border-0 bg-white rounded-right ui-element border-right"
+      @click="zoomIn"
+    >
+      <font-awesome-icon icon="plus" />
+    </button>
+  </div>
+</template>
+
+<style lang="sass">
+.zoom-buttons
+  position: absolute
+  z-index: 1
+  bottom: $small-offset
+  left: 50%
+  margin-left: -$icon-width
+  margin-bottom: 0
+  transition: margin-bottom 0.3s
+
+  .zoom-button
+    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>
+ */
+export default {
+  props: ["map"],
+  computed: {
+    zoomLevel: {
+      get() {
+        return this.map.getView().getZoom();
+      },
+      set(value) {
+        this.map.getView().animate({ zoom: value, duration: 300 });
+      }
+    }
+  },
+  methods: {
+    zoomIn() {
+      this.zoomLevel = this.zoomLevel + 1;
+    },
+    zoomOut() {
+      this.zoomLevel = this.zoomLevel - 1;
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/map/layers.js	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,644 @@
+import TileWMS from "ol/source/TileWMS";
+import {
+  Tile as TileLayer,
+  Vector as VectorLayer,
+  Image as ImageLayer
+} from "ol/layer";
+import { Icon, Stroke, Style } from "ol/style";
+import VectorSource from "ol/source/Vector";
+import { ImageWMS as ImageSource } from "ol/source";
+import Point from "ol/geom/Point";
+import { bbox as bboxStrategy } from "ol/loadingstrategy";
+import { WFS, GeoJSON } from "ol/format";
+import OSM from "ol/source/OSM";
+import { equalTo } from "ol/format/filter";
+import { HTTP } from "@/lib/http";
+import styleFactory from "./styles";
+import store from "@/store/index";
+
+const buildVectorLoader = (
+  featureRequestOptions,
+  vectorSource,
+  bboxStrategyDisabled,
+  featurePostProcessor
+) => {
+  // build a function to be used for VectorSource.setLoader()
+  // make use of WFS().writeGetFeature to build the request
+  // and use our HTTP library to actually do it
+  // NOTE: the geometryName has to be given in featureRequestOptions if
+  // bboxStrategy (default) is used
+  featureRequestOptions.featureNS = "gemma";
+  featureRequestOptions.featurePrefix = "gemma";
+  featureRequestOptions.outputFormat = "application/json";
+  return (extent, resolution, projection) => {
+    if (!bboxStrategyDisabled) {
+      featureRequestOptions.bbox = extent;
+    }
+    featureRequestOptions.srsName = projection.getCode();
+    HTTP.post(
+      "/internal/wfs",
+      new XMLSerializer().serializeToString(
+        new WFS().writeGetFeature(featureRequestOptions)
+      ),
+      {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-type": "text/xml; charset=UTF-8"
+        }
+      }
+    )
+      .then(response => {
+        const features = new GeoJSON().readFeatures(
+          JSON.stringify(response.data)
+        );
+        if (featurePostProcessor) {
+          features.map(f => featurePostProcessor(f, store, features));
+        }
+        vectorSource.addFeatures(features);
+      })
+      .catch(() => {
+        vectorSource.removeLoadedExtent(extent);
+      });
+  };
+};
+
+// SHARED LAYERS:
+// DRAW- and CUTLAYER are shared across maps. E.g. you want to see the cross cut
+// arrow on both maps when comparing surveys. So we don't need to initialize a
+// new VectorLayer object for each map. Instead we use these two constants so
+// that all maps use the same object.
+const DRAWLAYER = new VectorLayer({
+  id: "DRAWTOOL",
+  label: "Draw Tool",
+  visible: true,
+  source: new VectorSource({ wrapX: false }),
+  style: function(feature) {
+    // adapted from OpenLayer's LineString Arrow Example
+    var geometry = feature.getGeometry();
+    var styles = [
+      // linestring
+      new Style({
+        stroke: new Stroke({
+          color: "#369aca",
+          width: 2
+        })
+      })
+    ];
+
+    if (geometry.getType() === "LineString") {
+      geometry.forEachSegment(function(start, end) {
+        var dx = end[0] - start[0];
+        var dy = end[1] - start[1];
+        var rotation = Math.atan2(dy, dx);
+        // arrows
+        styles.push(
+          new Style({
+            geometry: new Point(end),
+            image: new Icon({
+              // we need to make sure the image is loaded by Vue Loader
+              src: require("@/assets/linestring_arrow.png"),
+              // fiddling with the anchor's y value does not help to
+              // position the image more centered on the line ending, as the
+              // default line style seems to be slightly uncentered in the
+              // anti-aliasing, but the image is not placed with subpixel
+              // precision
+              anchor: [0.75, 0.5],
+              rotateWithView: true,
+              rotation: -rotation
+            })
+          })
+        );
+      });
+    }
+    return styles;
+  }
+});
+
+const CUTLAYER = new VectorLayer({
+  id: "CUTTOOL",
+  label: "Cut Tool",
+  visible: true,
+  source: new VectorSource({ wrapX: false }),
+  style: function(feature) {
+    // adapted from OpenLayer's LineString Arrow Example
+    var geometry = feature.getGeometry();
+    var styles = [
+      // linestring
+      new Style({
+        stroke: new Stroke({
+          color: "#333333",
+          width: 2,
+          lineDash: [7, 7]
+        })
+      })
+    ];
+
+    if (geometry.getType() === "LineString") {
+      geometry.forEachSegment(function(start, end) {
+        var dx = end[0] - start[0];
+        var dy = end[1] - start[1];
+        var rotation = Math.atan2(dy, dx);
+        // arrows
+        styles.push(
+          new Style({
+            geometry: new Point(end),
+            image: new Icon({
+              // we need to make sure the image is loaded by Vue Loader
+              src: require("@/assets/linestring_arrow_grey.png"),
+              // fiddling with the anchor's y value does not help to
+              // position the image more centered on the line ending, as the
+              // default line style seems to be slightly uncentered in the
+              // anti-aliasing, but the image is not placed with subpixel
+              // precision
+              anchor: [0.75, 0.5],
+              rotateWithView: true,
+              rotation: -rotation
+            })
+          })
+        );
+      });
+    }
+    return styles;
+  }
+});
+
+export default function(mapId) {
+  const styles = styleFactory(mapId);
+  // Shared feature source for layers:
+  // BOTTLENECKS, BOTTLENECKSTATUS and BOTTLENECKFAIRWAYAVAILABILITY
+  // Reduces bottlenecks_geoserver requests and number of stored feature objects.
+  const bottlenecksSource = new VectorSource({ strategy: bboxStrategy });
+  bottlenecksSource.setLoader(
+    buildVectorLoader(
+      {
+        featureTypes: ["bottlenecks_geoserver"],
+        geometryName: "area"
+      },
+      bottlenecksSource,
+      false,
+      async (f, store) => {
+        if (f.get("fa_critical")) {
+          // look for fairway availability data in store. If present and
+          // not older than 15 min use it or fetch new data and store it.
+          let data = store.getters["fairwayavailability/fwLNWLOverviewData"](f);
+          if (
+            data &&
+            new Date().getTime() - data.createdAt.getTime() < 900000
+          ) {
+            f.set("fa_data", data.data);
+          } else {
+            let date = new Date();
+            data = await store.dispatch(
+              "fairwayavailability/loadAvailableFairwayDepthLNWLForMap",
+              {
+                feature: f,
+                from: date.toISOString().split("T")[0],
+                to: date.toISOString().split("T")[0],
+                frequency: "monthly",
+                LOS: 3
+              }
+            );
+            if (data) {
+              store.commit("fairwayavailability/addFwLNWLOverviewData", {
+                feature: f,
+                data,
+                createdAt: new Date()
+              });
+              f.set("fa_data", data);
+            }
+          }
+        }
+        return f;
+      }
+    )
+  );
+
+  return {
+    get(id) {
+      return this.config.find(l => l.get("id") === id);
+    },
+    config: [
+      new TileLayer({
+        id: "OPENSTREETMAP",
+        label: "Open Streetmap",
+        visible: true,
+        source: new OSM()
+      }),
+      new ImageLayer({
+        id: "INLANDECDIS",
+        label: "Inland ECDIS chart Danube",
+        visible: true,
+        source: new ImageSource({
+          preload: 1,
+          url: "https://service.d4d-portal.info/wms/",
+          crossOrigin: "anonymous",
+          params: { LAYERS: "d4d", VERSION: "1.1.1", TILED: true }
+        })
+      }),
+      new ImageLayer({
+        id: "WATERWAYAREA",
+        label: "Waterway Area",
+        maxResolution: 100,
+        minResolution: 0,
+        source: new ImageSource({
+          url: window.location.origin + "/api/internal/wms",
+          params: { LAYERS: "waterway_area", VERSION: "1.1.1", TILED: true },
+          imageLoadFunction: function(tile, src) {
+            HTTP.get(src, {
+              headers: {
+                "X-Gemma-Auth": localStorage.getItem("token")
+              },
+              responseType: "blob"
+            }).then(response => {
+              tile.getImage().src = URL.createObjectURL(response.data);
+            });
+          } // TODO  tile.setState(TileState.ERROR);
+        })
+      }),
+      (function() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["stretches_geoserver"],
+              geometryName: "area"
+            },
+            source,
+            true,
+            (f, store) => {
+              if (f.getId() === store.state.imports.selectedStretchId) {
+                f.set("highlighted", true);
+              }
+              return f;
+            }
+          )
+        );
+        return new VectorLayer({
+          id: "STRETCHES",
+          label: "Stretches",
+          visible: false,
+          style: styles.stretches,
+          source
+        });
+      })(),
+      (function() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["sections_geoserver"],
+              geometryName: "area"
+            },
+            source,
+            true,
+            (f, store) => {
+              if (f.getId() === store.state.imports.selectedSectionId) {
+                f.set("highlighted", true);
+              }
+              return f;
+            }
+          )
+        );
+        return new VectorLayer({
+          id: "SECTIONS",
+          label: "Sections",
+          visible: false,
+          style: styles.sections,
+          source
+        });
+      })(),
+      (function() {
+        const source = new VectorSource();
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["fairway_dimensions"],
+              filter: equalTo("level_of_service", 1)
+            },
+            source,
+            true
+          )
+        );
+        return new VectorLayer({
+          id: "FAIRWAYDIMENSIONSLOS1",
+          label: "LOS 1 Fairway Dimensions",
+          visible: false,
+          style: styles.fwd1,
+          maxResolution: 80,
+          minResolution: 0,
+          source
+        });
+      })(),
+      (function() {
+        const source = new VectorSource();
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["fairway_dimensions"],
+              filter: equalTo("level_of_service", 2)
+            },
+            source,
+            true
+          )
+        );
+        return new VectorLayer({
+          id: "FAIRWAYDIMENSIONSLOS2",
+          label: "LOS 2 Fairway Dimensions",
+          visible: false,
+          style: styles.fwd2,
+          maxResolution: 80,
+          minResolution: 0,
+          source
+        });
+      })(),
+      (function() {
+        const source = new VectorSource();
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["fairway_dimensions"],
+              filter: equalTo("level_of_service", 3)
+            },
+            source,
+            true
+          )
+        );
+        return new VectorLayer({
+          id: "FAIRWAYDIMENSIONSLOS3",
+          label: "LOS 3 Fairway Dimensions",
+          visible: true,
+          style: styles.fwd3,
+          maxResolution: 80,
+          minResolution: 0,
+          source
+        });
+      })(),
+      new ImageLayer({
+        id: "WATERWAYAXIS",
+        label: "Waterway Axis",
+        source: new ImageSource({
+          url: window.location.origin + "/api/internal/wms",
+          params: { LAYERS: "waterway_axis", VERSION: "1.1.1", TILED: true },
+          imageLoadFunction: function(tile, src) {
+            HTTP.get(src, {
+              headers: {
+                "X-Gemma-Auth": localStorage.getItem("token")
+              },
+              responseType: "blob"
+            }).then(response => {
+              tile.getImage().src = URL.createObjectURL(response.data);
+            });
+          } // TODO  tile.setState(TileState.ERROR);
+        })
+      }),
+      (function() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["waterway_profiles"],
+              geometryName: "geom"
+            },
+            source
+          )
+        );
+        return new VectorLayer({
+          id: "WATERWAYPROFILES",
+          label: "Waterway Profiles",
+          visible: true,
+          style: new Style({
+            stroke: new Stroke({
+              color: "rgba(0, 0, 255, .5)",
+              lineDash: [5, 5],
+              width: 2
+            })
+          }),
+          maxResolution: 2.5,
+          minResolution: 0,
+          source
+        });
+      })(),
+      (function() {
+        return new VectorLayer({
+          id: "BOTTLENECKS",
+          label: "Bottlenecks",
+          visible: true,
+          style: styles.bottleneck,
+          source: bottlenecksSource
+        });
+      })(),
+      new TileLayer({
+        id: "BOTTLENECKISOLINE",
+        label: "Bottleneck isolines",
+        visible: false,
+        source: new TileWMS({
+          preload: 0,
+          projection: "EPSG:3857",
+          url: window.location.origin + "/api/internal/wms",
+          params: {
+            LAYERS: "sounding_results_contour_lines_geoserver",
+            VERSION: "1.1.1",
+            TILED: true
+          },
+          tileLoadFunction: function(tile, src) {
+            // console.log("calling for", tile, src);
+            HTTP.get(src, {
+              headers: {
+                "X-Gemma-Auth": localStorage.getItem("token")
+              },
+              responseType: "blob"
+            }).then(response => {
+              tile.getImage().src = URL.createObjectURL(response.data);
+            });
+          } // TODO  tile.setState(TileState.ERROR);
+        })
+      }),
+      new TileLayer({
+        id: "DIFFERENCES",
+        label: "Bottleneck Differences",
+        visible: false,
+        source: new TileWMS({
+          preload: 0,
+          projection: "EPSG:3857",
+          url: window.location.origin + "/api/internal/wms",
+          params: {
+            LAYERS: "sounding_differences",
+            VERSION: "1.1.1",
+            TILED: true
+          },
+          tileLoadFunction: function(tile, src) {
+            // console.log("calling for", tile, src);
+            HTTP.get(src, {
+              headers: {
+                "X-Gemma-Auth": localStorage.getItem("token")
+              },
+              responseType: "blob"
+            }).then(response => {
+              tile.getImage().src = URL.createObjectURL(response.data);
+            });
+          } // TODO  tile.setState(TileState.ERROR);
+        })
+      }),
+      (function() {
+        return new VectorLayer({
+          id: "BOTTLENECKSTATUS",
+          label: "Critical Bottlenecks",
+          forLegendStyle: { point: true, resolution: 16 },
+          visible: true,
+          zIndex: 1,
+          style: styles.bottleneckStatus,
+          source: bottlenecksSource
+        });
+      })(),
+      (function() {
+        return new VectorLayer({
+          id: "BOTTLENECKFAIRWAYAVAILABILITY",
+          label: "Bottlenecks Fairway Availability",
+          forLegendStyle: { point: true, resolution: 16 },
+          visible: false,
+          zIndex: 1,
+          style: styles.bottleneckFairwayAvailability,
+          source: bottlenecksSource
+        });
+      })(),
+      (function() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: [
+                "bottlenecks_geoserver",
+                "gauges_geoserver",
+                "stretches_geoserver",
+                "sections_geoserver"
+              ]
+            },
+            source,
+            true,
+            // since we don't use bbox strategy, features will contain all features and we can use it
+            // to find reference gauges for bottlenecks, yeah!
+            async (f, store, features) => {
+              // attach reference gauge to bottleneck
+              if (f.getId().indexOf("bottlenecks") > -1) {
+                f.set(
+                  "gauge_obj",
+                  features.find(feat => {
+                    return (
+                      feat.getId().indexOf("gauges") > -1 &&
+                      feat.get("objname") === f.get("gauge_objname")
+                    );
+                  })
+                );
+              }
+
+              // attach nsc data to gauge
+              if (f.getId().indexOf("gauges") > -1) {
+                // look for nashSutcliffeOverview in store. If present and
+                // not older than 15 min use it or fetch new data and store it.
+                let data = store.getters["gauges/nashSutcliffeOverview"](f);
+                if (
+                  data &&
+                  new Date().getTime() - data.createdAt.getTime() < 900000
+                ) {
+                  f.set("nsc_data", data.data);
+                } else {
+                  data = await store.dispatch(
+                    "gauges/loadNashSutcliffeForOverview",
+                    f.get("isrs_code")
+                  );
+                  if (data) {
+                    store.commit("gauges/addNashSutcliffeOverviewEntry", {
+                      feature: f,
+                      data,
+                      createdAt: new Date()
+                    });
+                    f.set("nsc_data", data);
+                  }
+                }
+              }
+            }
+          )
+        );
+        return new VectorLayer({
+          id: "DATAAVAILABILITY",
+          label: "Data Availability/Accuracy",
+          forLegendStyle: { point: true, resolution: 16 },
+          visible: false,
+          zIndex: 1,
+          style: styles.dataAvailability,
+          source
+        });
+      })(),
+      new ImageLayer({
+        id: "DISTANCEMARKS",
+        label: "Distance Marks",
+        maxResolution: 10,
+        minResolution: 0,
+        source: new ImageSource({
+          url: window.location.origin + "/api/internal/wms",
+          params: {
+            LAYERS: "distance_marks_ashore_geoserver",
+            VERSION: "1.1.1",
+            TILED: true
+          },
+          imageLoadFunction: function(tile, src) {
+            HTTP.get(src, {
+              headers: {
+                "X-Gemma-Auth": localStorage.getItem("token")
+              },
+              responseType: "blob"
+            }).then(response => {
+              tile.getImage().src = URL.createObjectURL(response.data);
+            });
+          } // TODO  tile.setState(TileState.ERROR);
+        })
+      }),
+      new ImageLayer({
+        id: "DISTANCEMARKSAXIS",
+        label: "Distance Marks, Axis",
+        source: new ImageSource({
+          url: window.location.origin + "/api/internal/wms",
+          params: {
+            LAYERS: "distance_marks_geoserver",
+            VERSION: "1.1.1",
+            TILED: true
+          },
+          imageLoadFunction: function(tile, src) {
+            HTTP.get(src, {
+              headers: {
+                "X-Gemma-Auth": localStorage.getItem("token")
+              },
+              responseType: "blob"
+            }).then(response => {
+              tile.getImage().src = URL.createObjectURL(response.data);
+            });
+          } // TODO  tile.setState(TileState.ERROR);
+        })
+      }),
+      (function() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["gauges_geoserver"],
+              geometryName: "geom"
+            },
+            source
+          )
+        );
+        return new VectorLayer({
+          id: "GAUGES",
+          label: "Gauges",
+          forLegendStyle: { point: true, resolution: 8 },
+          visible: true,
+          style: styles.gauge,
+          maxResolution: 100,
+          minResolution: 0,
+          source
+        });
+      })(),
+      DRAWLAYER,
+      CUTLAYER
+    ]
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/map/styles.js	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,351 @@
+import { Icon, Stroke, Style, Fill, Text, Circle } from "ol/style";
+import Point from "ol/geom/Point";
+import { getCenter } from "ol/extent";
+import store from "@/store/index";
+import classifications from "../../lib/classifications";
+
+const styles = {
+  blue1: new Style({
+    stroke: new Stroke({
+      color: "rgba(0, 0, 255, 0.8)",
+      lineDash: [2, 4],
+      lineCap: "round",
+      width: 2
+    }),
+    fill: new Fill({
+      color: "rgba(240, 230, 0, 0.2)"
+    })
+  }),
+  blue2: new Style({
+    stroke: new Stroke({
+      color: "rgba(0, 0, 255, 0.9)",
+      lineDash: [3, 6],
+      lineCap: "round",
+      width: 2
+    }),
+    fill: new Fill({
+      color: "rgba(240, 230, 0, 0.1)"
+    })
+  }),
+  blue3: new Style({
+    stroke: new Stroke({
+      color: "rgba(0, 0, 255, 1.0)",
+      width: 2
+    }),
+    fill: new Fill({
+      color: "rgba(255, 255, 255, 0.4)"
+    })
+  }),
+  yellow1: new Style({
+    stroke: new Stroke({
+      color: "rgba(230, 230, 10, .8)",
+      width: 4
+    }),
+    fill: new Fill({
+      color: "rgba(230, 230, 10, .3)"
+    })
+  }),
+  yellow2: new Style({
+    stroke: new Stroke({
+      color: "rgba(250, 200, 0, .8)",
+      width: 2
+    }),
+    fill: new Fill({
+      color: "rgba(250, 200, 10, .3)"
+    })
+  }),
+  yellow3: new Style({
+    stroke: new Stroke({
+      color: "rgba(250, 240, 10, .9)",
+      width: 5
+    }),
+    fill: new Fill({
+      color: "rgba(250, 240, 0, .7)"
+    })
+  }),
+  orange1: new Style({
+    stroke: new Stroke({
+      color: "rgba(255, 150, 10, .8)",
+      width: 2
+    }),
+    fill: new Fill({
+      color: "rgba(255, 150, 0, .3)"
+    })
+  }),
+  orange2: new Style({
+    stroke: new Stroke({
+      color: "rgba(255, 166, 10, .9)",
+      width: 5
+    }),
+    fill: new Fill({
+      color: "rgba(255, 166, 0, .7)"
+    })
+  }),
+  red1: new Style({
+    stroke: new Stroke({
+      color: "rgba(255, 0, 0, 1)",
+      width: 4
+    })
+  }),
+  circleBlue: new Style({
+    image: new Circle({
+      radius: 5,
+      fill: new Fill({ color: "rgba(255, 0, 0, 0.1)" }),
+      stroke: new Stroke({ color: "blue", width: 1 })
+    })
+  }),
+  textFW1: new Style({
+    text: new Text({
+      font: 'bold 12px "Open Sans", "sans-serif"',
+      placement: "line",
+      fill: new Fill({
+        color: "black"
+      }),
+      text: "LOS: 1"
+      //, zIndex: 10
+    })
+  }),
+  textFW2: new Style({
+    text: new Text({
+      font: 'bold 12px "Open Sans", "sans-serif"',
+      placement: "line",
+      fill: new Fill({
+        color: "black"
+      }),
+      text: "LOS: 2"
+      //, zIndex: 10
+    })
+  }),
+  textFW3: new Style({
+    text: new Text({
+      font: 'bold 12px "Open Sans", "sans-serif"',
+      placement: "line",
+      fill: new Fill({
+        color: "black"
+      }),
+      text: "LOS: 3"
+      //, zIndex: 10
+    })
+  })
+};
+
+export default function(mapId) {
+  return {
+    stretches(feature) {
+      let style = styles.yellow2;
+      if (feature.get("highlighted")) {
+        style = styles.yellow3;
+      }
+      return style;
+    },
+    sections(feature) {
+      let style = styles.orange1;
+      if (feature.get("highlighted")) {
+        style = styles.orange2;
+      }
+      return style;
+    },
+    fwd1() {
+      return [styles.blue1, styles.textFW1];
+    },
+    fwd2() {
+      return [styles.blue2, styles.textFW2];
+    },
+    fwd3() {
+      return [styles.blue3, styles.textFW3];
+    },
+    bottleneck() {
+      return styles.yellow1;
+    },
+    bottleneckStatus(feature, resolution, isLegend) {
+      let s = [];
+      if ((feature.get("fa_critical") && resolution > 15) || isLegend) {
+        let bnCenter = getCenter(feature.getGeometry().getExtent());
+        s.push(
+          new Style({
+            geometry: new Point(bnCenter),
+            image: new Icon({
+              src: require("@/assets/marker-bottleneck-critical.png"),
+              anchor: [0.5, 0.5],
+              scale: isLegend ? 0.5 : 1
+            })
+          })
+        );
+      }
+      if (feature.get("fa_critical") && !isLegend) {
+        s.push(styles.red1);
+      }
+      return s;
+    },
+    bottleneckFairwayAvailability(feature, resolution, isLegend) {
+      let s = [];
+      if (isLegend) {
+        s.push(
+          new Style({
+            image: new Icon({
+              src: require("@/assets/fa-diagram.png"),
+              anchor: [0.5, 0.5],
+              scale: 1
+            })
+          })
+        );
+      }
+      if (
+        feature.get("fa_critical") &&
+        feature.get("fa_data") &&
+        resolution > 15
+      ) {
+        let data = feature.get("fa_data");
+        let lnwlHeight = (80 / 100) * data.ldc;
+        let belowThresholdHeight = (80 / 100) * data.below;
+        let betweenThresholdHeight = (80 / 100) * data.between;
+        let aboveThresholdHeight = (80 / 100) * data.above;
+
+        let frame = `<rect x='0' y='0' width='32' height='84' stroke-width='0' fill='white'/>`;
+        let lnwl = `<rect x='2' y='${80 -
+          lnwlHeight +
+          2}' width='10' height='${lnwlHeight}' stroke-width='0' fill='aqua'/>`;
+        let range1 = `<rect x='12' y='2' width='18' height='${belowThresholdHeight}' stroke-width='0' fill='hotpink'/>`;
+        let range2 = `<rect x='12' y='${belowThresholdHeight +
+          2}' width='18' height='${betweenThresholdHeight}' stroke-width='0' fill='darksalmon'/>`;
+        let range3 = `<rect x='12' y='${80 -
+          aboveThresholdHeight +
+          2}' width='18' height='${aboveThresholdHeight}' stroke-width='0' fill='blue'/>`;
+        let svg = `data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='84'><g>${frame}${lnwl}${range1}${range2}${range3}</g></svg>`;
+        let bnCenter = getCenter(feature.getGeometry().getExtent());
+        s.push(
+          new Style({
+            geometry: new Point(bnCenter),
+            image: new Icon({
+              src: svg,
+              anchor: [1.2, 1.2]
+            })
+          })
+        );
+      }
+      return s;
+    },
+    dataAvailability(feature, resolution, isLegend) {
+      let s = [];
+      if (isLegend) {
+        s.push(
+          new Style({
+            image: new Icon({
+              src: require("@/assets/da-diagram.png"),
+              anchor: [0.5, 0.5],
+              scale: 1
+            })
+          })
+        );
+      } else {
+        // TODO: Get information from feature and check the ranges according to #423, #424, #425
+        let colorWaterlevel = classifications.gmAvailability(feature);
+        let colorComparison = classifications.forecastVsReality(feature);
+        let colorAccuracy = classifications.forecastAccuracy(feature);
+        let map = store.getters["map/openLayersMap"](mapId);
+        let geom = feature.getGeometry();
+        if (!(geom instanceof Point)) {
+          geom = new Point(getCenter(feature.getGeometry().getExtent()));
+        }
+        if (
+          (map.getLayer("BOTTLENECKS").getVisible() &&
+            feature.getId().indexOf("bottlenecks") > -1) ||
+          (map.getLayer("SECTIONS").getVisible() &&
+            feature.getId().indexOf("sections") > -1) ||
+          (map.getLayer("STRETCHES").getVisible() &&
+            feature.getId().indexOf("stretches") > -1) ||
+          (map.getLayer("GAUGES").getVisible() &&
+            feature.getId().indexOf("gauges") > -1)
+        ) {
+          let frame = `<polyline points='16,0 32,32 0,32 16,0' stroke='grey' stroke-width='1' fill='white'/>`;
+          let waterlevel = `<polyline points="16,0 24,16 16,32 8,16 16,0" stroke='grey' stroke-width='1' fill='${colorWaterlevel}'/>`;
+          let accuracy = `<polyline points="24,16 32,32 16,32 24,16" stroke='grey' stroke-width='1' fill='${colorAccuracy}'/>`;
+          let comparison = `<polyline points="8,16 16,32 0,32 8,16" stroke='grey' stroke-width='1' fill='${colorComparison}'/>`;
+          let svg = `data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32'><g>${frame}${waterlevel}${comparison}${accuracy}</g></svg>`;
+          s.push(
+            new Style({
+              geometry: geom,
+              image: new Icon({
+                src: svg,
+                anchor: [-0.5, 1]
+              })
+            })
+          );
+        }
+
+        if (
+          map.getLayer("BOTTLENECKS").getVisible() &&
+          feature.getId().indexOf("bottlenecks") > -1
+        ) {
+          let colorUniformTriangle = classifications.surveyCurrency(feature);
+          let frame = `<polyline points='16,0 32,32 0,32 16,0' stroke='grey' stroke-width='1' fill='${colorUniformTriangle}'/>`;
+          let svg = `data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32'><g>${frame}</g></svg>`;
+          s.push(
+            new Style({
+              geometry: geom,
+              image: new Icon({
+                src: svg,
+                anchor: [0.5, 1]
+              })
+            })
+          );
+        }
+      }
+      return s;
+    },
+    dma(feature, resolution) {
+      if (resolution < 10) {
+        var s = styles.circleBlue;
+        if (resolution < 6) {
+          s.setText(
+            new Text({
+              offsetY: 12,
+              font: '10px "Open Sans", "sans-serif"',
+              fill: new Fill({
+                color: "black"
+              }),
+              text: (feature.get("hectometre") / 10).toString()
+            })
+          );
+        }
+        return s;
+      }
+      return [];
+    },
+    gauge(feature, resolution, isLegend) {
+      let waterlevel = feature.get("gm_waterlevel");
+      let text = feature.get("objname");
+      let iconColor = "white";
+      if (waterlevel) {
+        text += "\n(" + waterlevel + " cm)";
+        let refWaterlevels = JSON.parse(feature.get("reference_water_levels"));
+        if (waterlevel < refWaterlevels.LDC) iconColor = "brown";
+        if (waterlevel > refWaterlevels.LDC && waterlevel < refWaterlevels.HDC)
+          iconColor = "blue";
+        if (waterlevel > refWaterlevels.HDC) iconColor = "red";
+      }
+
+      return [
+        new Style({
+          image: new Icon({
+            src: require("@/assets/marker-gauge-" + iconColor + ".png"),
+            anchor: [0.5, isLegend ? 0.5 : 1],
+            scale: isLegend ? 0.5 : 1
+          }),
+          text: new Text({
+            font: '10px "Open Sans", "sans-serif"',
+            offsetY: 15,
+            fill: new Fill({
+              color: "black"
+            }),
+            backgroundFill: new Fill({
+              color: "rgba(255, 255, 255, 0.7)"
+            }),
+            padding: [2, 2, 2, 2],
+            text
+          })
+        })
+      ];
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/paneSetups.js	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,52 @@
+const main = { id: "main", component: "Map" };
+
+export const DEFAULT = { main };
+export const COMPARESURVEYS = {
+  main,
+  compare: { id: "compare-survey", component: "Map" }
+};
+export const FAIRWAYPROFILE = {
+  main,
+  fairwayprofile: { id: "fairwayprofile", component: "Fairwayprofile" }
+};
+
+export const AVAILABLEFAIRWAYDEPTH = {
+  main,
+  availablefairwaydepth: {
+    id: "availablefairwaydepth",
+    component: "AvailableFairwayDepth"
+  }
+};
+
+export const AVAILABLEFAIRWAYDEPTHLNWL = {
+  main,
+  availablefairwaydepth: {
+    id: "availablefairwaydepthlnwl",
+    component: "AvailableFairwayDepthLNWL"
+  }
+};
+
+export const COMPARESURVEYS_FAIRWAYPROFILE = {
+  main,
+  compare: { id: "compare-survey", component: "Map" },
+  fairwayprofile: { id: "fairwayprofile", component: "Fairwayprofile" }
+};
+export const GAUGE_WATERLEVEL = {
+  main,
+  waterlevel: { id: "gauge-waterlevel", component: "Waterlevel" }
+};
+export const GAUGE_HYDROLOGICALCONDITIONS = {
+  main,
+  hydrological: {
+    id: "gauge-hydrologicalconditions",
+    component: "HydrologicalConditions"
+  }
+};
+export const GAUGE_WATERLEVEL_HYDROLOGICALCONDITIONS = {
+  main,
+  waterlevel: { id: "gauge-waterlevel", component: "Waterlevel" },
+  hydrological: {
+    id: "gauge-hydrologicalconditions",
+    component: "HydrologicalConditions"
+  }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/sections/SectionForm.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,360 @@
+<template>
+  <div class="d-flex flex-column">
+    <div class="d-flex justify-content-between mt-2 px-2">
+      <div class="text-left flex-fill">
+        <small class="text-muted">
+          <translate>ID</translate>
+        </small>
+        <input
+          id="id"
+          type="text"
+          class="form-control form-control-sm"
+          placeholder="AT_Section_12"
+          v-model="id"
+          :disabled="editSection"
+        />
+        <span class="text-left text-danger">
+          <small v-if="errors.id && !id">
+            <translate>Please enter an id</translate>
+          </small>
+        </span>
+      </div>
+    </div>
+    <div class="d-flex justify-content-between mt-2 px-2">
+      <div class="text-left flex-fill">
+        <small class="text-muted">
+          <translate>Start rhm</translate>
+        </small>
+        <div class="d-flex flex-row position-relative">
+          <input
+            id="startrhm"
+            type="text"
+            class="form-control form-control-sm"
+            placeholder="e.g. ATXXX000010000019900"
+            v-model="startrhm"
+            ref="startrhm"
+            @focus="enablePipette('start')"
+            @blur="disablePipette('start')"
+          />
+          <span
+            class="input-group-text position-absolute input-button"
+            @click="$refs.startrhm.focus()"
+            v-tooltip="pipetteTooltip"
+          >
+            <font-awesome-icon
+              :class="{ 'text-info': pipetteStart }"
+              icon="crosshairs"
+            />
+          </span>
+        </div>
+        <span class="text-left text-danger">
+          <small v-if="errors.startrhm && !startrhm">
+            <translate>Please enter a start point</translate>
+          </small>
+        </span>
+      </div>
+      <div class="text-left flex-fill ml-2">
+        <small class="text-muted">
+          <translate>End rhm</translate>
+        </small>
+        <div class="d-flex flex-row position-relative">
+          <input
+            id="endrhm"
+            type="text"
+            class="form-control form-control-sm"
+            placeholder="e.g. ATXXX000010000019900"
+            v-model="endrhm"
+            ref="endrhm"
+            @focus="enablePipette('end')"
+            @blur="disablePipette('end')"
+          />
+          <span
+            class="input-group-text position-absolute input-button"
+            @click="$refs.endrhm.focus()"
+            v-tooltip="pipetteTooltip"
+          >
+            <font-awesome-icon
+              :class="{ 'text-info': pipetteEnd }"
+              icon="crosshairs"
+            />
+          </span>
+        </div>
+        <span class="text-left text-danger">
+          <small v-if="errors.endrhm && !endrhm">
+            <translate>Please enter an end point</translate>
+          </small>
+        </span>
+      </div>
+      <div class="text-left ml-2" v-if="!editSection">
+        <small class="text-muted">
+          <translate>Tolerance for snapping to axis</translate>
+        </small>
+        <div class="d-flex flex-row position-relative">
+          <input
+            class="form-control form-control-sm"
+            v-model.number="tolerance"
+            type="number"
+            min="0"
+            step="any"
+            id="tolerance"
+          />
+          <span class="input-group-text position-absolute input-button">
+            m
+          </span>
+        </div>
+        <span class="text-left text-danger">
+          <small v-if="errors.tolerance && !tolerance">
+            <translate>Please enter a tolerance value</translate>
+          </small>
+        </span>
+      </div>
+    </div>
+    <div class="d-flex flex-row justify-content-between px-2">
+      <div class="mt-2 mr-2 w-50 text-left">
+        <small class="text-muted">
+          <translate>Object name</translate>
+        </small>
+        <input
+          id="objbn"
+          type="text"
+          class="form-control form-control-sm"
+          placeholder=""
+          v-model="objbn"
+        />
+        <span class="text-left text-danger">
+          <small v-if="errors.objbn && !objbn">
+            <translate>Please enter an objectname</translate>
+          </small>
+        </span>
+      </div>
+      <div class="mt-2 w-50 text-left">
+        <small class="text-muted">
+          <translate>National Object name</translate>
+        </small>
+        <input
+          id="nobjbn"
+          type="text"
+          class="form-control form-control-sm"
+          v-model="nobjbn"
+        />
+      </div>
+    </div>
+    <div class="d-flex flex-row justify-content-between px-2">
+      <div class="mt-2 w-50 text-left">
+        <small class="text-muted">
+          <translate>Date info</translate>
+        </small>
+        <input
+          id="date_info"
+          type="date"
+          class="form-control form-control-sm"
+          placeholder="date_info"
+          v-model="date_info"
+        />
+        <span class="text-left text-danger">
+          <small v-if="errors.date_info && !date_info">
+            <translate>Please enter a date</translate>
+          </small>
+        </span>
+      </div>
+      <div class="mt-2 ml-2 w-50 text-left">
+        <small class="text-muted">
+          <translate>Source Organization</translate>
+        </small>
+        <input
+          id="source_organization"
+          type="text"
+          class="form-control form-control-sm"
+          v-model="source_organization"
+        />
+        <span class="text-left text-danger">
+          <small v-if="errors.source_organization && !source_organization">
+            <translate>Please enter a source organization</translate>
+          </small>
+        </span>
+      </div>
+    </div>
+    <div class="d-flex justify-content-between mt-2 p-2 border-top">
+      <button @click="$parent.showForm = false" class="btn btn-sm btn-warning">
+        <translate>Back</translate>
+      </button>
+      <button
+        @click="save"
+        type="submit"
+        class="shadow-sm btn btn-sm btn-info submit-button"
+      >
+        <translate>Submit</translate>
+      </button>
+    </div>
+  </div>
+</template>
+
+<style lang="sass" scoped>
+.input-button
+  border-top-left-radius: 0
+  border-bottom-left-radius: 0
+  right: 0
+  height: 31px
+</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, 2019 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Tom Gottfried <tom.gottfried@intevation.de>
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState, mapGetters } from "vuex";
+import { displayError, displayInfo } from "@/lib/errors";
+import { sortTable } from "@/lib/mixins";
+
+export default {
+  mixins: [sortTable],
+  props: ["editSection"],
+  data() {
+    return {
+      pipetteStart: false,
+      pipetteEnd: false,
+      id: null,
+      startrhm: null,
+      endrhm: null,
+      tolerance: 5,
+      objbn: null,
+      nobjbn: null,
+      date_info: new Date().toISOString().split("T")[0],
+      source_organization: null,
+      errors: {
+        id: false,
+        startrhm: false,
+        endrhm: false,
+        tolerance: false,
+        objbn: false,
+        nobjbn: false,
+        date_info: false,
+        source_organization: false
+      }
+    };
+  },
+  computed: {
+    ...mapState("map", ["identifiedFeatures"]),
+    ...mapGetters("map", ["openLayersMap"]),
+    pipetteTooltip() {
+      return this.$gettext("Choose a distance mark by clicking on the map.");
+    }
+  },
+  watch: {
+    identifiedFeatures() {
+      const distanceMark = this.identifiedFeatures.find(x =>
+        /^distance_marks_geoserver/.test(x["id_"])
+      );
+      if (distanceMark) {
+        const location = distanceMark.get("location");
+        this.startrhm = this.pipetteStart ? location : this.startrhm;
+        this.endrhm = this.pipetteEnd ? location : this.endrhm;
+        this.pipetteStart = false;
+        this.pipetteEnd = false;
+        this.$store.commit("map/mapPopupEnabled", true);
+      }
+    }
+  },
+  methods: {
+    enablePipette(t) {
+      this.openLayersMap()
+        .getLayer("DISTANCEMARKSAXIS")
+        .setVisible(true);
+      this.$store.commit("map/mapPopupEnabled", false);
+      if (t === "start") {
+        this.pipetteStart = true;
+        this.pipetteEnd = false;
+      } else {
+        this.pipetteStart = false;
+        this.pipetteEnd = true;
+      }
+    },
+    disablePipette() {
+      this.$store.commit("map/mapPopupEnabled", true);
+      this.pipetteStart = false;
+      this.pipetteEnd = false;
+    },
+    validate() {
+      const fields = [
+        "id",
+        "startrhm",
+        "endrhm",
+        "objbn",
+        "date_info",
+        "source_organization"
+      ];
+      if (!this.editSection) fields.push("tolerance");
+      fields.forEach(field => {
+        if (!this[field]) {
+          this.errors[field] = true;
+        } else {
+          this.errors[field] = false;
+        }
+      });
+
+      // return true if no errors
+      return !Object.values(this.errors).reduce((a, b) => a + b, 0);
+    },
+    save() {
+      if (this.validate()) {
+        const data = {
+          name: this.id,
+          from: this.startrhm,
+          to: this.endrhm,
+          "source-organization": this.source_organization,
+          "date-info": this.date_info,
+          objnam: this.objbn,
+          nobjnam: this.nobjbn
+        };
+        if (!this.editSection) {
+          data["tolerance"] = this.tolerance;
+        }
+        this.$parent.loading = true;
+        this.$store
+          .dispatch("imports/saveSection", data)
+          .then(() => {
+            displayInfo({
+              title: this.$gettext("Import"),
+              message: this.$gettext("Starting import of section")
+            });
+            this.$store.dispatch("imports/loadSections").then(() => {
+              this.$parent.loading = false;
+              this.$parent.showForm = false;
+            });
+          })
+          .catch(error => {
+            const { status, data } = error.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          });
+      }
+    }
+  },
+  mounted() {
+    if (this.editSection) {
+      const props = this.editSection.properties;
+      this.id = props.name;
+      this.startrhm = props.lower.replace(/[,()]/g, "");
+      this.endrhm = props.upper.replace(/[,()]/g, "");
+      this.tolerance = props.tolerance;
+      this.objbn = props.objnam;
+      this.nobjbn = props.nobjnam;
+      this.date_info = props.date_info.split("T")[0];
+      this.source_organization = props.source_organization;
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/sections/Sections.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,249 @@
+<template>
+  <div class="d-flex flex-column">
+    <UIBoxHeader icon="road" :title="title" :closeCallback="$parent.close" />
+    <div class="position-relative">
+      <UISpinnerOverlay v-if="loading" />
+      <SectionForm v-if="showForm" :editSection="editSection" />
+      <div v-else>
+        <UITableHeader
+          :columns="[
+            { id: 'properties.name', title: `${nameLabel}`, class: 'col-4' },
+            {
+              id: 'properties.date_info',
+              title: `${dateLabel}`,
+              class: 'col-2'
+            },
+            {
+              id: 'properties.source_organization',
+              title: `${sourceorganizationLabel}`,
+              class: 'col-3'
+            }
+          ]"
+        />
+        <UITableBody
+          :data="filteredSections() | sortTable(sortColumn, sortDirection)"
+        >
+          <template v-slot:row="{ item: section }">
+            <div class="py-1 px-2 col-4">
+              <a @click="moveMapToSection(section)" href="#">
+                {{ section.properties.name }}
+              </a>
+            </div>
+            <div class="py-1 px-2 col-2">
+              {{ section.properties.date_info | surveyDate }}
+            </div>
+            <div class="py-1 px-2 col-3">
+              {{ section.properties.source_organization }}
+            </div>
+            <div class="py-1 px-2 col text-right">
+              <button
+                v-if="isInStaging(section.properties.name)"
+                @click="gotoStaging(section.properties.name)"
+                class="btn btn-xs btn-danger mr-1"
+              >
+                <font-awesome-icon
+                  icon="exclamation-triangle"
+                  fixed-width
+                  v-tooltip="reviewTooltip"
+                />
+              </button>
+              <button
+                class="btn btn-xs btn-dark mr-1"
+                @click="
+                  showForm = true;
+                  editSection = section;
+                "
+              >
+                <font-awesome-icon icon="pencil-alt" fixed-width />
+              </button>
+              <button
+                class="btn btn-xs btn-dark"
+                @click="deleteSection(section)"
+              >
+                <font-awesome-icon icon="trash" fixed-width />
+              </button>
+            </div>
+          </template>
+        </UITableBody>
+        <div class="text-right p-2 border-top">
+          <button
+            @click="
+              showForm = true;
+              editSection = null;
+            "
+            class="btn btn-sm btn-info"
+          >
+            <translate>New section</translate>
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="sass" scoped>
+.input-button
+  border-top-left-radius: 0
+  border-bottom-left-radius: 0
+  right: 0
+  height: 31px
+</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, 2019 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Tom Gottfried <tom.gottfried@intevation.de>
+ */
+import { mapState, mapGetters } from "vuex";
+import { displayError, displayInfo } from "@/lib/errors";
+import { HTTP } from "@/lib/http";
+import { sortTable } from "@/lib/mixins";
+
+export default {
+  mixins: [sortTable],
+  components: {
+    SectionForm: () => import("./SectionForm")
+  },
+  data() {
+    return {
+      staging: [],
+      loading: false,
+      showForm: false,
+      editSection: null
+    };
+  },
+  computed: {
+    ...mapState("application", ["searchQuery"]),
+    ...mapGetters("map", ["openLayersMap"]),
+    ...mapState("imports", ["sections"]),
+    title() {
+      return this.$gettext("Define Sections");
+    },
+    nameLabel() {
+      return this.$gettext("Name");
+    },
+    dateLabel() {
+      return this.$gettext("Date");
+    },
+    sourceorganizationLabel() {
+      return this.$gettext("Source organization");
+    },
+    reviewTooltip() {
+      return this.$gettext("Review pending import");
+    }
+  },
+  methods: {
+    filteredSections() {
+      return this.sections.filter(s => {
+        return (s.properties.name + s.properties.source_organization)
+          .toLowerCase()
+          .includes(this.searchQuery.toLowerCase());
+      });
+    },
+    gotoStaging(sectionName) {
+      let pendingImport = this.staging.find(s => s.name === sectionName);
+      if (pendingImport)
+        this.$router.push("/imports/overview/" + pendingImport.id);
+    },
+    isInStaging(sectionName) {
+      return !!this.staging.find(s => s.name === sectionName);
+    },
+    loadStagingData() {
+      HTTP.get("/imports?states=pending&kinds=sec", {
+        headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+      })
+        .then(response => {
+          response.data.imports.forEach(i => {
+            HTTP.get("/imports/" + i.id, {
+              headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+            })
+              .then(response => {
+                this.staging.push({
+                  id: i.id,
+                  name: response.data.summary.section
+                });
+              })
+              .catch(error => {
+                const { status, data } = error.response;
+                displayError({
+                  title: this.$gettext("Backend Error"),
+                  message: `${status}: ${data.message || data}`
+                });
+              })
+              .finally(() => (this.loading = false));
+          });
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    },
+    deleteSection(section) {
+      this.$store.commit("application/popup", {
+        icon: "trash",
+        title: this.$gettext("Delete Section"),
+        content:
+          this.$gettext("Do you really want to delete this section:") +
+          `<br>
+        <b>${section.properties.name}, ${
+            section.properties.source_organization
+          } (${section.properties.countries})</b>`,
+        confirm: {
+          label: this.$gettext("Delete"),
+          icon: "trash",
+          callback: () => {
+            displayInfo({
+              title: this.$gettext("Not implemented"),
+              message: this.$gettext("Deleting ") + section.id
+            });
+          }
+        },
+        cancel: {
+          label: this.$gettext("Cancel"),
+          icon: "times"
+        }
+      });
+    },
+    moveMapToSection(section) {
+      this.$store.commit("imports/selectedSectionId", section.id);
+      this.$store.commit("fairwayavailability/type", "sections");
+      this.$store.commit("application/showFairwayDepth", true);
+      this.openLayersMap()
+        .getLayer("SECTIONS")
+        .setVisible(true);
+      this.$store.dispatch("map/moveToFeauture", {
+        feature: section,
+        zoom: 17,
+        preventZoomOut: true
+      });
+    }
+  },
+  mounted() {
+    this.loading = true;
+    this.$store
+      .dispatch("imports/loadSections")
+      .catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      })
+      .finally(() => (this.loading = false));
+    this.loadStagingData();
+  }
+};
+</script>
--- a/client/src/components/splitscreen/MinimizedSplitscreens.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-<template>
-  <transition-group
-    name="fade"
-    tag="div"
-    class="minimizedSplitscreens ui-element"
-  >
-    <UIBoxHeader
-      v-for="splitscreen in splitscreens"
-      :key="splitscreen.id"
-      :icon="splitscreen.icon"
-      :title="splitscreen.title"
-      :closeCallback="close(splitscreen)"
-      :expandCallback="expand(splitscreen)"
-      :collapsed="true"
-      class="mt-2"
-    />
-  </transition-group>
-</template>
-
-<style lang="sass" scoped>
-.minimizedSplitscreens
-  position: absolute
-  bottom: $small-offset
-  right: $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):
- * Markus Kottländer <markus@intevation.de>
- */
-
-import { mapState } from "vuex";
-
-export default {
-  computed: {
-    ...mapState("application", ["splitscreens"])
-  },
-  methods: {
-    close(splitscreen) {
-      return () => {
-        if (splitscreen.closeCallback) splitscreen.closeCallback();
-        this.$store.commit("application/removeSplitscreen", splitscreen.id);
-      };
-    },
-    expand(splitscreen) {
-      return () => {
-        if (splitscreen.expandCallback) splitscreen.expandCallback();
-        this.$store.commit("application/activeSplitscreenId", splitscreen.id);
-        this.$store.commit("application/showSplitscreen", true);
-      };
-    }
-  }
-};
-</script>
--- a/client/src/components/splitscreen/Splitscreen.vue	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-<template>
-  <div>
-    <div
-      :class="[
-        'splitscreen bg-white d-flex flex-column ui-element',
-        { show: showSplitscreen }
-      ]"
-    >
-      <UIBoxHeader
-        :icon="activeSplitscreen.icon"
-        :title="activeSplitscreen.title"
-        :closeCallback="close"
-        :collapseCallback="collapse"
-        v-if="activeSplitscreen"
-      />
-      <div class="d-flex flex-fill">
-        <transition name="fade">
-          <div class="loading" v-if="splitscreenLoading">
-            <font-awesome-icon icon="spinner" spin />
-          </div>
-        </transition>
-        <component :is="activeSplitscreen.component" v-if="activeSplitscreen" />
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="sass" scoped>
-.splitscreen
-  position: absolute
-  bottom: -50vh
-  left: 0
-  right: 0
-  height: 50vh
-  overflow: hidden
-  z-index: 1
-  box-shadow: 0 -.125rem .25rem rgba(0, 0, 0, 0.075)
-  transition: bottom 0.3s
-  &.show
-    bottom: 0
-
-  .loading
-    top: 34px
-</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 {
-  components: {
-    Fairwayprofile: () => import("@/components/fairway/Fairwayprofile"),
-    Waterlevel: () => import("@/components/gauge/Waterlevel"),
-    HydrologicalConditions: () =>
-      import("@/components/gauge/HydrologicalConditions")
-  },
-  computed: {
-    ...mapState("application", ["showSplitscreen", "splitscreenLoading"]),
-    ...mapGetters("application", ["activeSplitscreen"])
-  },
-  methods: {
-    collapse() {
-      if (this.activeSplitscreen.collapseCallback)
-        this.activeSplitscreen.collapseCallback();
-      this.$store.commit("application/showSplitscreen", false);
-    },
-    close() {
-      this.$store.commit("application/showSplitscreen", false);
-      setTimeout(() => {
-        let removeId = this.activeSplitscreen.id;
-        let callback = this.activeSplitscreen.closeCallback;
-        this.$store.commit("application/activeSplitscreenId", null);
-        if (callback) callback();
-        this.$store.commit("application/removeSplitscreen", removeId);
-      }, 350);
-    }
-  }
-};
-</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/stretches/StretchForm.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,384 @@
+<template>
+  <div class="d-flex flex-column">
+    <div class="d-flex justify-content-between mt-2 px-2">
+      <div class="text-left flex-fill mr-1">
+        <small class="text-muted">
+          <translate>ID</translate>
+        </small>
+        <input
+          id="id"
+          type="text"
+          class="form-control form-control-sm"
+          placeholder="AT_Stretch_12"
+          v-model="id"
+          :disabled="editStretch"
+        />
+        <span class="text-left text-danger">
+          <small v-if="errors.id && !id">
+            <translate>Please enter an id</translate>
+          </small>
+        </span>
+      </div>
+      <div class="text-left flex-fill ml-1">
+        <small class="text-muted">
+          <translate>Countrycode</translate>
+        </small>
+        <input
+          id="countryCode"
+          type="text"
+          class="form-control form-control-sm"
+          placeholder="AT"
+          v-model="countryCode"
+        />
+        <span class="text-left text-danger">
+          <small v-if="errors.countryCode && !countryCode">
+            <translate>Please enter a countrycode </translate>
+          </small>
+        </span>
+      </div>
+    </div>
+    <div class="d-flex justify-content-between mt-2 px-2">
+      <div class="text-left flex-fill">
+        <small class="text-muted">
+          <translate>Start rhm</translate>
+        </small>
+        <div class="d-flex flex-row position-relative">
+          <input
+            id="startrhm"
+            type="text"
+            class="form-control form-control-sm"
+            placeholder="e.g. ATXXX000010000019900"
+            v-model="startrhm"
+            ref="startrhm"
+            @focus="enablePipette('start')"
+            @blur="disablePipette('start')"
+          />
+          <span
+            class="input-group-text position-absolute input-button"
+            @click="$refs.startrhm.focus()"
+            v-tooltip="pipetteTooltip"
+          >
+            <font-awesome-icon
+              :class="{ 'text-info': pipetteStart }"
+              icon="crosshairs"
+            />
+          </span>
+        </div>
+        <span class="text-left text-danger">
+          <small v-if="errors.startrhm && !startrhm">
+            <translate>Please enter a start point</translate>
+          </small>
+        </span>
+      </div>
+      <div class="text-left flex-fill ml-2">
+        <small class="text-muted">
+          <translate>End rhm</translate>
+        </small>
+        <div class="d-flex flex-row position-relative">
+          <input
+            id="endrhm"
+            type="text"
+            class="form-control form-control-sm"
+            placeholder="e.g. ATXXX000010000019900"
+            v-model="endrhm"
+            ref="endrhm"
+            @focus="enablePipette('end')"
+            @blur="disablePipette('end')"
+          />
+          <span
+            class="input-group-text position-absolute input-button"
+            @click="$refs.endrhm.focus()"
+            v-tooltip="pipetteTooltip"
+          >
+            <font-awesome-icon
+              :class="{ 'text-info': pipetteEnd }"
+              icon="crosshairs"
+            />
+          </span>
+        </div>
+        <span class="text-left text-danger">
+          <small v-if="errors.endrhm && !endrhm">
+            <translate>Please enter an end point</translate>
+          </small>
+        </span>
+      </div>
+      <div class="text-left ml-2" v-if="!editStretch">
+        <small class="text-muted">
+          <translate>Tolerance for snapping to axis</translate>
+        </small>
+        <div class="d-flex flex-row position-relative">
+          <input
+            class="form-control form-control-sm"
+            v-model.number="tolerance"
+            type="number"
+            min="0"
+            step="any"
+            id="tolerance"
+          />
+          <span class="input-group-text position-absolute input-button">
+            m
+          </span>
+        </div>
+        <span class="text-left text-danger">
+          <small v-if="errors.tolerance && !tolerance">
+            <translate>Please enter a tolerance value</translate>
+          </small>
+        </span>
+      </div>
+    </div>
+    <div class="d-flex flex-row justify-content-between px-2">
+      <div class="mt-2 mr-2 w-50 text-left">
+        <small class="text-muted">
+          <translate>Object name</translate>
+        </small>
+        <input
+          id="objbn"
+          type="text"
+          class="form-control form-control-sm"
+          placeholder=""
+          v-model="objbn"
+        />
+        <span class="text-left text-danger">
+          <small v-if="errors.objbn && !objbn">
+            <translate>Please enter an objectname</translate>
+          </small>
+        </span>
+      </div>
+      <div class="mt-2 w-50 text-left">
+        <small class="text-muted">
+          <translate>National Object name</translate>
+        </small>
+        <input
+          id="nobjbn"
+          type="text"
+          class="form-control form-control-sm"
+          v-model="nobjbn"
+        />
+      </div>
+    </div>
+    <div class="d-flex flex-row justify-content-between px-2">
+      <div class="mt-2 w-50 text-left">
+        <small class="text-muted">
+          <translate>Date info</translate>
+        </small>
+        <input
+          id="date_info"
+          type="date"
+          class="form-control form-control-sm"
+          placeholder="date_info"
+          v-model="date_info"
+        />
+        <span class="text-left text-danger">
+          <small v-if="errors.date_info && !date_info">
+            <translate>Please enter a date</translate>
+          </small>
+        </span>
+      </div>
+      <div class="mt-2 ml-2 w-50 text-left">
+        <small class="text-muted">
+          <translate>Source Organization</translate>
+        </small>
+        <input
+          id="source_organization"
+          type="text"
+          class="form-control form-control-sm"
+          v-model="source_organization"
+        />
+        <span class="text-left text-danger">
+          <small v-if="errors.source_organization && !source_organization">
+            <translate>Please enter a source organization</translate>
+          </small>
+        </span>
+      </div>
+    </div>
+    <div class="d-flex justify-content-between mt-2 p-2 border-top">
+      <button @click="$parent.showForm = false" class="btn btn-sm btn-warning">
+        <translate>Back</translate>
+      </button>
+      <button
+        @click="save"
+        type="submit"
+        class="shadow-sm btn btn-sm btn-info submit-button"
+      >
+        <translate>Submit</translate>
+      </button>
+    </div>
+  </div>
+</template>
+
+<style lang="sass" scoped>
+.input-button
+  border-top-left-radius: 0
+  border-bottom-left-radius: 0
+  right: 0
+  height: 31px
+</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, 2019 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Tom Gottfried <tom.gottfried@intevation.de>
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState, mapGetters } from "vuex";
+import { displayError, displayInfo } from "@/lib/errors";
+import { sortTable } from "@/lib/mixins";
+
+export default {
+  mixins: [sortTable],
+  props: ["editStretch"],
+  data() {
+    return {
+      pipetteStart: false,
+      pipetteEnd: false,
+      id: null,
+      startrhm: null,
+      endrhm: null,
+      tolerance: 5,
+      objbn: null,
+      nobjbn: null,
+      date_info: new Date().toISOString().split("T")[0],
+      source_organization: null,
+      countryCode: null,
+      errors: {
+        id: false,
+        startrhm: false,
+        endrhm: false,
+        tolerance: false,
+        objbn: false,
+        nobjbn: false,
+        date_info: false,
+        source_organization: false,
+        countryCode: false
+      }
+    };
+  },
+  computed: {
+    ...mapState("map", ["identifiedFeatures"]),
+    ...mapGetters("map", ["openLayersMap"]),
+    pipetteTooltip() {
+      return this.$gettext("Choose a distance mark by clicking on the map.");
+    }
+  },
+  watch: {
+    identifiedFeatures() {
+      const distanceMark = this.identifiedFeatures.find(x =>
+        /^distance_marks_geoserver/.test(x["id_"])
+      );
+      if (distanceMark) {
+        const location = distanceMark.get("location");
+        this.startrhm = this.pipetteStart ? location : this.startrhm;
+        this.endrhm = this.pipetteEnd ? location : this.endrhm;
+        this.pipetteStart = false;
+        this.pipetteEnd = false;
+        this.$store.commit("map/mapPopupEnabled", true);
+      }
+    }
+  },
+  methods: {
+    enablePipette(t) {
+      this.openLayersMap()
+        .getLayer("DISTANCEMARKSAXIS")
+        .setVisible(true);
+      this.$store.commit("map/mapPopupEnabled", false);
+      if (t === "start") {
+        this.pipetteStart = true;
+        this.pipetteEnd = false;
+      } else {
+        this.pipetteStart = false;
+        this.pipetteEnd = true;
+      }
+    },
+    disablePipette() {
+      this.$store.commit("map/mapPopupEnabled", true);
+      this.pipetteStart = false;
+      this.pipetteEnd = false;
+    },
+    validate() {
+      const fields = [
+        "id",
+        "startrhm",
+        "endrhm",
+        "objbn",
+        "countryCode",
+        "date_info",
+        "source_organization"
+      ];
+      if (!this.editStretch) fields.push("tolerance");
+      fields.forEach(field => {
+        if (!this[field]) {
+          this.errors[field] = true;
+        } else {
+          this.errors[field] = false;
+        }
+      });
+
+      // return true if no errors
+      return !Object.values(this.errors).reduce((a, b) => a + b, 0);
+    },
+    save() {
+      if (this.validate()) {
+        const data = {
+          name: this.id,
+          from: this.startrhm,
+          to: this.endrhm,
+          "source-organization": this.source_organization,
+          "date-info": this.date_info,
+          objnam: this.objbn,
+          nobjnam: this.nobjbn,
+          countries: this.countryCode.split(",").map(x => {
+            return x.trim();
+          })
+        };
+        if (!this.editStretch) {
+          data["tolerance"] = this.tolerance;
+        }
+        this.$parent.loading = true;
+        this.$store
+          .dispatch("imports/saveStretch", data)
+          .then(() => {
+            displayInfo({
+              title: this.$gettext("Import"),
+              message: this.$gettext("Starting import of stretch")
+            });
+            this.$store.dispatch("imports/loadStretches").then(() => {
+              this.$parent.loading = false;
+              this.$parent.showForm = false;
+            });
+          })
+          .catch(error => {
+            const { status, data } = error.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          });
+      }
+    }
+  },
+  mounted() {
+    if (this.editStretch) {
+      const props = this.editStretch.properties;
+      this.id = props.name;
+      this.startrhm = props.lower.replace(/[,()]/g, "");
+      this.endrhm = props.upper.replace(/[,()]/g, "");
+      this.tolerance = props.tolerance;
+      this.objbn = props.objnam;
+      this.nobjbn = props.nobjnam;
+      this.date_info = props.date_info.split("T")[0];
+      this.source_organization = props.source_organization;
+      this.countryCode = props.countries;
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/stretches/Stretches.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,249 @@
+<template>
+  <div class="d-flex flex-column">
+    <UIBoxHeader icon="road" :title="title" :closeCallback="$parent.close" />
+    <div class="position-relative">
+      <UISpinnerOverlay v-if="loading" />
+      <StretchForm v-if="showForm" :editStretch="editStretch" />
+      <div v-else>
+        <UITableHeader
+          :columns="[
+            { id: 'properties.name', title: `${nameLabel}`, class: 'col-4' },
+            {
+              id: 'properties.date_info',
+              title: `${dateLabel}`,
+              class: 'col-2'
+            },
+            {
+              id: 'properties.source_organization',
+              title: `${sourceorganizationLabel}`,
+              class: 'col-3'
+            }
+          ]"
+        />
+        <UITableBody
+          :data="filteredStretches() | sortTable(sortColumn, sortDirection)"
+        >
+          <template v-slot:row="{ item: stretch }">
+            <div class="py-1 px-2 col-4">
+              <a @click="moveMapToStretch(stretch)" href="#">
+                {{ stretch.properties.name }}
+              </a>
+            </div>
+            <div class="py-1 px-2 col-2">
+              {{ stretch.properties.date_info | surveyDate }}
+            </div>
+            <div class="py-1 px-2 col-3">
+              {{ stretch.properties.source_organization }}
+            </div>
+            <div class="py-1 px-2 col text-right">
+              <button
+                v-if="isInStaging(stretch.properties.name)"
+                @click="gotoStaging(stretch.properties.name)"
+                class="btn btn-xs btn-danger mr-1"
+              >
+                <font-awesome-icon
+                  icon="exclamation-triangle"
+                  fixed-width
+                  v-tooltip="reviewTooltip"
+                />
+              </button>
+              <button
+                class="btn btn-xs btn-dark mr-1"
+                @click="
+                  showForm = true;
+                  editStretch = stretch;
+                "
+              >
+                <font-awesome-icon icon="pencil-alt" fixed-width />
+              </button>
+              <button
+                class="btn btn-xs btn-dark"
+                @click="deleteStretch(stretch)"
+              >
+                <font-awesome-icon icon="trash" fixed-width />
+              </button>
+            </div>
+          </template>
+        </UITableBody>
+        <div class="text-right p-2 border-top">
+          <button
+            @click="
+              showForm = true;
+              editStretch = null;
+            "
+            class="btn btn-sm btn-info"
+          >
+            <translate>New stretch</translate>
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="sass" scoped>
+.input-button
+  border-top-left-radius: 0
+  border-bottom-left-radius: 0
+  right: 0
+  height: 31px
+</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, 2019 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Tom Gottfried <tom.gottfried@intevation.de>
+ */
+import { mapState, mapGetters } from "vuex";
+import { displayError, displayInfo } from "@/lib/errors";
+import { HTTP } from "@/lib/http";
+import { sortTable } from "@/lib/mixins";
+
+export default {
+  mixins: [sortTable],
+  components: {
+    StretchForm: () => import("./StretchForm")
+  },
+  data() {
+    return {
+      staging: [],
+      loading: false,
+      showForm: false,
+      editStretch: null
+    };
+  },
+  computed: {
+    ...mapState("application", ["searchQuery"]),
+    ...mapGetters("map", ["openLayersMap"]),
+    ...mapState("imports", ["stretches"]),
+    title() {
+      return this.$gettext("Define Stretches");
+    },
+    nameLabel() {
+      return this.$gettext("Name");
+    },
+    dateLabel() {
+      return this.$gettext("Date");
+    },
+    sourceorganizationLabel() {
+      return this.$gettext("Source organization");
+    },
+    reviewTooltip() {
+      return this.$gettext("Review pending import");
+    }
+  },
+  methods: {
+    filteredStretches() {
+      return this.stretches.filter(s => {
+        return (s.properties.name + s.properties.source_organization)
+          .toLowerCase()
+          .includes(this.searchQuery.toLowerCase());
+      });
+    },
+    gotoStaging(stretchName) {
+      let pendingImport = this.staging.find(s => s.name === stretchName);
+      if (pendingImport)
+        this.$router.push("/imports/overview/" + pendingImport.id);
+    },
+    isInStaging(stretchName) {
+      return !!this.staging.find(s => s.name === stretchName);
+    },
+    loadStagingData() {
+      HTTP.get("/imports?states=pending&kinds=st", {
+        headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+      })
+        .then(response => {
+          response.data.imports.forEach(i => {
+            HTTP.get("/imports/" + i.id, {
+              headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+            })
+              .then(response => {
+                this.staging.push({
+                  id: i.id,
+                  name: response.data.summary.stretch
+                });
+              })
+              .catch(error => {
+                const { status, data } = error.response;
+                displayError({
+                  title: this.$gettext("Backend Error"),
+                  message: `${status}: ${data.message || data}`
+                });
+              })
+              .finally(() => (this.loading = false));
+          });
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    },
+    deleteStretch(stretch) {
+      this.$store.commit("application/popup", {
+        icon: "trash",
+        title: this.$gettext("Delete Stretch"),
+        content:
+          this.$gettext("Do you really want to delete this stretch:") +
+          `<br>
+        <b>${stretch.properties.name}, ${
+            stretch.properties.source_organization
+          } (${stretch.properties.countries})</b>`,
+        confirm: {
+          label: this.$gettext("Delete"),
+          icon: "trash",
+          callback: () => {
+            displayInfo({
+              title: this.$gettext("Not implemented"),
+              message: this.$gettext("Deleting ") + stretch.id
+            });
+          }
+        },
+        cancel: {
+          label: this.$gettext("Cancel"),
+          icon: "times"
+        }
+      });
+    },
+    moveMapToStretch(stretch) {
+      this.$store.commit("imports/selectedStretchId", stretch.id);
+      this.$store.commit("fairwayavailability/type", "stretches");
+      this.$store.commit("application/showFairwayDepth", true);
+      this.openLayersMap()
+        .getLayer("STRETCHES")
+        .setVisible(true);
+      this.$store.dispatch("map/moveToFeauture", {
+        feature: stretch,
+        zoom: 17,
+        preventZoomOut: true
+      });
+    }
+  },
+  mounted() {
+    this.loading = true;
+    this.$store
+      .dispatch("imports/loadStretches")
+      .catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      })
+      .finally(() => (this.loading = false));
+    this.loadStagingData();
+  }
+};
+</script>
--- a/client/src/components/systemconfiguration/ColorSettings.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/systemconfiguration/ColorSettings.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -43,11 +43,9 @@
  * Bernhard Reiter <bernhard@intevation.de>
  * Markus Kottländer <markus@intevation.de>
  */
-import { Chrome } from "vue-color";
-import { Compact } from "vue-color";
-
+import { Chrome, Compact } from "vue-color";
 import { HTTP } from "@/lib/http";
-import { displayError } from "@/lib/errors.js";
+import { displayError } from "@/lib/errors";
 
 export default {
   name: "colorsettings",
--- a/client/src/components/systemconfiguration/PDFTemplates.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/systemconfiguration/PDFTemplates.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -13,43 +13,63 @@
     <div class="mt-1 border-bottom pb-4">
       <UITableHeader
         :columns="[
-          { id: 'name', title: `${nameLabel}`, class: 'col-4' },
-          { id: 'time', title: `${dateLabel}`, class: 'col-4' },
+          { id: 'name', title: `${nameLabel}`, class: 'col-3' },
+          { id: 'time', title: `${dateLabel}`, class: 'col-3' },
+          { id: 'type', title: `${typeLabel}`, class: 'col-2' },
           { id: 'country', title: `${countryLabel}`, class: 'col-2' }
         ]"
       />
-      <UITableBody
-        :data="templates | sortTable(sortColumn, sortDirection)"
-        v-slot="{ item: template }"
-      >
-        <div class="py-1 col-4">{{ template.name }}</div>
-        <div class="py-1 col-4">{{ template.time }}</div>
-        <div class="py-1 col-2" v-if="template.country">
-          {{ template.country }}
-        </div>
-        <div class="py-1 col-2" v-else><i>global</i></div>
-        <div class="col py-1 text-right">
-          <button
-            class="btn btn-xs btn-info mr-1"
-            ref="downloadTemplate"
-            @click="downloadTemplate(template)"
-          >
-            <font-awesome-icon icon="download" fixed-width />
-          </button>
-          <button class="btn btn-xs btn-dark" @click="deleteTemplate(template)">
-            <font-awesome-icon icon="trash" fixed-width />
-          </button>
-        </div>
+      <UITableBody :data="templates | sortTable(sortColumn, sortDirection)">
+        <template v-slot:row="{ item: template }">
+          <div class="py-1 col-3">{{ template.name }}</div>
+          <div class="py-1 col-3">{{ template.time }}</div>
+          <div class="py-1 col-2">{{ template.type }}</div>
+          <div class="py-1 col-2" v-if="template.country">
+            {{ template.country }}
+          </div>
+          <div class="py-1 col-2" v-else><i>global</i></div>
+          <div class="col py-1 text-right">
+            <button
+              class="btn btn-xs btn-info mr-1"
+              ref="downloadTemplate"
+              @click="downloadTemplate(template)"
+            >
+              <font-awesome-icon icon="download" fixed-width />
+            </button>
+            <button
+              class="btn btn-xs btn-dark"
+              @click="deleteTemplate(template)"
+            >
+              <font-awesome-icon icon="trash" fixed-width />
+            </button>
+          </div>
+        </template>
       </UITableBody>
-      <button class="btn btn-info mt-2" @click="$refs.uploadTemplate.click()">
-        <font-awesome-icon
-          icon="spinner"
-          class="fa-spin fa-fw"
-          v-if="uploading"
-        />
-        <font-awesome-icon icon="upload" class="fa-fw" v-else />
-        <translate>Upload new template</translate>
-      </button>
+      <div class="d-flex flex-column mt-2 w-25 mr-auto">
+        <select
+          v-model="type"
+          class="form-control d-block custom-select-sm w-75 h-25"
+        >
+          <option :value="null">
+            Select template type
+          </option>
+          <option value="map">
+            Map-template
+          </option>
+          <option value="diagram">
+            Diagram-template
+          </option>
+        </select>
+        <button class="btn btn-info btn-sm mt-1 w-75" @click="checkUpload">
+          <font-awesome-icon
+            icon="spinner"
+            class="fa-spin fa-fw"
+            v-if="uploading"
+          />
+          <font-awesome-icon icon="upload" class="fa-fw" v-else />
+          <translate>Upload new template</translate>
+        </button>
+      </div>
     </div>
   </div>
 </template>
@@ -89,7 +109,8 @@
   data() {
     return {
       templates: [],
-      uploading: false
+      uploading: false,
+      type: null
     };
   },
   computed: {
@@ -101,9 +122,23 @@
     },
     countryLabel() {
       return this.$gettext("Country");
+    },
+    typeLabel() {
+      return this.$gettext("Type");
     }
   },
   methods: {
+    // check if template type is selceted
+    checkUpload() {
+      if (this.type) {
+        this.$refs.uploadTemplate.click();
+      } else {
+        displayError({
+          title: this.$gettext("Error"),
+          message: this.$gettext("Please select template type")
+        });
+      }
+    },
     downloadTemplate(template) {
       if (template) {
         var templateData = "";
@@ -111,7 +146,7 @@
         element.style.display = "none";
         element.setAttribute("download", template.name + ".json");
         document.body.appendChild(element);
-        HTTP.get("/templates/print/" + template.name, {
+        HTTP.get(`/templates/${template.type}/${template.name}`, {
           headers: {
             "X-Gemma-Auth": localStorage.getItem("token"),
             "Content-type": "text/xml; charset=UTF-8"
@@ -161,47 +196,83 @@
               "Uploaded file does not contain valid json data."
             )
           });
+          // allow the user to upload the same file
+          // if user wants to upload the same file after edit it.
+          this.$refs.uploadTemplate.value = null;
         }
         if (template.name) {
-          this.uploading = true;
-          HTTP.post(
-            "/templates/print/" + template.name,
-            {
-              template_name: template.name,
-              template_data: template
-            },
-            {
-              headers: {
-                "X-Gemma-Auth": localStorage.getItem("token"),
-                "Content-type": "text/xml; charset=UTF-8"
-              }
-            }
-          )
-            .then(() => {
-              this.loadTemplates();
-              displayInfo({
+          // check if an element in the uploaded file does not match the predefind template-elements
+          let checkElement = false;
+          template.elements.forEach(e => {
+            if (
+              [
+                "text",
+                "box",
+                "textbox",
+                "image",
+                "bottleneck",
+                "legend",
+                "scalebar",
+                "scale",
+                "northarrow",
+                "diagramlegend",
+                "diagramtitle",
+                "diagram"
+              ].indexOf(e.type) === -1
+            ) {
+              checkElement = true;
+              displayError({
+                title: this.$gettext("Invalid element"),
                 message:
-                  template.name + " " + this.$gettext("uploaded successfully")
+                  e.type +
+                  this.$gettext(" does not match any template's element")
               });
-            })
-            .catch(e => {
-              const { status, data } = e.response;
-              if (status === 400) {
-                displayError({
-                  title: this.$gettext("Error"),
-                  message: `${data.message || data}`
+              // allow the user to upload the same file
+              this.$refs.uploadTemplate.value = null;
+            }
+          });
+
+          if (!checkElement) {
+            this.uploading = true;
+            HTTP.post(
+              "/templates/" + this.type + "/" + template.name,
+              {
+                template_name: template.name,
+                template_data: template
+              },
+              {
+                headers: {
+                  "X-Gemma-Auth": localStorage.getItem("token"),
+                  "Content-type": "text/xml; charset=UTF-8"
+                }
+              }
+            )
+              .then(() => {
+                this.loadTemplates();
+                displayInfo({
+                  message:
+                    template.name + " " + this.$gettext("uploaded successfully")
                 });
-              } else {
-                displayError({
-                  title: this.$gettext("Backend Error"),
-                  message: `${status}: ${data.message || data}`
-                });
-              }
-            })
-            .finally(() => {
-              this.uploading = false;
-              this.$refs.uploadTemplate.value = null;
-            });
+              })
+              .catch(e => {
+                const { status, data } = e.response;
+                if (status === 400) {
+                  displayError({
+                    title: this.$gettext("Error"),
+                    message: `${data.message || data}`
+                  });
+                } else {
+                  displayError({
+                    title: this.$gettext("Backend Error"),
+                    message: `${status}: ${data.message || data}`
+                  });
+                }
+              })
+              .finally(() => {
+                this.uploading = false;
+                this.$refs.uploadTemplate.value = null;
+              });
+          }
         } else {
           displayError({
             title: this.$gettext("Format Error"),
@@ -209,13 +280,16 @@
               "The provided template has no name property."
             )
           });
+          // allow the user to upload the same file
+          this.$refs.uploadTemplate.value = null;
         }
       };
+
       reader.onerror = error => console.log(error);
       reader.readAsText(this.$refs.uploadTemplate.files[0]);
     },
     loadTemplates() {
-      HTTP.get("/templates/print", {
+      HTTP.get("/templates", {
         headers: {
           "X-Gemma-Auth": localStorage.getItem("token"),
           "Content-type": "text/xml; charset=UTF-8"
@@ -246,7 +320,7 @@
           label: this.$gettext("Delete"),
           icon: "trash",
           callback: () => {
-            HTTP.delete("/templates/print/" + template.name, {
+            HTTP.delete(`/templates/${template.type}/${template.name}`, {
               headers: {
                 "X-Gemma-Auth": localStorage.getItem("token"),
                 "Content-type": "text/xml; charset=UTF-8"
--- a/client/src/components/systemconfiguration/Systemconfiguration.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/systemconfiguration/Systemconfiguration.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,6 +1,6 @@
 <template>
   <div class="d-flex flex-row">
-    <Spacer></Spacer>
+    <Spacer />
     <div class="card sysconfig mt-2 shadow-xs">
       <UIBoxHeader icon="wrench" :title="systemconfigurationLabel" />
       <div class="card-body text-left">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/toolbar/AvailableFairwayDepth.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,39 @@
+<template>
+  <div
+    class="toolbar-button"
+    v-tooltip.right="label"
+    @click="$store.commit('application/showFairwayDepth', !showFairwayDepth)"
+  >
+    <font-awesome-icon
+      icon="chart-line"
+      :class="{ 'text-info': showFairwayDepth }"
+    />
+  </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>
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+import { mapState } from "vuex";
+
+export default {
+  computed: {
+    ...mapState("application", ["showFairwayDepth"]),
+    label() {
+      return this.$gettext("Available fairway depth");
+    }
+  }
+};
+</script>
--- a/client/src/components/toolbar/Gauges.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/toolbar/Gauges.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -2,11 +2,12 @@
   <div
     @click="$store.commit('application/showGauges', !showGauges)"
     class="toolbar-button"
+    v-tooltip.right="label"
   >
     <font-awesome-icon
       icon="ruler-vertical"
       :class="{ 'text-info': showGauges }"
-    ></font-awesome-icon>
+    />
   </div>
 </template>
 
@@ -28,7 +29,10 @@
 
 export default {
   computed: {
-    ...mapState("application", ["showGauges"])
+    ...mapState("application", ["showGauges"]),
+    label() {
+      return this.$gettext("Gauges");
+    }
   }
 };
 </script>
--- a/client/src/components/toolbar/Identify.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/toolbar/Identify.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -2,11 +2,9 @@
   <div
     @click="$store.commit('application/showIdentify', !showIdentify)"
     class="toolbar-button"
+    v-tooltip.right="label"
   >
-    <font-awesome-icon
-      icon="info"
-      :class="{ 'text-info': showIdentify }"
-    ></font-awesome-icon>
+    <font-awesome-icon icon="info" :class="{ 'text-info': showIdentify }" />
     <span
       :class="[
         'indicator',
@@ -46,6 +44,9 @@
     ...mapGetters("map", ["filteredIdentifiedFeatures"]),
     badgeCount() {
       return this.filteredIdentifiedFeatures.length + !!this.currentMeasurement;
+    },
+    label() {
+      return this.$gettext("Identified Features");
     }
   }
 };
--- a/client/src/components/toolbar/Layers.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/toolbar/Layers.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -2,11 +2,12 @@
   <div
     @click="$store.commit('application/showLayers', !showLayers)"
     class="toolbar-button"
+    v-tooltip.right="label"
   >
     <font-awesome-icon
       icon="layer-group"
       :class="{ 'text-info': showLayers }"
-    ></font-awesome-icon>
+    />
   </div>
 </template>
 
@@ -29,7 +30,10 @@
 export default {
   name: "layers",
   computed: {
-    ...mapState("application", ["showLayers"])
+    ...mapState("application", ["showLayers"]),
+    label() {
+      return this.$gettext("Map Layers");
+    }
   }
 };
 </script>
--- a/client/src/components/toolbar/Linetool.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/toolbar/Linetool.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,9 +1,6 @@
 <template>
-  <div @click="toggleLineTool" class="toolbar-button">
-    <font-awesome-icon
-      icon="ruler"
-      :class="{ 'text-info': lineTool && lineTool.getActive() }"
-    ></font-awesome-icon>
+  <div @click="toggle" class="toolbar-button" v-tooltip.right="label">
+    <font-awesome-icon icon="ruler" :class="{ 'text-info': lineToolEnabled }" />
   </div>
 </template>
 
@@ -21,22 +18,27 @@
  * Author(s):
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
-import { mapState, mapGetters } from "vuex";
-import { LAYERS } from "@/store/map.js";
+import { mapState } from "vuex";
 
 export default {
   name: "linetool",
   computed: {
-    ...mapGetters("map", ["getVSourceByName"]),
-    ...mapState("map", ["lineTool", "polygonTool", "cutTool"])
+    ...mapState("map", ["openLayersMaps", "lineToolEnabled"]),
+    label() {
+      return this.$gettext("Measure Distance");
+    }
   },
   methods: {
-    toggleLineTool() {
-      this.lineTool.setActive(!this.lineTool.getActive());
-      this.polygonTool.setActive(false);
-      this.cutTool.setActive(false);
+    toggle() {
+      this.$store.commit("map/lineToolEnabled", !this.lineToolEnabled);
+      this.$store.commit("map/polygonToolEnabled", false);
+      this.$store.commit("map/cutToolEnabled", false);
       this.$store.commit("map/setCurrentMeasurement", null);
-      this.getVSourceByName(LAYERS.DRAWTOOL).clear();
+      this.openLayersMaps.forEach(m => {
+        m.getLayer("DRAWTOOL")
+          .getSource()
+          .clear();
+      });
     }
   }
 };
--- a/client/src/components/toolbar/Pdftool.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/toolbar/Pdftool.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -2,11 +2,9 @@
   <div
     @click="$store.commit('application/showPdfTool', !showPdfTool)"
     class="toolbar-button"
+    v-tooltip.right="label"
   >
-    <font-awesome-icon
-      icon="file-pdf"
-      :class="{ 'text-info': showPdfTool }"
-    ></font-awesome-icon>
+    <font-awesome-icon icon="file-pdf" :class="{ 'text-info': showPdfTool }" />
   </div>
 </template>
 
@@ -29,7 +27,10 @@
 export default {
   name: "pdftool",
   computed: {
-    ...mapState("application", ["showPdfTool"])
+    ...mapState("application", ["showPdfTool"]),
+    label() {
+      return this.$gettext("Generate PDF");
+    }
   }
 };
 </script>
--- a/client/src/components/toolbar/Polygontool.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/toolbar/Polygontool.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,9 +1,9 @@
 <template>
-  <div @click="togglePolygonTool" class="toolbar-button">
+  <div @click="toggle" class="toolbar-button" v-tooltip.right="label">
     <font-awesome-icon
       icon="draw-polygon"
-      :class="{ 'text-info': polygonTool && polygonTool.getActive() }"
-    ></font-awesome-icon>
+      :class="{ 'text-info': polygonToolEnabled }"
+    />
   </div>
 </template>
 
@@ -21,22 +21,27 @@
  * Author(s):
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
-import { mapState, mapGetters } from "vuex";
-import { LAYERS } from "@/store/map.js";
+import { mapState } from "vuex";
 
 export default {
   name: "polygontool",
   computed: {
-    ...mapGetters("map", ["getVSourceByName"]),
-    ...mapState("map", ["lineTool", "polygonTool", "cutTool"])
+    ...mapState("map", ["openLayersMaps", "polygonToolEnabled"]),
+    label() {
+      return this.$gettext("Measure Area");
+    }
   },
   methods: {
-    togglePolygonTool() {
-      this.polygonTool.setActive(!this.polygonTool.getActive());
-      this.lineTool.setActive(false);
-      this.cutTool.setActive(false);
+    toggle() {
+      this.$store.commit("map/polygonToolEnabled", !this.polygonToolEnabled);
+      this.$store.commit("map/lineToolEnabled", false);
+      this.$store.commit("map/cutToolEnabled", false);
       this.$store.commit("map/setCurrentMeasurement", null);
-      this.getVSourceByName(LAYERS.DRAWTOOL).clear();
+      this.openLayersMaps.forEach(m => {
+        m.getLayer("DRAWTOOL")
+          .getSource()
+          .clear();
+      });
     }
   }
 };
--- a/client/src/components/toolbar/Profiles.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/toolbar/Profiles.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -2,11 +2,12 @@
   <div
     @click="$store.commit('application/showProfiles', !showProfiles)"
     class="toolbar-button"
+    v-tooltip.right="label"
   >
     <font-awesome-icon
       icon="chart-area"
       :class="{ 'text-info': showProfiles }"
-    ></font-awesome-icon>
+    />
   </div>
 </template>
 
@@ -29,7 +30,10 @@
 export default {
   name: "profiles",
   computed: {
-    ...mapState("application", ["showProfiles"])
+    ...mapState("application", ["showProfiles"]),
+    label() {
+      return this.$gettext("Bottleneck Surveys");
+    }
   }
 };
 </script>
--- a/client/src/components/toolbar/Toolbar.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/toolbar/Toolbar.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -6,13 +6,14 @@
           (expandToolbar ? 'expanded' : 'collapsed')
       "
     >
-      <Identify class="pointer" />
-      <Layers class="pointer" />
-      <Profiles class="pointer" />
-      <Gauges class="pointer" />
-      <Linetool class="pointer" />
-      <Polygontool class="pointer" />
-      <Pdftool class="pointer" />
+      <Identify />
+      <Layers />
+      <Profiles />
+      <Gauges />
+      <AvailableFairwayDepth />
+      <Linetool />
+      <Polygontool />
+      <Pdftool />
     </div>
     <div
       @click="$store.commit('application/expandToolbar', !expandToolbar)"
@@ -21,7 +22,7 @@
       <font-awesome-icon
         class="pointer"
         :icon="expandToolbar ? 'angle-up' : 'angle-down'"
-      ></font-awesome-icon>
+      />
     </div>
   </div>
 </template>
@@ -34,10 +35,11 @@
   overflow: hidden;
   transition: max-height 0.4s;
   margin-bottom: auto;
+  cursor: pointer;
 }
 
 .toolbar-collapsed {
-  max-height: 6rem;
+  max-height: 4rem;
 }
 
 .toolbar-expanded {
@@ -58,6 +60,10 @@
   pointer-events: auto;
   position: relative;
   overflow: hidden;
+  &.disabled {
+    color: #ccc;
+    cursor: default;
+  }
 }
 
 .toolbar-button:last-child {
@@ -110,37 +116,22 @@
  * Author(s):
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
-import { mapState, mapGetters } from "vuex";
-import { LAYERS } from "@/store/map.js";
+import { mapState } from "vuex";
 
 export default {
   name: "toolbar",
   components: {
-    Identify: () => import("./Identify.vue"),
-    Layers: () => import("./Layers.vue"),
-    Linetool: () => import("./Linetool.vue"),
-    Polygontool: () => import("./Polygontool.vue"),
-    Profiles: () => import("./Profiles.vue"),
-    Gauges: () => import("./Gauges.vue"),
-    Pdftool: () => import("./Pdftool.vue")
+    Identify: () => import("./Identify"),
+    Layers: () => import("./Layers"),
+    Linetool: () => import("./Linetool"),
+    Polygontool: () => import("./Polygontool"),
+    Profiles: () => import("./Profiles"),
+    Gauges: () => import("./Gauges"),
+    Pdftool: () => import("./Pdftool"),
+    AvailableFairwayDepth: () => import("./AvailableFairwayDepth")
   },
   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(LAYERS.DRAWTOOL).clear();
-      }
-    });
   }
 };
 </script>
--- a/client/src/components/ui/UIBoxHeader.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/ui/UIBoxHeader.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,5 +1,5 @@
 <template>
-  <h6 :class="['box-header', { 'rounded border-0 shadow-xs': collapsed }]">
+  <h6 :class="['box-header', { small }]">
     <span class="box-title">
       <font-awesome-icon
         :icon="icon"
@@ -9,21 +9,19 @@
       />
       {{ title }}
     </span>
-    <div class="box-controls">
+    <div class="d-flex flex-row">
       <span
+        class="box-control"
         v-for="(action, index) in actions"
         :key="index"
         @click="action.callback"
       >
-        <font-awesome-icon :icon="action.icon" />
-      </span>
-      <span @click="collapseCallback" v-if="!collapsed && collapseCallback">
-        <font-awesome-icon :icon="['far', 'window-minimize']" />
+        <font-awesome-icon
+          :icon="action.icon"
+          :spin="action.icon === 'spinner'"
+        />
       </span>
-      <span @click="expandCallback" v-if="collapsed && expandCallback">
-        <font-awesome-icon :icon="['far', 'window-maximize']" />
-      </span>
-      <span @click="closeCallback" v-if="closeCallback">
+      <span class="box-control" @click="closeCallback" v-if="closeCallback">
         <font-awesome-icon icon="times" />
       </span>
     </div>
@@ -49,18 +47,11 @@
     padding-left: 0.25rem
     .box-icon
       margin-right: 0.25rem
-  .box-controls
-    span
-      display: inline-block
-      margin-left: 3px
-      color: #888
-      padding: 3px 7px
-      border-radius: 0.25rem
-      cursor: pointer
-      transition: background-color 0.3s, color 0.3s
-      &:hover
-        color: #666
-        background-color: #eee
+  .box-control
+    margin-left: 3px
+  &.small
+    padding: 0.1rem 0.1rem 0.1rem 0.25rem
+    min-height: 27px
 </style>
 
 <script>
@@ -79,14 +70,12 @@
  */
 
 export default {
-  props: [
-    "icon",
-    "title",
-    "collapseCallback",
-    "closeCallback",
-    "expandCallback",
-    "actions",
-    "collapsed"
-  ]
+  props: {
+    icon: String,
+    title: String,
+    closeCallback: Function,
+    actions: Array,
+    small: Boolean
+  }
 };
 </script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/ui/UISpinnerButton.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,56 @@
+<template>
+  <div :class="classesString" @click="$emit('click')">
+    <font-awesome-icon :icon="iconString" :spin="loading" fixed-width />
+    <slot />
+  </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>
+ */
+export default {
+  props: {
+    loading: {
+      type: Boolean,
+      default: false
+    },
+    state: [Number, Boolean],
+    icons: {
+      type: [String, Array],
+      default: () => ["angle-down", "angle-up"]
+    },
+    classes: {
+      type: [String, Array],
+      default: () => ["text-info", "text-white"]
+    }
+  },
+  computed: {
+    classesString() {
+      return (
+        "pointer " +
+        (Array.isArray(this.classes)
+          ? this.classes[Number(this.state)]
+          : this.classes)
+      );
+    },
+    iconString() {
+      return this.loading
+        ? "spinner"
+        : Array.isArray(this.icons)
+        ? this.icons[Number(this.state)]
+        : this.icons;
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/ui/UISpinnerOverlay.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,38 @@
+<template>
+  <transition name="fade">
+    <div class="spinner-overlay">
+      <font-awesome-icon icon="spinner" spin />
+    </div>
+  </transition>
+</template>
+
+<style lang="sass">
+.spinner-overlay
+  background: rgba(255, 255, 255, 0.9)
+  position: absolute
+  z-index: 99
+  top: 0
+  right: 0
+  bottom: 0
+  left: 0
+  display: flex
+  align-items: center
+  justify-content: center
+  color: #888
+</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>
+ */
+</script>
--- a/client/src/components/ui/UITableBody.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/ui/UITableBody.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -7,12 +7,14 @@
     <div
       v-for="(item, index) in data"
       :key="key(index)"
-      :class="[
-        'border-top row mx-0 align-items-center',
-        { active: active === item }
-      ]"
+      :class="['row-container border-top', { active: isActive(item) }]"
     >
-      <slot :item="item" :index="index"></slot>
+      <div class="row mx-0">
+        <slot :item="item" :index="index" name="row"></slot>
+      </div>
+      <div class="expand" v-if="isActive(item)">
+        <slot :item="item" :index="index" name="expand"></slot>
+      </div>
     </div>
   </div>
   <div v-else class="small text-center py-3 border-top">
@@ -20,6 +22,35 @@
   </div>
 </template>
 
+<style lang="sass">
+.table-body
+  .row-container
+    > .row
+      &:hover
+        background-color: #fcfcfc
+      .table-cell
+        display: flex
+        align-items: center
+        padding: 1.5px 3px
+        border-right: solid 1px #dee2e6
+        &:last-child
+          border-right: none
+        &.center
+          justify-content: center
+    .expand
+      border-bottom: solid 2px $color-info
+
+    &.active
+      > .row
+        color: #fff
+        .table-cell
+          border-right-color: rgba(255, 255, 255, 0.3)
+          background-color: $color-info
+          color: #fff
+          a
+            color: #fff !important
+</style>
+
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -44,8 +75,9 @@
       type: String,
       default: "18rem"
     },
-    active: {
-      type: [Object, Array]
+    isActive: {
+      type: Function,
+      default: () => false
     }
   },
   methods: {
--- a/client/src/components/ui/UITableHeader.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/ui/UITableHeader.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -5,7 +5,7 @@
       @click.prevent="!column.disableSorting && sortTable(column.id)"
       :key="column.id"
       :class="[
-        'd-flex py-1 align-items-center justify-content-center small ' +
+        'd-inline-block py-1 text-center truncate small ' +
           (column.class || '') +
           ' ' +
           (column.disableSorting ? ' sorting-disabled' : ''),
--- a/client/src/components/usermanagement/Passwordfield.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/usermanagement/Passwordfield.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -12,14 +12,12 @@
         :required="required"
       />
       <span class="input-group-text" @click="showPassword">
-        <font-awesome-icon
-          :icon="readablePassword ? 'eye-slash' : 'eye'"
-        ></font-awesome-icon>
+        <font-awesome-icon :icon="readablePassword ? 'eye-slash' : 'eye'" />
       </span>
     </div>
     <div v-show="passworderrors" class="text-danger">
       <small>
-        <font-awesome-icon icon="exclamation-triangle"></font-awesome-icon>
+        <font-awesome-icon icon="exclamation-triangle" />
         {{ this.passworderrors }}
       </small>
     </div>
--- a/client/src/components/usermanagement/Userdetail.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/usermanagement/Userdetail.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -20,9 +20,7 @@
             />
             <div v-show="errors.user" class="text-danger">
               <small>
-                <font-awesome-icon
-                  icon="exclamation-triangle"
-                ></font-awesome-icon>
+                <font-awesome-icon icon="exclamation-triangle" />
                 {{ errors.user }}
               </small>
             </div>
@@ -46,9 +44,7 @@
             </select>
             <div v-show="errors.country" class="text-danger">
               <small>
-                <font-awesome-icon
-                  icon="exclamation-triangle"
-                ></font-awesome-icon>
+                <font-awesome-icon icon="exclamation-triangle" />
                 {{ errors.country }}
               </small>
             </div>
@@ -65,9 +61,7 @@
             />
             <div v-show="errors.email" class="text-danger">
               <small>
-                <font-awesome-icon
-                  icon="exclamation-triangle"
-                ></font-awesome-icon>
+                <font-awesome-icon icon="exclamation-triangle" />
                 {{ errors.email }}
               </small>
             </div>
@@ -94,9 +88,7 @@
             </select>
             <div v-show="errors.role" class="text-danger">
               <small>
-                <font-awesome-icon
-                  icon="exclamation-triangle"
-                ></font-awesome-icon>
+                <font-awesome-icon icon="exclamation-triangle" />
                 {{ errors.role }}
               </small>
             </div>
@@ -107,7 +99,7 @@
               :placeholder="passwordPlaceholder"
               :label="passwordLabel"
               :passworderrors="errors.password"
-            ></PasswordField>
+            />
           </div>
           <div class="form-group row">
             <PasswordField
@@ -115,7 +107,7 @@
               :placeholder="passwordRePlaceholder"
               :label="passwordReLabel"
               :passworderrors="errors.passwordre"
-            ></PasswordField>
+            />
           </div>
         </div>
         <div>
@@ -124,7 +116,7 @@
             :disabled="submitted"
             class="shadow-sm btn btn-info submit-button"
           >
-            <translate>Submit</translate>
+            <translate>Save</translate>
           </button>
         </div>
       </form>
@@ -173,7 +165,7 @@
  * Author(s):
  * Thomas Junk <thomas.junk@intevation.de>
  */
-import { displayError } from "@/lib/errors.js";
+import { displayError } from "@/lib/errors";
 import { mapState } from "vuex";
 
 const emptyErrormessages = () => {
--- a/client/src/components/usermanagement/Usermanagement.vue	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/components/usermanagement/Usermanagement.vue	Mon Jun 03 10:19:18 2019 +0200
@@ -1,6 +1,6 @@
 <template>
   <div class="main d-flex flex-row" style="position: relative;">
-    <Spacer></Spacer>
+    <Spacer />
     <div class="d-flex content py-2">
       <div :class="userlistStyle">
         <div class="card shadow-xs">
@@ -8,86 +8,89 @@
           <UITableHeader
             :columns="[
               { id: 'role', title: `${roleForColumLabel}`, class: 'col-1' },
-              { id: 'user', title: `${usernameLabel}`, class: 'col-3' },
+              { id: 'user', title: `${usernameLabel}`, class: 'col-4' },
               { id: 'country', title: `${countryLabel}`, class: 'col-2' },
               { id: 'email', title: `${emailLabel}`, class: 'col-3' }
             ]"
           />
           <UITableBody
             :data="users | sortTable(sortColumn, sortDirection, page, pageSize)"
+            :isActive="item => item === currentUser"
             maxHeight="47rem"
-            :active="currentUser"
-            v-slot="{ item: user }"
           >
-            <div class="py-1 col-1" @click="selectUser(user.user)">
-              <font-awesome-icon
-                v-tooltip="roleLabel(user.role)"
-                :icon="roleIcon(user.role)"
-                class="fa-lg"
-              ></font-awesome-icon>
-            </div>
-            <div class="py-1 col-3" @click="selectUser(user.user)">
-              {{ user.user }}
-            </div>
-            <div class="py-1 col-2" @click="selectUser(user.user)">
-              {{ user.country }}
-            </div>
-            <div class="py-1 col-3" @click="selectUser(user.user)">
-              {{ user.email }}
-            </div>
-            <div class="py-1 col text-right">
+            <template v-slot:row="{ item: user }">
+              <div
+                class="table-cell center col-1"
+                @click="selectUser(user.user)"
+              >
+                <font-awesome-icon
+                  v-tooltip="roleLabel(user.role)"
+                  :icon="roleIcon(user.role)"
+                  class="fa-lg"
+                />
+              </div>
+              <div class="table-cell col-4" @click="selectUser(user.user)">
+                {{ user.user }}
+              </div>
+              <div
+                class="table-cell center col-2"
+                @click="selectUser(user.user)"
+              >
+                {{ user.country }}
+              </div>
+              <div class="table-cell col-3" @click="selectUser(user.user)">
+                {{ user.email }}
+              </div>
+              <div class="table-cell col text-right justify-content-end">
+                <button
+                  @click="sendTestMail(user.user)"
+                  class="btn btn-xs btn-dark mr-1"
+                  v-tooltip="sendMailLabel"
+                  v-if="user.email"
+                >
+                  <font-awesome-icon icon="paper-plane" fixed-width />
+                </button>
+                <button
+                  @click="deleteUser(user.user)"
+                  class="btn btn-xs btn-dark"
+                  v-tooltip="deleteUserLabel"
+                >
+                  <font-awesome-icon icon="trash" fixed-width />
+                </button>
+              </div>
+            </template>
+          </UITableBody>
+          <div class="p-3 border-top d-flex justify-content-between">
+            <div></div>
+            <div>
               <button
-                @click="sendTestMail(user.user)"
-                class="btn btn-xs btn-dark mr-1"
-                v-tooltip="sendMailLabel"
-                v-if="user.email"
+                @click="prevPage"
+                v-if="this.page !== 1"
+                class="mr-2 btn btn-sm btn-light align-self-center"
               >
-                <font-awesome-icon icon="paper-plane" fixed-width />
+                <font-awesome-icon icon="angle-left" />
               </button>
+              {{ this.page }} / {{ this.pages }}
               <button
-                @click="deleteUser(user.user)"
-                class="btn btn-xs btn-dark"
-                v-tooltip="deleteUserLabel"
+                @click="nextPage"
+                v-if="this.page !== this.pages"
+                class="ml-2 btn btn-sm btn-light align-self-center"
               >
-                <font-awesome-icon icon="trash" fixed-width />
+                <font-awesome-icon icon="angle-right" />
               </button>
             </div>
-          </UITableBody>
-          <div class="d-flex mx-auto align-items-center">
-            <button
-              @click="prevPage"
-              v-if="this.page !== 1"
-              class="mr-2 btn btn-sm btn-light align-self-center"
-            >
-              <font-awesome-icon icon="angle-left"></font-awesome-icon>
-            </button>
-            {{ this.page }} / {{ this.pages }}
-            <button
-              @click="nextPage"
-              v-if="this.page !== 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 py-3 text-right">
             <button @click="addUser" class="btn btn-info addbutton shadow-sm">
               <translate>Add User</translate>
             </button>
           </div>
         </div>
       </div>
-      <Userdetail v-if="isUserDetailsVisible"></Userdetail>
+      <Userdetail v-if="isUserDetailsVisible" />
     </div>
   </div>
 </template>
 
 <style lang="sass" scoped>
-.addbutton
-  position: absolute
-  bottom: $offset
-  right: $offset
-
 .content
   width: 100%
 
@@ -109,16 +112,6 @@
 
 .userlistextended
   width: 100%
-
-.table-body
-  /deep/.row
-    > div
-      transition: background-color 0.3s, color 0.3s
-    &.active
-      background-color: $color-info
-      color: #fff
-      a
-        color: #fff !important
 </style>
 
 <script>
@@ -137,16 +130,10 @@
  */
 import store from "@/store";
 import { mapGetters, mapState } from "vuex";
-import { displayError, displayInfo } from "@/lib/errors.js";
+import { displayError, displayInfo } from "@/lib/errors";
 import { HTTP } from "@/lib/http";
-import Vue from "vue";
-import { VTooltip, VPopover, VClosePopover } from "v-tooltip";
 import { sortTable } from "@/lib/mixins";
 
-Vue.directive("tooltip", VTooltip);
-Vue.directive("close-popover", VClosePopover);
-Vue.component("v-popover", VPopover);
-
 export default {
   name: "userview",
   mixins: [sortTable],
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/lib/classifications.js	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,92 @@
+/* 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>
+ */
+
+const getGauge = f => {
+  if (f.getId().indexOf("bottlenecks") > -1) {
+    return f.get("gauge_obj");
+  }
+  return f;
+};
+
+export default {
+  surveyCurrency(bottleneck) {
+    if (
+      bottleneck.get("revisiting_time") === null ||
+      bottleneck.get("revisiting_time") === 0
+    ) {
+      return "white";
+    }
+    if (bottleneck.get("date_max") === null) {
+      return "red";
+    }
+    let revTime = bottleneck.get("revisiting_time") * 30.5;
+    let latest = Date.parse(bottleneck.get("date_max").replace("Z", ""));
+    var diff = Math.floor((Date.now() - latest) / 86400000);
+    if (diff <= revTime) {
+      return "lime";
+    } else if (revTime < diff && diff <= revTime * 1.5) {
+      return "yellow";
+    } else if (revTime * 1.5 < diff) {
+      return "red";
+    }
+  },
+  gmAvailability(feature) {
+    let gauge = getGauge(feature);
+    let gmDate = gauge.get("gm_measuredate");
+    let gmN = gauge.get("gm_n_14d");
+    if (
+      gmDate !== undefined &&
+      gmDate !== null &&
+      Date.parse(gmDate) > Date.now() - 86400000 // latest value within 24 h
+    ) {
+      // 1344: one value every 15 min in 14 days, but the Hydra says:
+      // let 85% be enough for now.
+      const valuesAtLeast = 1124;
+      if (gmN !== undefined && gmN !== null && gmN >= valuesAtLeast) {
+        return "lime";
+      }
+      return "yellow";
+    }
+    return "red";
+  },
+  forecastAccuracy(feature) {
+    let gauge = getGauge(feature);
+    let fa3d = gauge.get("forecast_accuracy_3d");
+    let fa1d = gauge.get("forecast_accuracy_1d");
+    if (typeof fa3d == "number" && typeof fa1d == "number") {
+      if (fa1d > 15) {
+        return "red";
+      } else if (fa3d > 15) {
+        return "yellow";
+      } else {
+        return "lime";
+      }
+    }
+    return "white";
+  },
+  forecastVsReality(feature) {
+    let gauge = getGauge(feature);
+    let nsc = gauge.get("nsc_data");
+    if (nsc && nsc.coeffs.reduce((sum, coeff) => sum + coeff.samples, 0)) {
+      // 24h < 12.5
+      if (nsc.coeffs[0].samples && nsc.coeffs[0].value < -12.5) return "red";
+      // 72h < 12.5
+      if (nsc.coeffs[2].samples && nsc.coeffs[2].value < -12.5) return "yellow";
+      // both > 12.5
+      return "lime";
+    }
+    // no data available
+    return "white";
+  }
+};
--- a/client/src/lib/errors.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/lib/errors.js	Mon Jun 03 10:19:18 2019 +0200
@@ -12,7 +12,7 @@
  * Thomas Junk <thomas.junk@intevation.de>
  */
 
-import app from "../main";
+import app from "@/main";
 
 let displayOptions = {
   timeout: 2500,
--- a/client/src/lib/filters.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/lib/filters.js	Mon Jun 03 10:19:18 2019 +0200
@@ -25,20 +25,22 @@
         })
       : "";
   },
-  dateTime(date) {
+  dateTime(date, hideTime) {
     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
-      })
-    );
+    let dateString = d.toLocaleDateString(locale2, {
+      day: "2-digit",
+      month: "2-digit",
+      year: "numeric"
+    });
+    if (!hideTime) {
+      dateString +=
+        " - " +
+        d.toLocaleTimeString(locale2, {
+          hour12: false
+        });
+    }
+    return dateString;
   },
   sortTable(data, sortColumn, sortDirection, page, pageSize) {
     // clone the array and leave the original intact
--- a/client/src/lib/geo.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/lib/geo.js	Mon Jun 03 10:19:18 2019 +0200
@@ -20,7 +20,7 @@
  *
  */
 
-import { GeoJSON } from "ol/format.js";
+import { GeoJSON } from "ol/format";
 import Feature from "ol/Feature";
 import distance from "@turf/distance";
 import {
--- a/client/src/lib/http.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/lib/http.js	Mon Jun 03 10:19:18 2019 +0200
@@ -12,7 +12,7 @@
  * Thomas Junk <thomas.junk@intevation.de>
  */
 
-import { logOff } from "@/lib/session.js";
+import { logOff } from "@/lib/session";
 
 import axios from "axios";
 
--- a/client/src/lib/mixins.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/lib/mixins.js	Mon Jun 03 10:19:18 2019 +0200
@@ -10,9 +10,11 @@
  *
  * Author(s):
  * Markus Kottländer <markus.kottlaender@intevation.de>
+ * Fadi Abbud <fadi.abbud@intevation.de>
  */
-
-const sortTable = {
+import locale2 from "locale2";
+import { mapState } from "vuex";
+export const sortTable = {
   data() {
     return {
       sortColumn: "",
@@ -29,4 +31,199 @@
   }
 };
 
-export { sortTable };
+export const diagram = {
+  methods: {
+    getDimensions({ main, nav }) {
+      //dimensions and margins
+      const elem = document.querySelector("#" + this.containerId);
+      const svgWidth = elem != null ? elem.clientWidth : 0;
+      const svgHeight = elem != null ? elem.clientHeight : 0;
+      const mainMargin = main || { top: 20, right: 20, bottom: 110, left: 80 };
+      const navMargin = nav || {
+        top: svgHeight - mainMargin.top - 65,
+        right: 20,
+        bottom: 30,
+        left: 80
+      };
+      const width = Number(svgWidth) - mainMargin.left - mainMargin.right;
+      const mainHeight = Number(svgHeight) - mainMargin.top - mainMargin.bottom;
+      const navHeight = Number(svgHeight) - navMargin.top - navMargin.bottom;
+      return { width, mainHeight, navHeight, mainMargin, navMargin };
+    }
+  }
+};
+
+export const pane = {
+  computed: {
+    paneId() {
+      return this.$parent.pane.id;
+    }
+  }
+};
+
+export const pdfgen = {
+  computed: {
+    ...mapState("application", ["logoForPDF"]),
+    ...mapState("user", ["user"])
+  },
+  methods: {
+    // add text at specific coordinates and determine how many wrolds in a single line
+    addText(position, offset, width, fontSize, color, text) {
+      text = this.replacePlaceholders(text);
+      // split the incoming string to an array, each element is a string of
+      // words in a single line
+      this.pdf.doc.setFontStyle("normal");
+      this.pdf.doc.setTextColor(color);
+      this.pdf.doc.setFontSize(fontSize);
+      var textLines = this.pdf.doc.splitTextToSize(text, width);
+      // x/y defaults to offset for topleft corner (normal x/y coordinates)
+      let x = offset.x;
+      let y = offset.y;
+      // if position is on the right, x needs to be calculate with pdf width and
+      // the size of the element
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - this.getTextHeight(textLines.length);
+      }
+      this.pdf.doc.text(textLines, x, y, { baseline: "hanging" });
+    },
+    replacePlaceholders(text) {
+      if (text.includes("{date}")) {
+        text = text.replace("{date}", new Date().toLocaleString(locale2));
+      }
+      // get only day,month and year from the Date object
+      if (text.includes("{date-minor}")) {
+        var date = new Date();
+        var dt =
+          (date.getDate() < 10 ? "0" : "") +
+          date.getDate() +
+          "." +
+          (date.getMonth() + 1 < 10 ? "0" : "") +
+          (date.getMonth() + 1) +
+          "." +
+          date.getFullYear();
+        text = text.replace("{date-minor}", dt.toLocaleString(locale2));
+      }
+      if (text.includes("{user}")) {
+        text = text.replace("{user}", this.user);
+      }
+      return text;
+    },
+    addImage(url, format, position, offset, width, height) {
+      let x = offset.x;
+      let y = offset.y;
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - height;
+      }
+      let image = new Image();
+      if (url) {
+        image.src = url;
+      } else {
+        if (this.logoForPDF) {
+          image.src = this.logoForPDF;
+        } else {
+          image.src = "/img/gemma-logo-for-pdf.png";
+        }
+      }
+      if (format === "") {
+        let tmp = image.src.split(".");
+        format = tmp[tmp.length - 1].toUpperCase();
+      }
+      this.pdf.doc.addImage(image, format, x, y, width, height);
+    },
+    // add text at specific coordinates with a background box
+    addBox(position, offset, width, height, rounding, color, brcolor) {
+      // x/y defaults to offset for topleft corner (normal x/y coordinates)
+      let x = offset.x;
+      let y = offset.y;
+
+      // if position is on the right, x needs to be calculate with pdf width and
+      // the size of the element
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - height;
+      }
+      this.addRoundedBox(x, y, width, height, color, rounding, brcolor);
+    },
+    getTextHeight(numberOfLines) {
+      return (
+        numberOfLines *
+        ((this.pdf.doc.getFontSize() * 25.4) / 80) *
+        this.pdf.doc.getLineHeightFactor()
+      );
+    },
+    // title for diagram
+    addDiagramTitle(position, offset, size, color, text) {
+      let x = offset.x,
+        y = offset.y;
+      this.pdf.doc.setFontSize(size);
+      this.pdf.doc.setFontStyle("bold");
+      this.pdf.doc.setTextColor(color);
+      let width =
+        (this.pdf.doc.getStringUnitWidth(text) * size) / (72 / 25.6) + size / 2;
+      // if position is on the right, x needs to be calculate with pdf width and
+      // the size of the element
+      if (["topright", "bottomright"].indexOf(position) !== -1) {
+        x = this.pdf.width - offset.x - width;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - this.getTextHeight(1);
+      }
+      this.pdf.doc.text(text, x, y, { baseline: "hanging" });
+    },
+    addRoundedBox(x, y, w, h, color, rounding, brcolor) {
+      this.pdf.doc.setDrawColor(brcolor);
+      this.pdf.doc.setFillColor(color);
+      this.pdf.doc.roundedRect(x, y, w, h, rounding, rounding, "FD");
+    },
+    addTextBox(
+      position,
+      offset,
+      width,
+      height,
+      rounding,
+      padding,
+      fontSize,
+      color,
+      background,
+      text,
+      brcolor
+    ) {
+      this.pdf.doc.setFontSize(fontSize);
+      text = this.replacePlaceholders(text);
+
+      if (!width) {
+        width = this.pdf.doc.getTextWidth(text) + 2 * padding;
+      }
+      let textWidth = width - 2 * padding;
+      if (!height) {
+        let textLines = this.pdf.doc.splitTextToSize(text, textWidth);
+        height = this.getTextHeight(textLines.length) + 2 * padding;
+      }
+      this.addBox(
+        position,
+        offset,
+        width,
+        height,
+        rounding,
+        background,
+        brcolor
+      );
+      this.addText(
+        position,
+        { x: offset.x + padding, y: offset.y + padding },
+        textWidth,
+        fontSize,
+        color,
+        text
+      );
+    }
+  }
+};
--- a/client/src/main.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/main.js	Mon Jun 03 10:19:18 2019 +0200
@@ -28,11 +28,13 @@
 import store from "@/store";
 import translations from "@/locale/translations.json";
 import filters from "@/lib/filters";
-import { supportedLanguages, defaultLanguage } from "./locale/languages.js";
-import App from "@/components/App.vue";
+import { supportedLanguages, defaultLanguage } from "./locale/languages";
+import App from "@/components/App";
 import UIBoxHeader from "@/components/ui/UIBoxHeader";
 import UITableHeader from "@/components/ui/UITableHeader";
 import UITableBody from "@/components/ui/UITableBody";
+import UISpinnerOverlay from "@/components/ui/UISpinnerOverlay";
+import UISpinnerButton from "@/components/ui/UISpinnerButton";
 
 // styles
 import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
@@ -47,10 +49,12 @@
   faAngleLeft,
   faAngleRight,
   faAngleUp,
+  faAngleDoubleRight,
   faBars,
   faBook,
-  faBullseye,
   faChartArea,
+  faChartBar,
+  faChartLine,
   faCheck,
   faCity,
   faClipboardCheck,
@@ -64,10 +68,13 @@
   faEyeSlash,
   faFilePdf,
   faFolderPlus,
+  faFrownOpen,
   faInfo,
   faLayerGroup,
+  faLink,
   faMapMarkedAlt,
   faMinus,
+  faObjectGroup,
   faPaperPlane,
   faPencilAlt,
   faPlay,
@@ -88,12 +95,14 @@
   faTasks,
   faTimes,
   faTrash,
+  faUnlink,
   faUpload,
   faUser,
   faUsersCog,
   faWater,
   faWrench,
   faRedo,
+  faSync,
   faCrosshairs
 } from "@fortawesome/free-solid-svg-icons";
 import {
@@ -108,10 +117,12 @@
   faAngleLeft,
   faAngleRight,
   faAngleUp,
+  faAngleDoubleRight,
   faBars,
   faBook,
-  faBullseye,
   faChartArea,
+  faChartBar,
+  faChartLine,
   faCheck,
   faCity,
   faClipboardCheck,
@@ -125,10 +136,13 @@
   faEyeSlash,
   faFilePdf,
   faFolderPlus,
+  faFrownOpen,
   faInfo,
   faLayerGroup,
+  faLink,
   faMapMarkedAlt,
   faMinus,
+  faObjectGroup,
   faPaperPlane,
   faPencilAlt,
   faPlay,
@@ -149,22 +163,24 @@
   faTasks,
   faTimes,
   faTrash,
+  faUnlink,
   faUpload,
   faUser,
   faUsersCog,
   faWater,
   faWrench,
   faRedo,
+  faSync,
   faWindowMinimize,
   faWindowMaximize,
   faCrosshairs
 );
-
 // register plugins
 Vue.use(GetTextPlugin, {
   translations: translations,
   availableLanguages: supportedLanguages,
-  defaultLanguage: defaultLanguage
+  defaultLanguage: defaultLanguage,
+  silent: process.env.VUE_APP_SILENCE_TRANSLATIONWARNINGS
 });
 Vue.use(Snotify, { toast: { position: SnotifyPosition.centerBottom } });
 Vue.use(ToggleButton);
@@ -176,6 +192,8 @@
 Vue.component("UIBoxHeader", UIBoxHeader);
 Vue.component("UITableHeader", UITableHeader);
 Vue.component("UITableBody", UITableBody);
+Vue.component("UISpinnerOverlay", UISpinnerOverlay);
+Vue.component("UISpinnerButton", UISpinnerButton);
 
 // register global filters
 for (let name in filters) Vue.filter(name, filters[name]);
--- a/client/src/router.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/router.js	Mon Jun 03 10:19:18 2019 +0200
@@ -19,8 +19,8 @@
 import { sessionStillActive, toMillisFromString } from "./lib/session";
 
 /*  facilitate codesplitting */
-const Login = () => import("./components/Login.vue");
-const Maplayer = () => import("./components/Maplayer.vue");
+const Login = () => import("./components/Login");
+const Main = () => import("./components/Main");
 
 Vue.use(Router);
 
@@ -34,14 +34,14 @@
     {
       path: "/usermanagement",
       name: "usermanagement",
-      component: () => import("./components/usermanagement/Usermanagement.vue"),
+      component: () => import("./components/usermanagement/Usermanagement"),
       meta: {
         requiresAuth: true
       },
       beforeEnter: (to, from, next) => {
         const isSysadmin = store.getters["user/isSysAdmin"];
         if (!isSysadmin) {
-          next("/");
+          next("/login");
         } else {
           next();
         }
@@ -50,14 +50,14 @@
     {
       path: "/logs",
       name: "logs",
-      component: () => import("./components/Logs.vue"),
+      component: () => import("./components/Logs"),
       meta: {
         requiresAuth: true
       },
       beforeEnter: (to, from, next) => {
         const isSysadmin = store.getters["user/isSysAdmin"];
         if (!isSysadmin) {
-          next("/");
+          next("/login");
         } else {
           next();
         }
@@ -67,78 +67,14 @@
       path: "/systemconfiguration",
       name: "systemconfiguration",
       component: () =>
-        import("./components/systemconfiguration/Systemconfiguration.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"),
+        import("./components/systemconfiguration/Systemconfiguration"),
       meta: {
         requiresAuth: true
       },
       beforeEnter: (to, from, next) => {
         const isWaterwayAdmin = store.getters["user/isWaterwayAdmin"];
         if (!isWaterwayAdmin) {
-          next("/");
-        } else {
-          next();
-        }
-      }
-    },
-    {
-      path: "/importwaterwayprofiles",
-      name: "waterwayprofiles",
-      component: () => import("./components/ImportWaterwayProfiles"),
-      meta: {
-        requiresAuth: true
-      },
-      beforeEnter: (to, from, next) => {
-        const isWaterwayAdmin = store.getters["user/isWaterwayAdmin"];
-        if (!isWaterwayAdmin) {
-          next("/");
-        } else {
-          next();
-        }
-      }
-    },
-    {
-      path: "/importapprovedgaugemeasurement",
-      name: "approvedgaugemeasurement",
-      component: () => import("./components/ImportApprovedGaugeMeasurement"),
-      meta: {
-        requiresAuth: true
-      },
-      beforeEnter: (to, from, next) => {
-        const isWaterwayAdmin = store.getters["user/isWaterwayAdmin"];
-        if (!isWaterwayAdmin) {
-          next("/");
-        } else {
-          next();
-        }
-      }
-    },
-    {
-      path: "/importschedule",
-      name: "importschedule",
-      component: () => import("./components/importschedule/Importschedule.vue"),
-      meta: {
-        requiresAuth: true
-      },
-      beforeEnter: (to, from, next) => {
-        const isWaterwayAdmin = store.getters["user/isWaterwayAdmin"];
-        if (!isWaterwayAdmin) {
-          next("/");
+          next("/login");
         } else {
           next();
         }
@@ -147,7 +83,7 @@
     {
       path: "/",
       name: "mainview",
-      component: Maplayer,
+      component: Main,
       meta: {
         requiresAuth: true
       },
@@ -162,7 +98,7 @@
     {
       path: "/bottlenecks",
       name: "bottlenecks",
-      component: Maplayer,
+      component: Main,
       meta: {
         requiresAuth: true
       },
@@ -175,16 +111,36 @@
       }
     },
     {
-      path: "/imports/overview/:id?",
-      name: "importoverview",
-      component: Maplayer,
+      path: "/imports/configuration",
+      name: "importconfiguration",
+      component: Main,
       meta: {
         requiresAuth: true
       },
       beforeEnter: (to, from, next) => {
         const isWaterwayAdmin = store.getters["user/isWaterwayAdmin"];
         if (!isWaterwayAdmin) {
-          next("/");
+          next("/login");
+        } else {
+          store.commit("application/searchQuery", "");
+          store.commit("application/showContextBox", true);
+          store.commit("application/contextBoxContent", "importconfiguration");
+          store.commit("application/showSearchbar", true);
+          next();
+        }
+      }
+    },
+    {
+      path: "/imports/overview/:id?",
+      name: "importoverview",
+      component: Main,
+      meta: {
+        requiresAuth: true
+      },
+      beforeEnter: (to, from, next) => {
+        const isWaterwayAdmin = store.getters["user/isWaterwayAdmin"];
+        if (!isWaterwayAdmin) {
+          next("/login");
         } else {
           store.commit("application/showContextBox", true);
           store.commit("application/contextBoxContent", "importoverview");
@@ -196,14 +152,14 @@
     {
       path: "/stretches",
       name: "stretches",
-      component: Maplayer,
+      component: Main,
       meta: {
         requiresAuth: true
       },
       beforeEnter: (to, from, next) => {
         const isSysadmin = store.getters["user/isSysAdmin"];
         if (!isSysadmin) {
-          next("/");
+          next("/login");
         } else {
           store.commit("application/searchQuery", "");
           store.commit("application/showContextBox", true);
@@ -214,8 +170,36 @@
       }
     },
     {
+      path: "/sections",
+      name: "sections",
+      component: Main,
+      meta: {
+        requiresAuth: true
+      },
+      beforeEnter: (to, from, next) => {
+        const isWaterwayAdmin = store.getters["user/isWaterwayAdmin"];
+        if (!isWaterwayAdmin) {
+          next("/login");
+        } else {
+          store.commit("application/searchQuery", "");
+          store.commit("application/showContextBox", true);
+          store.commit("application/contextBoxContent", "sections");
+          store.commit("application/showSearchbar", true);
+          next();
+        }
+      }
+    },
+    {
+      path: "/fairwaydepth",
+      name: "fairwaydepth",
+      component: () => import("./components/fairway/AvailableFairwayDepth"),
+      meta: {
+        requiresAuth: true
+      }
+    },
+    {
       path: "*",
-      component: Login
+      component: () => import("./components/PageNotFound")
     }
   ]
 });
@@ -234,16 +218,12 @@
     store.commit("user/clearAuth");
   }
   const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
-  const loggedIn = store.state.user.isAuthenticated;
-  const authRequired =
-    requiresAuth && !(loggedIn || sessionStillActive(expiresFromPastSession));
-  if (authRequired) {
+  const redirectToLogin = requiresAuth && !store.state.user.isAuthenticated;
+  if (redirectToLogin) {
+    localStorage.setItem("tempRoute", to.path);
     next("/login");
-  } else if (!authRequired) {
-    next();
-  } else {
-    next();
   }
+  next();
 });
 
 export default router;
--- a/client/src/store/application.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/store/application.js	Mon Jun 03 10:19:18 2019 +0200
@@ -14,20 +14,18 @@
  *   Bernhard E. Reiter <bernhard.reiter@intevation.de>
  */
 
-import Vue from "vue";
 import { version } from "../../package.json";
 
 // initial state
 const init = () => {
   return {
+    userManualUrl: process.env.VUE_APP_USER_MANUAL_URL,
     appTitle: process.env.VUE_APP_TITLE,
     secondaryLogo: process.env.VUE_APP_SECONDARY_LOGO_URL,
     logoForPDF: process.env.VUE_APP_LOGO_FOR_PDF_URL,
     popup: null,
-    splitscreens: [],
-    splitscreenLoading: false,
-    activeSplitscreenId: null,
-    showSplitscreen: false,
+    paneSetup: "DEFAULT",
+    paneRotate: 1,
     showSidebar: false,
     showUsermenu: false,
     showSearchbar: false,
@@ -38,11 +36,14 @@
     showContextBox: false,
     showProfiles: false,
     showGauges: false,
+    showFairwayDepth: false,
+    showFairwayDepthLNWL: false,
     contextBoxContent: null, // bottlenecks, imports, staging
     expandToolbar: false,
     countries: ["AT", "SK", "HU", "HR", "RS", "BiH", "BG", "RO", "UA"],
     searchQuery: "",
-    version
+    version,
+    tempRoute: ""
   };
 };
 
@@ -68,36 +69,29 @@
         versionStr += "+" + process.env.VUE_APP_HGREV;
 
       return versionStr;
-    },
-    activeSplitscreen: state => {
-      return state.splitscreens.find(s => s.id === state.activeSplitscreenId);
     }
   },
   mutations: {
+    setTempRoute: (state, tempRoute) => {
+      state.tempRoute = tempRoute;
+    },
     popup: (state, popup) => {
       state.popup = popup;
     },
+    paneSetup: (state, setup) => {
+      state.paneSetup = setup;
+    },
+    paneRotate: (state, rotate) => {
+      if (rotate) {
+        state.paneRotate = rotate;
+      } else {
+        state.paneRotate++;
+        if (state.paneRotate === 5) state.paneRotate = 1;
+      }
+    },
     showSidebar: (state, show) => {
       state.showSidebar = show;
     },
-    showSplitscreen: (state, show) => {
-      state.showSplitscreen = show;
-    },
-    splitscreenLoading: (state, loading) => {
-      state.splitscreenLoading = loading;
-    },
-    activeSplitscreenId: (state, id) => {
-      state.activeSplitscreenId = id;
-    },
-    addSplitscreen: (state, config) => {
-      let index = state.splitscreens.findIndex(s => s.id === config.id);
-      if (index !== -1) Vue.set(state.splitscreens, index, config);
-      else state.splitscreens.push(config);
-    },
-    removeSplitscreen: (state, id) => {
-      let index = state.splitscreens.findIndex(s => s.id === id);
-      if (index !== -1) state.splitscreens.splice(index, 1);
-    },
     showUsermenu: (state, show) => {
       state.showUsermenu = show;
     },
@@ -122,6 +116,12 @@
     showGauges: (state, show) => {
       state.showGauges = show;
     },
+    showFairwayDepth: (state, show) => {
+      state.showFairwayDepth = show;
+    },
+    showFairwayDepthLNWL: (state, show) => {
+      state.showFairwayDepthLNWL = show;
+    },
     contextBoxContent: (state, context) => {
       state.contextBoxContent = context;
       if (context) {
--- a/client/src/store/bottlenecks.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/store/bottlenecks.js	Mon Jun 03 10:19:18 2019 +0200
@@ -13,9 +13,8 @@
  * Thomas Junk <thomas.junk@intevation.de>
  */
 import { HTTP } from "@/lib/http";
-import { WFS } from "ol/format.js";
-import { displayError } from "@/lib/errors.js";
-import { LAYERS } from "@/store/map.js";
+import { WFS } from "ol/format";
+import { displayError } from "@/lib/errors";
 
 // initial state
 const init = () => {
@@ -33,6 +32,36 @@
   init,
   namespaced: true,
   state: init(),
+  getters: {
+    limitingFactorsPerBottleneck: state => {
+      if (state.bottlenecks.length === 0) return {};
+      return state.bottlenecks.reduce((o, n) => {
+        o[n.properties.objnam] = n.properties.limiting;
+        return o;
+      }, {});
+    },
+    orderedBottlenecks: state => {
+      let groupedBottlenecks = {},
+        orderedGroups = {};
+
+      // group bottlenecks by cc
+      state.bottlenecksList.forEach(bn => {
+        let cc = bn.properties.responsible_country;
+        if (groupedBottlenecks.hasOwnProperty(cc)) {
+          groupedBottlenecks[cc].push(bn);
+        } else {
+          groupedBottlenecks[cc] = [bn];
+        }
+      });
+
+      // order groups by cc
+      Object.keys(groupedBottlenecks)
+        .sort()
+        .forEach(cc => (orderedGroups[cc] = groupedBottlenecks[cc]));
+
+      return orderedGroups;
+    }
+  },
   mutations: {
     setBottlenecks: (state, bottlenecks) => {
       state.bottlenecks = bottlenecks;
@@ -61,24 +90,18 @@
     }
   },
   actions: {
-    setSelectedBottleneck({ state, commit, rootState, rootGetters }, name) {
+    setSelectedBottleneck({ state, commit, rootState }, name) {
       return new Promise((resolve, reject) => {
         if (name !== state.selectedBottleneck) {
           commit("selectedSurvey", null);
-          commit("application/splitscreenLoading", true, { root: true });
-          commit("application/showSplitscreen", false, { root: true });
-          commit("application/removeSplitscreen", "fairwayprofile", {
-            root: true
+          commit("fairwayprofile/additionalSurvey", null, { root: true });
+          commit("fairwayprofile/clearCurrentProfile", null, { root: true });
+          commit("map/cutToolEnabled", false, { root: true });
+          rootState.map.openLayersMaps.forEach(m => {
+            m.getLayer("CUTTOOL")
+              .getSource()
+              .clear();
           });
-          setTimeout(() => {
-            commit("fairwayprofile/clearCurrentProfile", null, { root: true });
-            commit("application/splitscreenLoading", false, { root: true });
-          }, 350);
-          rootState.map.cutTool.setActive(false);
-          rootGetters["map/getVSourceByName"](LAYERS.CUTTOOL).clear();
-        }
-        if (name) {
-          commit("application/showProfiles", true, { root: true });
         }
         commit("setSelectedBottleneck", name);
         if (name) {
@@ -94,6 +117,7 @@
                 a.date_info < b.date_info ? 1 : -1
               );
               commit("setSurveys", surveys);
+              commit("setFirstSurveySelected");
               resolve(response);
             })
             .catch(error => {
@@ -150,7 +174,8 @@
           featureNS: "gemma",
           featurePrefix: "gemma",
           featureTypes: ["bottlenecks_geoserver"],
-          outputFormat: "application/json"
+          outputFormat: "application/json",
+          propertyNames: ["objnam", "limiting", "reference_water_levels"]
         });
         HTTP.post(
           "/internal/wfs",
--- a/client/src/store/fairway.js	Wed May 29 10:58:45 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,346 +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):
- * Thomas Junk <thomas.junk@intevation.de>
- * Markus Kottländer <markuks.kottlaender@intevation.de>
- */
-import Vue from "vue";
-import { HTTP } from "../lib/http";
-import { prepareProfile } from "../lib/geo";
-import LineString from "ol/geom/LineString.js";
-import { generateFeatureRequest } from "../lib/geo.js";
-import { getLength } from "ol/sphere.js";
-import { displayError } from "../lib/errors.js";
-import { featureToFairwayCoordinates } from "../lib/geo.js";
-import { LAYERS } from "@/store/map.js";
-
-// initial state
-const init = () => {
-  return {
-    additionalSurvey: null,
-    minAlt: 0,
-    maxAlt: 0,
-    currentProfile: {},
-    referenceWaterLevel: null,
-    waterLevels: {},
-    selectedWaterLevel: "",
-    fairwayData: [],
-    startPoint: null,
-    endPoint: null,
-    previousCuts: [],
-    profileLoading: false,
-    selectedCut: null
-  };
-};
-
-export default {
-  init,
-  namespaced: true,
-  state: init(),
-  getters: {
-    totalLength: state => {
-      const keys = Object.keys(state.currentProfile);
-      return keys.length
-        ? Math.max(...keys.map(x => state.currentProfile[x].length))
-        : 0;
-    },
-    additionalSurvey: state => {
-      return state.additionalSurvey;
-    }
-  },
-  mutations: {
-    additionalSurvey: (state, additionalSurvey) => {
-      state.additionalSurvey = additionalSurvey;
-    },
-    setSelectedWaterLevel: (state, level) => {
-      state.selectedWaterLevel = state.waterLevels[level];
-    },
-    profileLoaded: (state, answer) => {
-      const { response, surveyDate } = answer;
-      const { data } = response;
-      const { waterlevel } = response.data.properties;
-      const { value, when } = waterlevel;
-      const coordinates = data.geometry.coordinates;
-      if (!coordinates) return;
-      const startPoint = state.startPoint;
-      const endPoint = state.endPoint;
-      const geoJSON = data;
-      const result = prepareProfile({ geoJSON, startPoint, endPoint });
-      // Use Vue.set() to make new object properties rective
-      // https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
-      const entry = {
-        date: when,
-        value: value
-      };
-      state.waterLevels = { [when]: entry };
-      state.selectedWaterLevel = entry;
-      Vue.set(state.currentProfile, surveyDate, {
-        points: result.points,
-        length: result.lengthPolyLine
-      });
-      if (!state.minAlt || state.minAlt > result.minAlt) {
-        state.minAlt = result.minAlt;
-      }
-      if (!state.maxAlt || state.maxAlt < result.maxAlt) {
-        state.maxAlt = result.maxAlt;
-      }
-    },
-    setStartPoint: (state, start) => {
-      state.startPoint = start;
-    },
-    setEndPoint: (state, end) => {
-      state.endPoint = end;
-    },
-    addFairwayData: (state, coordinates) => {
-      state.fairwayData.push(coordinates);
-    },
-    clearFairwayData: state => {
-      state.fairwayData = [];
-    },
-    clearCurrentProfile: state => {
-      state.additionalSurvey = null;
-      state.currentProfile = {};
-      state.minAlt = null;
-      state.maxAlt = null;
-      state.totalLength = null;
-      state.fairwayData = [];
-      state.startPoint = null;
-      state.endPoint = null;
-      state.referenceWaterLevel = null;
-      state.waterLevels = {};
-      state.selectedWaterLevel = "";
-    },
-    previousCuts: (state, previousCuts) => {
-      state.previousCuts = previousCuts;
-    },
-    profileLoading: (state, loading) => {
-      state.profileLoading = loading;
-    },
-    selectedCut: (state, cut) => {
-      state.selectedCut = cut;
-    }
-  },
-  actions: {
-    clearSelection({ commit, dispatch, rootGetters, rootState }) {
-      dispatch("bottlenecks/setSelectedBottleneck", null, { root: true });
-      dispatch("map/enableIdentifyTool", null, { root: true });
-      commit("clearCurrentProfile");
-      rootState.map.cutTool.setActive(false);
-      rootGetters["map/getVSourceByName"](LAYERS.CUTTOOL).clear();
-    },
-    loadProfile({ commit, state }, survey) {
-      if (state.startPoint && state.endPoint) {
-        return new Promise((resolve, reject) => {
-          const profileLine = new LineString([
-            state.startPoint,
-            state.endPoint
-          ]);
-          const geoJSON = generateFeatureRequest(
-            profileLine,
-            survey.bottleneck_id,
-            survey.date_info
-          );
-          HTTP.post("/cross", geoJSON, {
-            headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-          })
-            .then(response => {
-              if (response.data.geometry.coordinates.length) {
-                commit("profileLoaded", {
-                  response: response,
-                  surveyDate: survey.date_info
-                });
-                resolve(response);
-              } else {
-                commit("clearCurrentProfile");
-                reject({
-                  response: {
-                    status: null,
-                    data: "No intersection with sounding data."
-                  }
-                });
-              }
-            })
-            .catch(error => reject(error));
-        });
-      }
-    },
-    cut({ commit, dispatch, rootState, rootGetters }, cut) {
-      return new Promise(resolve => {
-        const length = getLength(cut.getGeometry());
-        commit(
-          "map/setCurrentMeasurement",
-          {
-            quantity: "Length",
-            unitSymbol: "m",
-            value: Math.round(length * 10) / 10
-          },
-          { root: true }
-        );
-        commit("clearFairwayData");
-        // if a survey has been selected, request a profile
-        // TODO an improvement could be to check if the line intersects
-        // with the bottleneck area's polygon before trying the server request
-        if (rootState.bottlenecks.selectedSurvey) {
-          const inputLineString = cut.getGeometry().clone();
-          inputLineString.transform("EPSG:3857", "EPSG:4326");
-          const [start, end] = inputLineString
-            .getCoordinates()
-            .map(coords => coords.map(coord => parseFloat(coord.toFixed(8))));
-          commit("setStartPoint", start);
-          commit("setEndPoint", end);
-          const profileLine = new LineString([start, end]);
-
-          const profileLoaders = [
-            dispatch("loadProfile", rootState.bottlenecks.selectedSurvey)
-          ];
-          if (rootState.fairwayprofile.additionalSurvey) {
-            profileLoaders.push(
-              dispatch("loadProfile", rootState.fairwayprofile.additionalSurvey)
-            );
-          }
-
-          commit("application/showSplitscreen", true, { root: true });
-          commit("application/splitscreenLoading", true, { root: true });
-          commit("profileLoading", true);
-          Promise.all(profileLoaders)
-            .then(() => {
-              rootState.map.cutTool.setActive(false);
-              const los3 = rootGetters["map/getLayerByName"](
-                LAYERS.FAIRWAYDIMENSIONSLOS3
-              );
-              los3.data.getSource().forEachFeatureIntersectingExtent(
-                profileLine
-                  .clone()
-                  .transform("EPSG:4326", "EPSG:3857")
-                  .getExtent(),
-                feature => {
-                  const fairwayCoordinates = featureToFairwayCoordinates(
-                    feature,
-                    profileLine
-                  );
-                  let fairwayData = {
-                    coordinates: fairwayCoordinates,
-                    style: los3.data.getStyle()
-                  };
-                  if (fairwayCoordinates.length > 0) {
-                    commit("addFairwayData", fairwayData);
-                  }
-                }
-              );
-              const los2 = rootGetters["map/getLayerByName"](
-                LAYERS.FAIRWAYDIMENSIONSLOS2
-              );
-              los2.data.getSource().forEachFeatureIntersectingExtent(
-                profileLine
-                  .clone()
-                  .transform("EPSG:4326", "EPSG:3857")
-                  .getExtent(),
-                feature => {
-                  let fairwayCoordinates = featureToFairwayCoordinates(
-                    feature,
-                    profileLine
-                  );
-                  let fairwayData = {
-                    coordinates: fairwayCoordinates,
-                    style: los2.data.getStyle()
-                  };
-                  if (fairwayCoordinates.length > 0) {
-                    commit("addFairwayData", fairwayData);
-                  }
-                }
-              );
-              const los1 = rootGetters["map/getLayerByName"](
-                LAYERS.FAIRWAYDIMENSIONSLOS1
-              );
-              los1.data.getSource().forEachFeatureIntersectingExtent(
-                profileLine
-                  .clone()
-                  .transform("EPSG:4326", "EPSG:3857")
-                  .getExtent(),
-                feature => {
-                  const fairwayCoordinates = featureToFairwayCoordinates(
-                    feature,
-                    profileLine
-                  );
-                  let fairwayData = {
-                    coordinates: fairwayCoordinates,
-                    style: los1.data.getStyle()
-                  };
-                  if (fairwayCoordinates.length > 0) {
-                    commit("addFairwayData", fairwayData);
-                  }
-                }
-              );
-              resolve();
-            })
-            .catch(error => {
-              const { status, data } = error.response;
-              displayError({
-                title: "Backend Error",
-                message: `${status ? status + ":" : ""} ${data.message || data}`
-              });
-            })
-            .finally(() => {
-              let splitscreenConf = {
-                id: "fairwayprofile",
-                component: "fairwayprofile",
-                title: `${rootState.bottlenecks.selectedBottleneck} (${
-                  rootState.bottlenecks.selectedSurvey.date_info
-                })`,
-                icon: "chart-area",
-                closeCallback: () => {
-                  dispatch("clearSelection");
-                },
-                expandCallback: () => {
-                  let bottleneck = rootState.bottlenecks.bottlenecksList.find(
-                    bn =>
-                      bn.properties.name ===
-                      rootState.bottlenecks.selectedBottleneck
-                  );
-                  commit(
-                    "map/moveToExtent",
-                    {
-                      feature: bottleneck,
-                      zoom: 17,
-                      preventZoomOut: true
-                    },
-                    { root: true }
-                  );
-                }
-              };
-              commit("application/addSplitscreen", splitscreenConf, {
-                root: true
-              });
-              commit("application/activeSplitscreenId", "fairwayprofile", {
-                root: true
-              });
-              commit("application/splitscreenLoading", false, { root: true });
-              commit("profileLoading", false);
-            });
-        }
-      });
-    },
-    previousCuts({ commit, rootState }) {
-      const previousCuts =
-        JSON.parse(localStorage.getItem("previousCuts")) || [];
-      commit(
-        "previousCuts",
-        previousCuts
-          .filter(cut => {
-            return (
-              cut.bottleneckName === rootState.bottlenecks.selectedBottleneck
-            );
-          })
-          .sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1))
-      );
-    }
-  }
-};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/store/fairwayavailability.js	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,334 @@
+/* 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>
+ */
+
+/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "_" }]*/
+
+import { HTTP } from "@/lib/http";
+import {
+  format,
+  subYears,
+  startOfMonth,
+  endOfMonth,
+  startOfYear,
+  endOfYear,
+  startOfQuarter,
+  endOfQuarter
+} from "date-fns";
+
+const LIMITINGFACTORS = {
+  WIDTH: "width",
+  DEPTH: "depth"
+};
+
+const TYPES = {
+  BOTTLENECK: "bottleneck",
+  SECTION: "section",
+  STRETCH: "stretch"
+};
+
+const FREQUENCIES = {
+  MONTHLY: "monthly",
+  QUARTERLY: "quarterly",
+  YEARLY: "yearly"
+};
+
+const isoFormat = date => {
+  return format(date, "YYYY-MM-DD");
+};
+
+const getIntervallBorders = (start, end, frequency) => {
+  switch (frequency) {
+    case FREQUENCIES.MONTHLY:
+      return [isoFormat(startOfMonth(start)), isoFormat(endOfMonth(end))];
+    case FREQUENCIES.YEARLY:
+      return [isoFormat(startOfYear(start)), isoFormat(endOfYear(end))];
+    case FREQUENCIES.QUARTERLY:
+      return [isoFormat(startOfQuarter(start)), isoFormat(endOfQuarter(end))];
+    default:
+      throw new Error("Boom!");
+  }
+};
+
+const init = () => {
+  return {
+    type: TYPES.BOTTLENECK,
+    selectedFairwayAvailabilityFeature: null,
+    to: isoFormat(new Date()),
+    from: isoFormat(subYears(new Date(), 1)),
+    frequency: FREQUENCIES.MONTHLY,
+    limitingFactor: null,
+    depthlimit1: 230,
+    depthlimit2: 250,
+    widthlimit1: null,
+    widthlimit2: null,
+    csv: null,
+    fwData: null,
+    fwLNWLData: null,
+    fwLNWLOverviewData: [],
+    legendLNWL: null,
+    legend: null,
+    LOS: 3
+  };
+};
+
+/**
+ * transformAFD
+ * @param {string} csv
+ *
+ * takes the afd csv and transforms it to an intermediary format
+ * for display of diagrams
+ *
+ * Incoming csv Format
+ * #label,# <LDC ,# >= LDC [h],# < 230.00 [h],# >= 230.00 [h],# >= 250.00 [h]
+ * 05-2019,215.500,0.000,0.000,215.500
+ *
+ * Format:
+ * $LABEL, $LDC, $BELOWLIMIT1, $BETWEENLIMIT12, $ABOVELIMIT2^
+ *
+ * This format is assumed to be fix
+ *
+ */
+const transformAFD = csv => {
+  return csv.map(e => {
+    const result = e.split(",");
+    let [label, _, ldc, lower, middle, highestLevel] = result;
+    let levelsWithSum = [
+      {
+        height: Number(lower),
+        translateY: Number(middle)
+      },
+      {
+        height: Number(middle),
+        translateY: 0
+      }
+    ];
+    return {
+      label: label,
+      ldc: ldc,
+      highestLevel: highestLevel,
+      lowerLevels: levelsWithSum
+    };
+  });
+};
+
+const fairwayavailability = {
+  init,
+  namespaced: true,
+  state: init(),
+  getters: {
+    fwLNWLOverviewData: state => feature => {
+      return state.fwLNWLOverviewData.find(
+        d => d.feature.get("id") === feature.get("id")
+      );
+    }
+  },
+  mutations: {
+    type: (state, type) => {
+      state.type = type;
+    },
+    setLOS: (state, LOS) => {
+      state.LOS = LOS;
+    },
+    setFrequency: (state, frequency) => {
+      state.frequency = frequency;
+    },
+    setFrom: (state, from) => {
+      state.from = from;
+    },
+    setTo: (state, to) => {
+      state.to = to;
+    },
+    setDepthlimit1: (state, depthlimit1) => {
+      state.depthlimit1 = depthlimit1;
+    },
+    setDepthlimit2: (state, depthlimit2) => {
+      state.depthlimit2 = depthlimit2;
+    },
+    setWidthlimit1: (state, widthlimit1) => {
+      state.widthlimit1 = widthlimit1;
+    },
+    setWidthlimit2: (state, widthlimit2) => {
+      state.widthlimit2 = widthlimit2;
+    },
+    setSelectedFairwayAvailability: (state, feature) => {
+      state.selectedFairwayAvailabilityFeature = feature;
+    },
+    setFwData: (state, fwData) => {
+      state.fwData = fwData;
+    },
+    setFwLNWLData: (state, fwLNWLData) => {
+      state.fwLNWLData = fwLNWLData;
+    },
+    setCSV: (state, csv) => {
+      state.csv = csv;
+    },
+    addFwLNWLOverviewData: (state, data) => {
+      let existingIndex = state.fwLNWLOverviewData.findIndex(
+        d => d.feature.get("id") === data.feature.get("id")
+      );
+      if (existingIndex !== -1)
+        state.fwLNWLOverviewData.splice(existingIndex, 1);
+      state.fwLNWLOverviewData.push(data);
+    },
+    setLegend: (state, header) => {
+      const headerEntries = header.split(",");
+      headerEntries.shift();
+      headerEntries.shift();
+      state.legend = headerEntries.map(x => {
+        let entry = x.split("#")[1]; // split leading #
+        entry = entry.replace("[h]", "").trim(); // omit unit
+        return entry;
+      });
+    },
+    setLegendLNWL: (state, headerLNWL) => {
+      this.headerLNWL = headerLNWL;
+    }
+  },
+  actions: {
+    loadAvailableFairwayDepth: ({ commit }, options) => {
+      return new Promise((resolve, reject) => {
+        const {
+          feature,
+          frequency,
+          LOS,
+          depthLimit1,
+          depthLimit2,
+          widthLimit1,
+          widthLimit2,
+          limitingFactor,
+          type
+        } = options;
+        let { from, to } = options;
+        let name = feature.hasOwnProperty("properties")
+          ? feature.properties.name
+          : feature.get("objnam");
+        [from, to] = getIntervallBorders(from, to, frequency);
+        let additionalParams = "";
+        let endpoint = type;
+        if (type === TYPES.BOTTLENECK) {
+          if (limitingFactor === LIMITINGFACTORS.DEPTH)
+            additionalParams = `&breaks=${depthLimit1},${depthLimit2}`;
+          if (limitingFactor === LIMITINGFACTORS.WIDTH)
+            additionalParams = `&breaks=${widthLimit1},${widthLimit2}`;
+        } else if (type == TYPES.SECTION || type == TYPES.STRETCH) {
+          additionalParams = `&depthbreaks=${depthLimit1},${depthLimit2}&widthbreaks=${widthLimit1},${widthLimit2}`;
+        }
+        const start = encodeURIComponent("00:00:00+00:00");
+        const end = encodeURIComponent("23:59:59+00:00");
+        const URL = `data/${endpoint}/fairway-depth/${encodeURIComponent(
+          name
+        )}?from=${from}T${start}&to=${to}T${end}&mode=${frequency}&los=${LOS}${additionalParams}`;
+        HTTP.get(URL, {
+          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+        })
+          .then(response => {
+            const { data } = response;
+            commit("setCSV", data);
+            const csv = data.split("\n").filter(x => x !== ""); //omit empty lines
+            commit("setLegend", csv.shift());
+            let transformed = transformAFD(csv);
+            commit("setFwData", transformed);
+            resolve(response);
+          })
+          .catch(error => {
+            reject(error);
+          });
+      });
+    },
+    loadAvailableFairwayDepthLNWL: (context, options) => {
+      return new Promise((resolve, reject) => {
+        const {
+          feature,
+          frequency,
+          LOS,
+          depthLimit1,
+          depthLimit2,
+          widthLimit1,
+          widthLimit2,
+          limitingFactor,
+          type
+        } = options;
+        let { from, to } = options;
+        let name = feature.hasOwnProperty("properties")
+          ? feature.properties.name
+          : feature.get("objnam");
+        [from, to] = getIntervallBorders(from, to, frequency);
+        const start = encodeURIComponent("00:00:00+00:00");
+        const end = encodeURIComponent("23:59:59+00:00");
+        let additionalParams = "";
+        let endpoint = type || TYPES.BOTTLENECK;
+        if (type === TYPES.BOTTLENECK) {
+          if (limitingFactor === LIMITINGFACTORS.DEPTH)
+            additionalParams = `&breaks=${depthLimit1},${depthLimit2}`;
+          if (limitingFactor === LIMITINGFACTORS.WIDTH)
+            additionalParams = `&breaks=${widthLimit1},${widthLimit2}`;
+        } else if (type == TYPES.SECTION || type == TYPES.STRETCH) {
+          additionalParams = `&depthbreaks=${depthLimit1},${depthLimit2}&widthbreaks=${widthLimit1},${widthLimit2}`;
+        }
+        const URL = `data/${endpoint}/availability/${encodeURIComponent(
+          name
+        )}?from=${from}T${start}&to=${to}T${end}&mode=${frequency}&los=${LOS}${additionalParams}`;
+        HTTP.get(URL, {
+          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+        })
+          .then(response => {
+            const { data } = response;
+            resolve(data);
+          })
+          .catch(error => {
+            reject(error);
+          });
+      });
+    },
+    loadAvailableFairwayDepthLNWLDiagram: ({ commit, dispatch }, options) => {
+      dispatch("loadAvailableFairwayDepthLNWL", options).then(response => {
+        commit("setCSV", response);
+        let data = response.split("\n").filter(d => d);
+        data.shift(); // remove header line
+        data = data.map(d => {
+          let columns = d.split(",");
+          return {
+            date: columns[0],
+            ldc: Number(columns[2]),
+            below: Number(columns[3]),
+            between: Number(columns[4]),
+            above: Number(columns[5])
+          };
+        });
+        commit("setFwLNWLData", data);
+        return data;
+      });
+    },
+    loadAvailableFairwayDepthLNWLForMap: ({ dispatch }, options) => {
+      return dispatch("loadAvailableFairwayDepthLNWL", options).then(
+        response => {
+          let data = response.split("\n").filter(d => d);
+          data.shift(); // remove header line
+          data = data.map(d => {
+            let columns = d.split(",");
+            return {
+              ldc: Number(columns[2]),
+              below: Number(columns[3]),
+              between: Number(columns[4]),
+              above: Number(columns[5])
+            };
+          });
+          return data[0];
+        }
+      );
+    }
+  }
+};
+
+export { LIMITINGFACTORS, FREQUENCIES, fairwayavailability };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/store/fairwayprofile.js	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,314 @@
+/* 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 <markuks.kottlaender@intevation.de>
+ */
+import Vue from "vue";
+import { HTTP } from "@/lib/http";
+import { prepareProfile } from "@/lib/geo";
+import LineString from "ol/geom/LineString";
+import { generateFeatureRequest } from "@/lib/geo";
+import { getLength } from "ol/sphere";
+import { displayError } from "@/lib/errors";
+import { featureToFairwayCoordinates } from "@/lib/geo";
+
+// initial state
+const init = () => {
+  return {
+    additionalSurvey: null,
+    minAlt: 0,
+    maxAlt: 0,
+    currentProfile: {},
+    selectedWaterLevel: "ref",
+    fairwayData: [],
+    startPoint: null,
+    endPoint: null,
+    previousCuts: [],
+    profileLoading: false,
+    selectedCut: null,
+    differencesLoading: false
+  };
+};
+
+export default {
+  init,
+  namespaced: true,
+  state: init(),
+  getters: {
+    totalLength: state => {
+      const keys = Object.keys(state.currentProfile);
+      return keys.length
+        ? Math.max(...keys.map(x => state.currentProfile[x].length))
+        : 0;
+    },
+    additionalSurvey: state => {
+      return state.additionalSurvey;
+    }
+  },
+  mutations: {
+    additionalSurvey: (state, additionalSurvey) => {
+      state.additionalSurvey = additionalSurvey;
+    },
+    setSelectedWaterLevel: (state, level) => {
+      state.selectedWaterLevel = level;
+    },
+    setDifferencesLoading: (state, value) => {
+      state.differencesLoading = value;
+    },
+    profileLoaded: (state, answer) => {
+      const { response, surveyDate } = answer;
+      const { data } = response;
+      const coordinates = data.geometry.coordinates;
+      if (!coordinates) return;
+      const startPoint = state.startPoint;
+      const endPoint = state.endPoint;
+      const geoJSON = data;
+      const result = prepareProfile({ geoJSON, startPoint, endPoint });
+      // Use Vue.set() to make new object properties rective
+      // https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
+      Vue.set(state.currentProfile, surveyDate, {
+        points: result.points,
+        length: result.lengthPolyLine
+      });
+      if (!state.minAlt || state.minAlt > result.minAlt) {
+        state.minAlt = result.minAlt;
+      }
+      if (!state.maxAlt || state.maxAlt < result.maxAlt) {
+        state.maxAlt = result.maxAlt;
+      }
+    },
+    setStartPoint: (state, start) => {
+      state.startPoint = start;
+    },
+    setEndPoint: (state, end) => {
+      state.endPoint = end;
+    },
+    addFairwayData: (state, coordinates) => {
+      state.fairwayData.push(coordinates);
+    },
+    clearFairwayData: state => {
+      state.fairwayData = [];
+    },
+    clearCurrentProfile: state => {
+      state.currentProfile = {};
+      state.minAlt = null;
+      state.maxAlt = null;
+      state.totalLength = null;
+      state.fairwayData = [];
+      state.startPoint = null;
+      state.endPoint = null;
+      state.selectedWaterLevel = "ref";
+    },
+    previousCuts: (state, previousCuts) => {
+      state.previousCuts = previousCuts;
+    },
+    profileLoading: (state, loading) => {
+      state.profileLoading = loading;
+    },
+    selectedCut: (state, cut) => {
+      state.selectedCut = cut;
+    }
+  },
+  actions: {
+    clearCurrentProfile({ commit, rootState }) {
+      commit("clearCurrentProfile");
+      commit("map/cutToolEnabled", false, { root: true });
+      rootState.map.openLayersMaps.forEach(m => {
+        m.getLayer("CUTTOOL")
+          .getSource()
+          .clear();
+      });
+    },
+    loadProfile({ commit, state }, survey) {
+      if (state.startPoint && state.endPoint) {
+        return new Promise((resolve, reject) => {
+          const profileLine = new LineString([
+            state.startPoint,
+            state.endPoint
+          ]);
+          const geoJSON = generateFeatureRequest(
+            profileLine,
+            survey.bottleneck_id,
+            survey.date_info
+          );
+          HTTP.post("/cross", geoJSON, {
+            headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+          })
+            .then(response => {
+              if (response.data.geometry.coordinates.length) {
+                commit("profileLoaded", {
+                  response: response,
+                  surveyDate: survey.date_info
+                });
+                resolve(response);
+              } else {
+                commit("clearCurrentProfile");
+                reject({
+                  response: {
+                    status: null,
+                    data: "No intersection with sounding data."
+                  }
+                });
+              }
+            })
+            .catch(error => reject(error));
+        });
+      }
+    },
+    cut({ commit, dispatch, state, rootState, rootGetters }, cut) {
+      return new Promise(resolve => {
+        const length = getLength(cut.getGeometry());
+        commit(
+          "map/setCurrentMeasurement",
+          {
+            quantity: "Length",
+            unitSymbol: "m",
+            value: Math.round(length * 10) / 10
+          },
+          { root: true }
+        );
+        commit("clearFairwayData");
+        // if a survey has been selected, request a profile
+        // TODO an improvement could be to check if the line intersects
+        // with the bottleneck area's polygon before trying the server request
+        if (rootState.bottlenecks.selectedSurvey) {
+          const inputLineString = cut.getGeometry().clone();
+          inputLineString.transform("EPSG:3857", "EPSG:4326");
+          const [start, end] = inputLineString
+            .getCoordinates()
+            .map(coords => coords.map(coord => parseFloat(coord.toFixed(8))));
+          commit("setStartPoint", start);
+          commit("setEndPoint", end);
+          const profileLine = new LineString([start, end]);
+
+          const profileLoaders = [
+            dispatch("loadProfile", rootState.bottlenecks.selectedSurvey)
+          ];
+          if (state.additionalSurvey) {
+            profileLoaders.push(
+              dispatch("loadProfile", state.additionalSurvey)
+            );
+          }
+
+          commit("profileLoading", true);
+          Promise.all(profileLoaders)
+            .then(() => {
+              commit("map/cutToolEnabled", false, { root: true });
+              const los3 = rootGetters["map/openLayersMap"]().getLayer(
+                "FAIRWAYDIMENSIONSLOS3"
+              );
+              los3.getSource().forEachFeatureIntersectingExtent(
+                profileLine
+                  .clone()
+                  .transform("EPSG:4326", "EPSG:3857")
+                  .getExtent(),
+                feature => {
+                  const fairwayCoordinates = featureToFairwayCoordinates(
+                    feature,
+                    profileLine
+                  );
+                  let fairwayData = {
+                    coordinates: fairwayCoordinates,
+                    style: los3.getStyle()
+                  };
+                  if (fairwayCoordinates.length > 0) {
+                    commit("addFairwayData", fairwayData);
+                  }
+                }
+              );
+              const los2 = rootGetters["map/openLayersMap"]().getLayer(
+                "FAIRWAYDIMENSIONSLOS2"
+              );
+              los2.getSource().forEachFeatureIntersectingExtent(
+                profileLine
+                  .clone()
+                  .transform("EPSG:4326", "EPSG:3857")
+                  .getExtent(),
+                feature => {
+                  let fairwayCoordinates = featureToFairwayCoordinates(
+                    feature,
+                    profileLine
+                  );
+                  let fairwayData = {
+                    coordinates: fairwayCoordinates,
+                    style: los2.getStyle()
+                  };
+                  if (fairwayCoordinates.length > 0) {
+                    commit("addFairwayData", fairwayData);
+                  }
+                }
+              );
+              const los1 = rootGetters["map/openLayersMap"]().getLayer(
+                "FAIRWAYDIMENSIONSLOS1"
+              );
+              los1.getSource().forEachFeatureIntersectingExtent(
+                profileLine
+                  .clone()
+                  .transform("EPSG:4326", "EPSG:3857")
+                  .getExtent(),
+                feature => {
+                  const fairwayCoordinates = featureToFairwayCoordinates(
+                    feature,
+                    profileLine
+                  );
+                  let fairwayData = {
+                    coordinates: fairwayCoordinates,
+                    style: los1.getStyle()
+                  };
+                  if (fairwayCoordinates.length > 0) {
+                    commit("addFairwayData", fairwayData);
+                  }
+                }
+              );
+              resolve();
+            })
+            .catch(error => {
+              const { status, data } = error.response;
+              displayError({
+                title: "Backend Error",
+                message: `${status ? status + ":" : ""} ${data.message || data}`
+              });
+            })
+            .finally(() => {
+              commit("application/paneRotate", 1, { root: true });
+              if (state.additionalSurvey) {
+                commit(
+                  "application/paneSetup",
+                  "COMPARESURVEYS_FAIRWAYPROFILE",
+                  { root: true }
+                );
+              } else {
+                commit("application/paneSetup", "FAIRWAYPROFILE", {
+                  root: true
+                });
+              }
+              commit("profileLoading", false);
+            });
+        }
+      });
+    },
+    previousCuts({ commit, rootState }) {
+      const previousCuts =
+        JSON.parse(localStorage.getItem("previousCuts")) || [];
+      commit(
+        "previousCuts",
+        previousCuts
+          .filter(cut => {
+            return (
+              cut.bottleneckName === rootState.bottlenecks.selectedBottleneck
+            );
+          })
+          .sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1))
+      );
+    }
+  }
+};
--- a/client/src/store/gauges.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/store/gauges.js	Mon Jun 03 10:19:18 2019 +0200
@@ -12,7 +12,7 @@
  * Markus Kottländer <markus@intevation.de>
  */
 import { HTTP } from "@/lib/http";
-import { WFS } from "ol/format.js";
+import { WFS } from "ol/format";
 import { isPast } from "date-fns";
 
 let dateFrom = new Date();
@@ -25,10 +25,15 @@
     gauges: [],
     selectedGaugeISRS: null,
     waterlevels: [],
-    meanWaterlevels: [],
+    waterlevelsCSV: "",
+    longtermWaterlevels: [],
+    longtermInterval: [],
+    yearWaterlevels: [],
     nashSutcliffe: null,
+    nashSutcliffeOverview: [],
     dateFrom: dateFrom,
-    dateTo: dateTo
+    dateTo: dateTo,
+    yearCompare: new Date().getFullYear()
   };
 };
 
@@ -41,6 +46,11 @@
       return state.gauges.find(
         g => g.properties.isrs_code === state.selectedGaugeISRS
       );
+    },
+    nashSutcliffeOverview: state => feature => {
+      return state.nashSutcliffeOverview.find(
+        d => d.feature.get("id") === feature.get("id")
+      );
     }
   },
   mutations: {
@@ -53,23 +63,49 @@
     waterlevels: (state, data) => {
       state.waterlevels = data;
     },
-    meanWaterlevels: (state, data) => {
-      state.meanWaterlevels = data;
+    waterlevelsCSV: (state, data) => {
+      state.waterlevelsCSV = data;
+    },
+    longtermWaterlevels: (state, data) => {
+      state.longtermWaterlevels = data;
+    },
+    longtermInterval: (state, interval) => {
+      state.longtermInterval = interval;
+    },
+    yearWaterlevels: (state, data) => {
+      state.yearWaterlevels = data;
     },
     nashSutcliffe: (state, data) => {
       state.nashSutcliffe = data;
     },
+    addNashSutcliffeOverviewEntry: (state, data) => {
+      let existingIndex = state.nashSutcliffeOverview.findIndex(
+        d => d.feature.get("id") === data.feature.get("id")
+      );
+      if (existingIndex !== -1)
+        state.nashSutcliffeOverview.splice(existingIndex, 1);
+      state.nashSutcliffeOverview.push(data);
+    },
     dateFrom: (state, dateFrom) => {
       state.dateFrom = dateFrom;
     },
     dateTo: (state, dateTo) => {
       state.dateTo = dateTo;
+    },
+    yearCompare: (state, year) => {
+      state.yearCompare = year;
     }
   },
   actions: {
-    selectedGaugeISRS: ({ commit }, isrs) => {
-      commit("selectedGaugeISRS", isrs);
-      commit("application/showGauges", true, { root: true });
+    selectedGaugeISRS: ({ commit, dispatch, state }, isrs) => {
+      if (state.selectedGaugeISRS !== isrs) {
+        commit("selectedGaugeISRS", isrs);
+        commit("application/showGauges", true, { root: true });
+        dispatch("loadWaterlevels");
+        dispatch("loadLongtermWaterlevels");
+        dispatch("loadYearWaterlevels");
+        dispatch("loadNashSutcliffe");
+      }
     },
     loadGauges: ({ commit }) => {
       return new Promise((resolve, reject) => {
@@ -115,15 +151,19 @@
           }
         )
           .then(response => {
+            commit("waterlevelsCSV", response.data);
             let data = response.data
               .split("\n")
-              .filter(wl => wl)
+              // remove empty rows and rows starting with #
+              .filter(wl => wl && wl[0] !== "#")
               .map(wl => {
                 wl = wl.split(",");
                 return {
                   date: new Date(wl[0]),
                   waterlevel: Number(wl[1]),
-                  predicted: wl[2] === "f" ? false : true
+                  min: Number(wl[2]),
+                  max: Number(wl[3]),
+                  predicted: wl[4] === "f" ? false : true
                 };
               })
               .filter(wl => !(wl.predicted && isPast(wl.date)));
@@ -137,26 +177,91 @@
           });
       });
     },
-    loadMeanWaterlevels({ /*state,*/ commit }) {
-      return new Promise(resolve => {
-        setTimeout(() => {
-          commit("meanWaterlevels", [1]);
-          resolve();
-        }, 2000);
+    loadLongtermWaterlevels({ state, commit }) {
+      return new Promise((resolve, reject) => {
+        HTTP.get(`/data/longterm-waterlevels/${state.selectedGaugeISRS}`, {
+          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+        })
+          .then(response => {
+            const now = new Date();
+            let data = response.data.split("\n");
+            // get result interval from first line
+            let interval = data[0]
+              .split(",")[0]
+              .split(" ")[1]
+              .split("-")
+              .map(y => Number(y));
+            if (interval[0] === interval[1]) interval = [interval[0]];
+            commit("longtermInterval", interval);
+            data = data
+              // remove empty rows and rows starting with #
+              .filter(wl => wl && wl[0] !== "#")
+              .map(wl => {
+                wl = wl.split(",");
+                let dayAndMonth = wl[0].split("-").map(n => Number(n));
+                let date = new Date(
+                  now.getFullYear(),
+                  dayAndMonth[1] - 1,
+                  dayAndMonth[0]
+                );
+                return {
+                  date: date,
+                  min: Number(wl[1]),
+                  max: Number(wl[2]),
+                  mean: Number(wl[3]),
+                  median: Number(wl[4]),
+                  q25: Number(wl[5]),
+                  q75: Number(wl[6])
+                };
+              });
+            data = data.sort((a, b) => a.date - b.date);
+            commit("longtermWaterlevels", data);
+            resolve(data);
+          })
+          .catch(error => {
+            commit("longtermWaterlevels", []);
+            reject(error);
+          });
       });
-      // return new Promise((resolve, reject) => {
-      //   HTTP.get(`/data/mean-waterlevels/${state.selectedGaugeISRS}`, {
-      //     headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-      //   })
-      //     .then(response => {
-      //       commit("meanWaterlevels", response.data);
-      //       resolve(response.data);
-      //     })
-      //     .catch(error => {
-      //       commit("meanWaterlevels", []);
-      //       reject(error);
-      //     })
-      // });
+    },
+    loadYearWaterlevels({ state, commit }) {
+      return new Promise((resolve, reject) => {
+        HTTP.get(
+          `/data/year-waterlevels/${state.selectedGaugeISRS}/${
+            state.yearCompare
+          }`,
+          {
+            headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+          }
+        )
+          .then(response => {
+            const now = new Date();
+            let data = response.data
+              .split("\n")
+              // remove empty rows and rows starting with #
+              .filter(wl => wl && wl[0] !== "#")
+              .map(wl => {
+                wl = wl.split(",");
+                let dayAndMonth = wl[0].split("-").map(n => Number(n));
+                let date = new Date(
+                  now.getFullYear(),
+                  dayAndMonth[1] - 1,
+                  dayAndMonth[0]
+                );
+                return {
+                  date: date,
+                  mean: Number(wl[1])
+                };
+              });
+            data = data.sort((a, b) => a.date - b.date);
+            commit("yearWaterlevels", data);
+            resolve(data);
+          })
+          .catch(error => {
+            commit("yearWaterlevels", []);
+            reject(error);
+          });
+      });
     },
     loadNashSutcliffe({ state, commit }) {
       return new Promise((resolve, reject) => {
@@ -172,6 +277,19 @@
             reject(error);
           });
       });
+    },
+    loadNashSutcliffeForOverview(context, isrsCode) {
+      return new Promise((resolve, reject) => {
+        HTTP.get(`/data/nash-sutcliffe/${isrsCode}`, {
+          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+        })
+          .then(response => {
+            resolve(response.data);
+          })
+          .catch(error => {
+            reject(error);
+          });
+      });
     }
   }
 };
--- a/client/src/store/imports.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/store/imports.js	Mon Jun 03 10:19:18 2019 +0200
@@ -13,20 +13,16 @@
  */
 
 import { HTTP } from "@/lib/http";
-import { WFS } from "ol/format.js";
-import { equalTo as equalToFilter } from "ol/format/filter.js";
-import { startOfHour } from "date-fns";
+import { WFS } from "ol/format";
+import { equalTo as equalToFilter } from "ol/format/filter";
+import { startOfHour, endOfHour } from "date-fns";
 
-/* eslint-disable no-unused-vars */
-/* eslint-disable no-unreachable */
 const STATES = {
   NEEDSAPPROVAL: "pending",
   APPROVED: "accepted",
   REJECTED: "declined"
 };
 
-const NODETAILS = -1;
-
 // initial state
 const init = () => {
   return {
@@ -36,32 +32,35 @@
     declined: false,
     warning: false,
     stretches: [],
+    selectedStretchId: null,
+    sections: [],
+    selectedSectionId: null,
     imports: [],
     reviewed: [],
-    show: NODETAILS,
-    showAdditional: NODETAILS,
-    showLogs: NODETAILS,
+    show: null,
+    showAdditional: null,
+    showLogs: null,
     details: [],
     startDate: startOfHour(new Date()),
-    endDate: new Date(),
+    endDate: endOfHour(new Date()),
     prev: null,
     next: null
   };
 };
 
-const getStretchFromWFS = filter => {
+const getFromWFS = (type, filter) => {
   return new Promise((resolve, reject) => {
-    var stretchesFeatureCollectionRequest = new WFS().writeGetFeature({
+    var featureCollectionRequest = new WFS().writeGetFeature({
       srsName: "EPSG:4326",
       featureNS: "gemma",
       featurePrefix: "gemma",
-      featureTypes: ["stretches_geoserver"],
+      featureTypes: [type],
       outputFormat: "application/json",
       filter: filter
     });
     HTTP.post(
       "/internal/wfs",
-      new XMLSerializer().serializeToString(stretchesFeatureCollectionRequest),
+      new XMLSerializer().serializeToString(featureCollectionRequest),
       {
         headers: {
           "X-Gemma-Auth": localStorage.getItem("token"),
@@ -132,6 +131,15 @@
     setStretches: (state, stretches) => {
       state.stretches = stretches;
     },
+    selectedStretchId: (state, id) => {
+      state.selectedStretchId = id;
+    },
+    setSections: (state, sections) => {
+      state.sections = sections;
+    },
+    selectedSectionId: (state, id) => {
+      state.selectedSectionId = id;
+    },
     setReviewed: (state, reviewed) => {
       state.reviewed = reviewed;
     },
@@ -158,19 +166,19 @@
       state.show = id;
     },
     hideDetails: state => {
-      state.show = NODETAILS;
+      state.show = null;
     },
     showAdditionalInfoFor: (state, id) => {
       state.showAdditional = id;
     },
     hideAdditionalInfo: state => {
-      state.showAdditional = NODETAILS;
+      state.showAdditional = false;
     },
     showAdditionalLogsFor: (state, id) => {
       state.showLogs = id;
     },
     hideAdditionalLogs: state => {
-      state.showLogs = NODETAILS;
+      state.showLogs = false;
     },
     toggleApprove: (state, change) => {
       const { id, newStatus } = change;
@@ -192,9 +200,9 @@
     }
   },
   actions: {
-    loadStretch({ commit }, name) {
+    loadStretch(context, name) {
       return new Promise((resolve, reject) => {
-        getStretchFromWFS(equalToFilter("name", name))
+        getFromWFS("stretches_geoserver", equalToFilter("name", name))
           .then(response => {
             resolve(response);
           })
@@ -205,7 +213,7 @@
     },
     loadStretches({ commit }) {
       return new Promise((resolve, reject) => {
-        getStretchFromWFS(equalToFilter("staging_done", true))
+        getFromWFS("stretches_geoserver", equalToFilter("staging_done", true))
           .then(response => {
             if (response.data.features) {
               commit("setStretches", response.data.features);
@@ -219,7 +227,7 @@
           });
       });
     },
-    saveStretch({ commit }, stretch) {
+    saveStretch(context, stretch) {
       return new Promise((resolve, reject) => {
         HTTP.post("/imports/st", stretch, {
           headers: { "X-Gemma-Auth": localStorage.getItem("token") }
@@ -232,6 +240,46 @@
           });
       });
     },
+    loadSection(context, name) {
+      return new Promise((resolve, reject) => {
+        getFromWFS("sections_geoserver", equalToFilter("name", name))
+          .then(response => {
+            resolve(response);
+          })
+          .catch(error => {
+            reject(error);
+          });
+      });
+    },
+    loadSections({ commit }) {
+      return new Promise((resolve, reject) => {
+        getFromWFS("sections_geoserver", equalToFilter("staging_done", true))
+          .then(response => {
+            if (response.data.features) {
+              commit("setSections", response.data.features);
+            } else {
+              commit("setSections", []);
+            }
+            resolve(response);
+          })
+          .catch(error => {
+            reject(error);
+          });
+      });
+    },
+    saveSection(context, section) {
+      return new Promise((resolve, reject) => {
+        HTTP.post("/imports/sec", section, {
+          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+        })
+          .then(response => {
+            resolve(response);
+          })
+          .catch(error => {
+            reject(error);
+          });
+      });
+    },
     getImports({ commit }, options) {
       let { filter, from, to, query } = options;
       let queryParams = "";
@@ -271,7 +319,7 @@
           });
       });
     },
-    confirmReview({ state }, reviewResults) {
+    confirmReview(context, reviewResults) {
       return new Promise((resolve, reject) => {
         HTTP.patch("/imports", reviewResults, {
           headers: {
--- a/client/src/store/importschedule.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/store/importschedule.js	Mon Jun 03 10:19:18 2019 +0200
@@ -27,7 +27,10 @@
   FAIRWAYDIMENSION: "fairwaydimension",
   WATERWAYGAUGES: "waterwaygauges",
   DISTANCEMARKSVIRTUAL: "distancemarksvirtual",
-  DISTANCEMARKSASHORE: "distancemarksashore"
+  DISTANCEMARKSASHORE: "distancemarksashore",
+  SOUNDINGRESULTS: "soundingresults",
+  APPROVEDGAUGEMEASUREMENTS: "approvedgaugemeasurements",
+  WATERWAYPROFILES: "waterwayprofiles"
 };
 
 const KINDIMPORTTYPE = {
@@ -59,7 +62,6 @@
     id: null,
     importType: null,
     schedule: null,
-    import_: null,
     importSource: null,
     eMailNotification: false,
     scheduled: false,
@@ -84,16 +86,24 @@
     minWidth: null,
     maxWidth: null,
     depth: null,
-    sourceOrganization: null
+    sourceOrganization: null,
+    trys: null,
+    waitRetry: null
   };
 };
 
+const MODES = {
+  LIST: "list",
+  EDIT: "edit"
+};
+
 // initial state
 const init = () => {
   return {
     schedules: [],
     importScheduleDetailVisible: false,
-    currentSchedule: initializeCurrentSchedule()
+    currentSchedule: initializeCurrentSchedule(),
+    mode: MODES.LIST
   };
 };
 
@@ -102,6 +112,16 @@
   namespaced: true,
   state: init(),
   mutations: {
+    setEditMode: state => {
+      state.mode = MODES.EDIT;
+    },
+    setListMode: state => {
+      state.currentSchedule = initializeCurrentSchedule();
+      state.mode = MODES.LIST;
+    },
+    setImportType: (state, type) => {
+      Vue.set(state.currentSchedule, "importType", type);
+    },
     clearCurrentSchedule: state => {
       state.currentSchedule = initializeCurrentSchedule();
     },
@@ -118,12 +138,13 @@
       const { kind, config, id } = payload;
       const eMailNotification = config["send-email"];
       const { cron, url } = config;
-      Vue.set(state.currentSchedule, "import_", KINDIMPORTTYPE[kind]);
+      Vue.set(state.currentSchedule, "importType", KINDIMPORTTYPE[kind]);
       Vue.set(state.currentSchedule, "id", id);
       if (cron) {
         Vue.set(state.currentSchedule, "scheduled", true);
         Vue.set(state.currentSchedule, "cronString", cron);
-
+        Vue.set(state.currentSchedule, "trys", config["trys"]);
+        Vue.set(state.currentSchedule, "waitRetry", config["wait-retry"]);
         // simple weekly  or monthly?
         if (cron === "0 0 0 * * 0") {
           Vue.set(state.currentSchedule, "simple", "weekly");
@@ -320,5 +341,6 @@
   importschedule,
   initializeCurrentSchedule,
   IMPORTTYPES,
-  IMPORTTYPEKIND
+  IMPORTTYPEKIND,
+  MODES
 };
--- a/client/src/store/index.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/store/index.js	Mon Jun 03 10:19:18 2019 +0200
@@ -19,7 +19,8 @@
 import user from "./user";
 import usermanagement from "./usermanagement";
 import map from "./map";
-import fairwayprofile from "./fairway";
+import { fairwayavailability } from "./fairwayavailability";
+import fairwayprofile from "./fairwayprofile";
 import bottlenecks from "./bottlenecks";
 import { imports } from "./imports";
 import { importschedule } from "./importschedule";
@@ -32,6 +33,7 @@
     reset() {
       this.replaceState({
         application: application.init(),
+        fairwayavailability: fairwayavailability.init(),
         fairwayprofile: fairwayprofile.init(),
         imports: imports.init(),
         importschedule: importschedule.init(),
@@ -45,6 +47,7 @@
   },
   modules: {
     application,
+    fairwayavailability,
     fairwayprofile,
     imports,
     importschedule,
--- a/client/src/store/map.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/store/map.js	Mon Jun 03 10:19:18 2019 +0200
@@ -14,544 +14,41 @@
  * * Thomas Junk <thomas.junk@intevation.de>
  */
 
-//import { HTTP } from "../lib/http";
-
-import TileWMS from "ol/source/TileWMS.js";
-import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js";
-import OSM from "ol/source/OSM";
-import Draw from "ol/interaction/Draw.js";
-import { Icon, Stroke, Style, Fill, Text, Circle } from "ol/style.js";
-import VectorSource from "ol/source/Vector.js";
-import Point from "ol/geom/Point.js";
-import { bbox as bboxStrategy } from "ol/loadingstrategy";
-import { HTTP } from "../lib/http";
+import Draw from "ol/interaction/Draw";
+import { Stroke, Style, Fill, Circle } from "ol/style";
 import { fromLonLat } from "ol/proj";
-import { getLength, getArea } from "ol/sphere.js";
-import { unByKey } from "ol/Observable";
-import { getCenter } from "ol/extent";
-import { transformExtent } from "ol/proj.js";
+import { getLength, getArea } from "ol/sphere";
+import { transformExtent } from "ol/proj";
 import bbox from "@turf/bbox";
-import app from "../main";
-
-const LAYERS = {
-  OPENSTREETMAP: "Open Streetmap",
-  INLANDECDIS: "Inland ECDIS chart Danube",
-  WATERWAYAREA: "Waterway Area",
-  STRETCHES: "Stretches",
-  FAIRWAYDIMENSIONSLOS1: "LOS 1 Fairway Dimensions",
-  FAIRWAYDIMENSIONSLOS2: "LOS 2 Fairway Dimensions",
-  FAIRWAYDIMENSIONSLOS3: "LOS 3 Fairway Dimensions",
-  WATERWAYAXIS: "Waterway Axis",
-  WATERWAYPROFILES: "Waterway Profiles",
-  BOTTLENECKS: "Bottlenecks",
-  BOTTLENECKSTATUS: "Critical Bottlenecks",
-  BOTTLENECKISOLINE: "Bottleneck isolines",
-  DISTANCEMARKS: "Distance marks",
-  DISTANCEMARKSAXIS: "Distance marks, Axis",
-  GAUGES: "Gauges",
-  DRAWTOOL: "Draw Tool",
-  CUTTOOL: "Cut Tool"
-};
-
-const moveMap = ({ view, extent, zoom, preventZoomOut }) => {
-  const currentZoom = view.getZoom();
-  zoom = zoom || currentZoom;
-  view.fit(extent, {
-    maxZoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom,
-    duration: 700
-  });
-};
+import app from "@/main";
+import { HTTP } from "@/lib/http";
+import Feature from "ol/Feature";
+import Point from "ol/geom/Point";
+import { Vector as VectorLayer } from "ol/layer";
+import { toLonLat } from "ol/proj";
 
 // initial state
 const init = () => {
   return {
-    openLayersMap: null,
+    openLayersMaps: [],
+    syncedMaps: [],
+    syncedView: null,
+    mapPopup: null,
+    mapPopupEnabled: true,
     initialLoad: true,
     extent: {
       lat: 6155376,
       lon: 1819178,
       zoom: 11
     },
-    identifyTool: null, // event binding (singleclick, dblclick)
     identifiedFeatures: [], // map features identified by clicking on the map
+    identifiedCoordinates: null,
     currentMeasurement: null, // distance or area from line-/polygon-/cutTool
-    lineTool: null, // open layers interaction object (Draw)
-    polygonTool: null, // open layers interaction object (Draw)
-    cutTool: null, // open layers interaction object (Draw)
+    lineToolEnabled: false,
+    polygonToolEnabled: false,
+    cutToolEnabled: false,
     isolinesLegendImgDataURL: "",
-    layers: [
-      {
-        name: LAYERS.OPENSTREETMAP,
-        data: new TileLayer({
-          source: new OSM()
-        }),
-        isVisible: true,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.INLANDECDIS,
-        data: new TileLayer({
-          source: new TileWMS({
-            preload: 1,
-            url: "https://service.d4d-portal.info/wms/",
-            crossOrigin: "anonymous",
-            params: { LAYERS: "d4d", VERSION: "1.1.1", TILED: true }
-          })
-        }),
-        isVisible: true,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.WATERWAYAREA,
-        data: new VectorLayer({
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: new Style({
-            stroke: new Stroke({
-              color: "rgba(0, 102, 0, 1)",
-              width: 2
-            })
-          })
-        }),
-        isVisible: true,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.STRETCHES,
-        data: new VectorLayer({
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: new Style({
-            stroke: new Stroke({
-              color: "rgba(250, 200, 0, .8)",
-              width: 2
-            }),
-            fill: new Fill({
-              color: "rgba(250, 200, 10, .3)"
-            })
-          })
-        }),
-        isVisible: false,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.FAIRWAYDIMENSIONSLOS3,
-        data: new VectorLayer({
-          source: new VectorSource(),
-          style: function() {
-            return [
-              new Style({
-                stroke: new Stroke({
-                  color: "rgba(0, 0, 255, 1.0)",
-                  width: 2
-                }),
-                fill: new Fill({
-                  color: "rgba(255, 255, 255, 0.4)"
-                })
-              }),
-              new Style({
-                text: new Text({
-                  font: 'bold 12px "Open Sans", "sans-serif"',
-                  placement: "line",
-                  fill: new Fill({
-                    color: "black"
-                  }),
-                  text: "LOS: 3"
-                  //, zIndex: 10
-                })
-              })
-            ];
-          }
-        }),
-        isVisible: true,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.FAIRWAYDIMENSIONSLOS2,
-        data: new VectorLayer({
-          source: new VectorSource(),
-          style: function() {
-            return [
-              new Style({
-                stroke: new Stroke({
-                  color: "rgba(0, 0, 255, 0.9)",
-                  lineDash: [3, 6],
-                  lineCap: "round",
-                  width: 2
-                }),
-                fill: new Fill({
-                  color: "rgba(240, 230, 0, 0.1)"
-                })
-              }),
-              new Style({
-                text: new Text({
-                  font: 'bold 12px "Open Sans", "sans-serif"',
-                  placement: "line",
-                  fill: new Fill({
-                    color: "black"
-                  }),
-                  text: "LOS: 2"
-                  //, zIndex: 10
-                })
-              })
-            ];
-          }
-        }),
-        isVisible: false,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.FAIRWAYDIMENSIONSLOS1,
-        data: new VectorLayer({
-          source: new VectorSource(),
-          style: function() {
-            return [
-              new Style({
-                stroke: new Stroke({
-                  color: "rgba(0, 0, 255, 0.8)",
-                  lineDash: [2, 4],
-                  lineCap: "round",
-                  width: 2
-                }),
-                fill: new Fill({
-                  color: "rgba(240, 230, 0, 0.2)"
-                })
-              }),
-              new Style({
-                text: new Text({
-                  font: 'bold 12px "Open Sans", "sans-serif"',
-                  placement: "line",
-                  fill: new Fill({
-                    color: "black"
-                  }),
-                  text: "LOS: 1"
-                  //, zIndex: 10
-                })
-              })
-            ];
-          }
-        }),
-        isVisible: false,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.WATERWAYAXIS,
-        data: new VectorLayer({
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: new Style({
-            stroke: new Stroke({
-              color: "rgba(0, 0, 255, .5)",
-              lineDash: [5, 5],
-              width: 2
-            })
-          }),
-          // TODO: Set layer in layertree active/inactive depending on
-          // resolution.
-          maxResolution: 5,
-          minResolution: 0
-        }),
-        isVisible: true,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.WATERWAYPROFILES,
-        data: new VectorLayer({
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: new Style({
-            stroke: new Stroke({
-              color: "rgba(0, 0, 255, .5)",
-              lineDash: [5, 5],
-              width: 2
-            })
-          }),
-          maxResolution: 2.5,
-          minResolution: 0
-        }),
-        isVisible: true,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.BOTTLENECKS,
-        data: new VectorLayer({
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: function() {
-            return new Style({
-              stroke: new Stroke({
-                color: "rgba(230, 230, 10, .8)",
-                width: 4
-              }),
-              fill: new Fill({
-                color: "rgba(230, 230, 10, .3)"
-              })
-            });
-          }
-        }),
-        isVisible: true,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.BOTTLENECKISOLINE,
-        data: new TileLayer({
-          source: new TileWMS({
-            preload: 0,
-            projection: "EPSG:3857",
-            url: window.location.origin + "/api/internal/wms",
-            params: {
-              LAYERS: "sounding_results_contour_lines_geoserver",
-              VERSION: "1.1.1",
-              TILED: true
-            },
-            tileLoadFunction: function(tile, src) {
-              // console.log("calling for", tile, src);
-              HTTP.get(src, {
-                headers: {
-                  "X-Gemma-Auth": localStorage.getItem("token")
-                },
-                responseType: "blob"
-              }).then(response => {
-                tile.getImage().src = URL.createObjectURL(response.data);
-              });
-            } // TODO  tile.setState(TileState.ERROR);
-          })
-        }),
-        isVisible: false,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.BOTTLENECKSTATUS,
-        forLegendStyle: { point: true, resolution: 16 },
-        data: new VectorLayer({
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: function(feature, resolution, isLegend) {
-            let styles = [];
-            if ((feature.get("fa_critical") && resolution > 15) || isLegend) {
-              let bnCenter = getCenter(feature.getGeometry().getExtent());
-              styles.push(
-                new Style({
-                  geometry: new Point(bnCenter),
-                  image: new Icon({
-                    src: require("../assets/marker-bottleneck-critical.png"),
-                    anchor: [0.5, 0.5],
-                    scale: isLegend ? 0.5 : 1
-                  })
-                })
-              );
-            }
-            if (feature.get("fa_critical") && !isLegend) {
-              styles.push(
-                new Style({
-                  stroke: new Stroke({
-                    color: "rgba(255, 0, 0, 1)",
-                    width: 4
-                  })
-                })
-              );
-            }
-            return styles;
-          }
-        }),
-        isVisible: true,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.DISTANCEMARKS,
-        forLegendStyle: { point: true, resolution: 8 },
-        data: new VectorLayer({
-          source: new VectorSource({
-            strategy: bboxStrategy
-          })
-        }),
-        isVisible: false,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.DISTANCEMARKSAXIS,
-        forLegendStyle: { point: true, resolution: 8 },
-        data: new VectorLayer({
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: function(feature, resolution) {
-            if (resolution < 10) {
-              var s = new Style({
-                image: new Circle({
-                  radius: 5,
-                  fill: new Fill({ color: "rgba(255, 0, 0, 0.1)" }),
-                  stroke: new Stroke({ color: "blue", width: 1 })
-                })
-              });
-              if (resolution < 6) {
-                s.setText(
-                  new Text({
-                    offsetY: 12,
-                    font: '10px "Open Sans", "sans-serif"',
-                    fill: new Fill({
-                      color: "black"
-                    }),
-                    text: (feature.get("hectometre") / 10).toString()
-                  })
-                );
-              }
-              return s;
-            } else {
-              return [];
-            }
-          }
-        }),
-        isVisible: true,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.GAUGES,
-        forLegendStyle: { point: true, resolution: 8 },
-        data: new VectorLayer({
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: function(feature, resolution, isLegend) {
-            return [
-              new Style({
-                image: new Icon({
-                  src: require("../assets/marker-gauge.png"),
-                  anchor: [0.5, isLegend ? 0.5 : 1],
-                  scale: isLegend ? 0.5 : 1
-                }),
-                text: new Text({
-                  font: '10px "Open Sans", "sans-serif"',
-                  offsetY: 8,
-                  fill: new Fill({
-                    color: "white"
-                  }),
-                  text: feature.get("objname")
-                })
-              }),
-              new Style({
-                text: new Text({
-                  font: '10px "Open Sans", "sans-serif"',
-                  offsetY: 7,
-                  offsetX: -1,
-                  fill: new Fill({
-                    color: "black"
-                  }),
-                  text: feature.get("objname")
-                })
-              })
-            ];
-          },
-          maxResolution: 100,
-          minResolution: 0
-        }),
-        isVisible: true,
-        showInLegend: true
-      },
-      {
-        name: LAYERS.DRAWTOOL,
-        data: new VectorLayer({
-          source: new VectorSource({ wrapX: false }),
-          style: function(feature) {
-            // adapted from OpenLayer's LineString Arrow Example
-            var geometry = feature.getGeometry();
-            var styles = [
-              // linestring
-              new Style({
-                stroke: new Stroke({
-                  color: "#369aca",
-                  width: 2
-                })
-              })
-            ];
-
-            if (geometry.getType() === "LineString") {
-              geometry.forEachSegment(function(start, end) {
-                var dx = end[0] - start[0];
-                var dy = end[1] - start[1];
-                var rotation = Math.atan2(dy, dx);
-                // arrows
-                styles.push(
-                  new Style({
-                    geometry: new Point(end),
-                    image: new Icon({
-                      // we need to make sure the image is loaded by Vue Loader
-                      src: require("../assets/linestring_arrow.png"),
-                      // fiddling with the anchor's y value does not help to
-                      // position the image more centered on the line ending, as the
-                      // default line style seems to be slightly uncentered in the
-                      // anti-aliasing, but the image is not placed with subpixel
-                      // precision
-                      anchor: [0.75, 0.5],
-                      rotateWithView: true,
-                      rotation: -rotation
-                    })
-                  })
-                );
-              });
-            }
-            return styles;
-          }
-        }),
-        isVisible: true,
-        showInLegend: false
-      },
-      {
-        name: LAYERS.CUTTOOL,
-        data: new VectorLayer({
-          source: new VectorSource({ wrapX: false }),
-          style: function(feature) {
-            // adapted from OpenLayer's LineString Arrow Example
-            var geometry = feature.getGeometry();
-            var styles = [
-              // linestring
-              new Style({
-                stroke: new Stroke({
-                  color: "#333333",
-                  width: 2,
-                  lineDash: [7, 7]
-                })
-              })
-            ];
-
-            if (geometry.getType() === "LineString") {
-              geometry.forEachSegment(function(start, end) {
-                var dx = end[0] - start[0];
-                var dy = end[1] - start[1];
-                var rotation = Math.atan2(dy, dx);
-                // arrows
-                styles.push(
-                  new Style({
-                    geometry: new Point(end),
-                    image: new Icon({
-                      // we need to make sure the image is loaded by Vue Loader
-                      src: require("../assets/linestring_arrow_grey.png"),
-                      // fiddling with the anchor's y value does not help to
-                      // position the image more centered on the line ending, as the
-                      // default line style seems to be slightly uncentered in the
-                      // anti-aliasing, but the image is not placed with subpixel
-                      // precision
-                      anchor: [0.75, 0.5],
-                      rotateWithView: true,
-                      rotation: -rotation
-                    })
-                  })
-                );
-              });
-            }
-            return styles;
-          }
-        }),
-        isVisible: true,
-        showInLegend: false
-      }
-    ]
+    differencesLegendImgDataURL: ""
   };
 };
 
@@ -560,14 +57,10 @@
   namespaced: true,
   state: init(),
   getters: {
-    layersForLegend: state => {
-      return state.layers.filter(layer => layer.showInLegend);
-    },
-    getLayerByName: state => name => {
-      return state.layers.find(layer => layer.name === name);
-    },
-    getVSourceByName: (state, getters) => name => {
-      return getters.getLayerByName(name).data.getSource();
+    openLayersMap: state => id => {
+      return state.openLayersMaps.find(
+        map => map.getTarget() === "map-" + (id || "main")
+      );
     },
     filteredIdentifiedFeatures: state => {
       return state.identifiedFeatures.filter(f => f.getId());
@@ -580,85 +73,106 @@
     extent: (state, extent) => {
       state.extent = extent;
     },
-    setLayerVisible: (state, name) => {
-      const layer = state.layers.findIndex(l => l.name === name);
-      state.layers[layer].isVisible = true;
-      state.layers[layer].data.setVisible(true);
+    addOpenLayersMap: (state, map) => {
+      state.openLayersMaps.push(map);
     },
-    setLayerInvisible: (state, name) => {
-      const layer = state.layers.findIndex(l => l.name === name);
-      state.layers[layer].isVisible = false;
-      state.layers[layer].data.setVisible(false);
+    removeOpenLayersMap: (state, map) => {
+      let index = state.openLayersMaps.findIndex(
+        m => m.getTarget() === map.getTarget()
+      );
+      if (index !== -1) {
+        state.openLayersMaps.splice(index, 1);
+      }
     },
-    toggleVisibilityByName: (state, name) => {
-      const layer = state.layers.findIndex(l => l.name === name);
-      state.layers[layer].isVisible = !state.layers[layer].isVisible;
-      state.layers[layer].data.setVisible(state.layers[layer].isVisible);
+    syncedMaps: (state, ids) => {
+      state.syncedMaps = ids;
+    },
+    syncedView: (state, view) => {
+      state.syncedView = view;
     },
-    toggleVisibility: (state, layer) => {
-      state.layers[layer].isVisible = !state.layers[layer].isVisible;
-      state.layers[layer].data.setVisible(state.layers[layer].isVisible);
+    mapPopup: (state, popup) => {
+      state.mapPopup = popup;
     },
-    openLayersMap: (state, map) => {
-      state.openLayersMap = map;
-    },
-    identifyTool: (state, events) => {
-      state.identifyTool = events;
+    mapPopupEnabled: (state, enabled) => {
+      state.mapPopupEnabled = enabled;
     },
     setIdentifiedFeatures: (state, identifiedFeatures) => {
       state.identifiedFeatures = identifiedFeatures;
     },
+    addIdentifiedFeatures: (state, identifiedFeatures) => {
+      state.identifiedFeatures = state.identifiedFeatures.concat(
+        identifiedFeatures
+      );
+    },
+    identifiedCoordinates: (state, coordinates) => {
+      state.identifiedCoordinates = coordinates;
+    },
     setCurrentMeasurement: (state, measurement) => {
       state.currentMeasurement = measurement;
     },
-    lineTool: (state, lineTool) => {
-      state.lineTool = lineTool;
-    },
-    polygonTool: (state, polygonTool) => {
-      state.polygonTool = polygonTool;
-    },
-    cutTool: (state, cutTool) => {
-      state.cutTool = cutTool;
-    },
-    moveToBoundingBox: (state, { boundingBox, zoom, preventZoomOut }) => {
-      const extent = transformExtent(boundingBox, "EPSG:4326", "EPSG:3857");
-      let view = state.openLayersMap.getView();
-      moveMap({ view, extent, zoom, preventZoomOut });
+    lineToolEnabled: (state, enabled) => {
+      state.lineToolEnabled = enabled;
+      state.openLayersMaps.forEach(m => {
+        let tool = m
+          .getInteractions()
+          .getArray()
+          .find(i => i.get("id") === "linetool");
+        if (tool) {
+          tool.setActive(enabled);
+        }
+      });
     },
-    moveToExtent: (state, { feature, zoom, preventZoomOut }) => {
-      const boundingBox = bbox(feature.geometry);
-      const extent = transformExtent(boundingBox, "EPSG:4326", "EPSG:3857");
-      let view = state.openLayersMap.getView();
-      moveMap({ view, extent, zoom, preventZoomOut });
+    polygonToolEnabled: (state, enabled) => {
+      state.polygonToolEnabled = enabled;
+      state.openLayersMaps.forEach(m => {
+        let tool = m
+          .getInteractions()
+          .getArray()
+          .find(i => i.get("id") === "polygontool");
+        if (tool) {
+          tool.setActive(enabled);
+        }
+      });
     },
-    moveMap: (state, { coordinates, zoom, preventZoomOut }) => {
-      let view = state.openLayersMap.getView();
-      const currentZoom = view.getZoom();
-      zoom = zoom || currentZoom;
-      view.animate({
-        zoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom,
-        center: fromLonLat(coordinates, view.getProjection()),
-        duration: 700
+    cutToolEnabled: (state, enabled) => {
+      state.cutToolEnabled = enabled;
+      state.openLayersMaps.forEach(m => {
+        let tool = m
+          .getInteractions()
+          .getArray()
+          .find(i => i.get("id") === "cuttool");
+        if (tool) {
+          tool.setActive(enabled);
+        }
       });
     },
     isolinesLegendImgDataURL: (state, isolinesLegendImgDataURL) => {
       state.isolinesLegendImgDataURL = isolinesLegendImgDataURL;
+    },
+    differencesLegendImgDataURL: (state, differencesLegendImgDataURL) => {
+      state.differencesLegendImgDataURL = differencesLegendImgDataURL;
     }
   },
   actions: {
-    openLayersMap({ commit, dispatch, getters }, map) {
-      const drawVectorSrc = getters.getVSourceByName("Draw Tool");
-      const cutVectorSrc = getters.getVSourceByName("Cut Tool");
+    openLayersMap({ state, commit, dispatch }, map) {
+      const drawVectorSrc = map.getLayer("DRAWTOOL").getSource();
+      const cutVectorSrc = map.getLayer("CUTTOOL").getSource();
 
       // init line tool
       const lineTool = new Draw({
         source: drawVectorSrc,
         type: "LineString",
-        maxPoints: 2
+        maxPoints: 2,
+        stopClick: true
       });
+      lineTool.set("id", "linetool");
       lineTool.setActive(false);
       lineTool.on("drawstart", () => {
-        drawVectorSrc.clear();
+        state.openLayersMaps.forEach(m => {
+          m.getLayer("DRAWTOOL")
+            .getSource()
+            .clear();
+        });
         commit("setCurrentMeasurement", null);
       });
       lineTool.on("drawend", event => {
@@ -674,11 +188,17 @@
       const polygonTool = new Draw({
         source: drawVectorSrc,
         type: "Polygon",
-        maxPoints: 50
+        maxPoints: 50,
+        stopClick: true
       });
+      polygonTool.set("id", "polygontool");
       polygonTool.setActive(false);
       polygonTool.on("drawstart", () => {
-        drawVectorSrc.clear();
+        state.openLayersMaps.forEach(m => {
+          m.getLayer("DRAWTOOL")
+            .getSource()
+            .clear();
+        });
         commit("setCurrentMeasurement", null);
       });
       polygonTool.on("drawend", event => {
@@ -699,6 +219,7 @@
         source: cutVectorSrc,
         type: "LineString",
         maxPoints: 2,
+        stopClick: true,
         style: new Style({
           stroke: new Stroke({
             color: "#444",
@@ -712,151 +233,358 @@
           })
         })
       });
+      cutTool.set("id", "cuttool");
       cutTool.setActive(false);
       cutTool.on("drawstart", () => {
-        dispatch("disableIdentifyTool");
-        cutVectorSrc.clear();
+        state.openLayersMaps.forEach(m => {
+          m.getLayer("CUTTOOL")
+            .getSource()
+            .clear();
+        });
       });
       cutTool.on("drawend", event => {
         commit("fairwayprofile/selectedCut", null, { root: true });
-        dispatch("fairwayprofile/cut", event.feature, { root: true }).then(() =>
-          // This setTimeout is an ugly workaround. If we would enable the
-          // identifyTool here immediately then the click event from ending the
-          // cut will trigger it. We don't want that.
-          setTimeout(() => dispatch("enableIdentifyTool"), 1000)
-        );
+        dispatch("fairwayprofile/cut", event.feature, { root: true });
       });
 
       map.addInteraction(lineTool);
       map.addInteraction(cutTool);
       map.addInteraction(polygonTool);
 
-      commit("lineTool", lineTool);
-      commit("polygonTool", polygonTool);
-      commit("cutTool", cutTool);
-      commit("openLayersMap", map);
-    },
-    disableIdentifyTool({ state }) {
-      unByKey(state.identifyTool);
-      state.identifyTool = null;
+      // If there are multiple maps and you enable one of the draw tools, when
+      // moving the mouse to another map, the cursor for the draw tool stays
+      // visible in the first map, right next to the edge where the cursor left
+      // the map. So here we disable all draw layers except the ones in the map
+      // that the user currently hovering with the mouse.
+      map.getTargetElement().addEventListener("mouseenter", () => {
+        if (
+          state.lineToolEnabled ||
+          state.polygonToolEnabled ||
+          state.cutToolEnabled
+        ) {
+          state.openLayersMaps.forEach(m => {
+            let lineTool = m
+              .getInteractions()
+              .getArray()
+              .find(i => i.get("id") === "linetool");
+            let polygonTool = m
+              .getInteractions()
+              .getArray()
+              .find(i => i.get("id") === "polygontool");
+            let cutTool = m
+              .getInteractions()
+              .getArray()
+              .find(i => i.get("id") === "cuttool");
+            if (lineTool) lineTool.setActive(false);
+            if (polygonTool) polygonTool.setActive(false);
+            if (cutTool) cutTool.setActive(false);
+          });
+          let lineTool = map
+            .getInteractions()
+            .getArray()
+            .find(i => i.get("id") === "linetool");
+          let polygonTool = map
+            .getInteractions()
+            .getArray()
+            .find(i => i.get("id") === "polygontool");
+          let cutTool = map
+            .getInteractions()
+            .getArray()
+            .find(i => i.get("id") === "cuttool");
+          if (lineTool && state.lineToolEnabled) lineTool.setActive(true);
+          if (polygonTool && state.polygonToolEnabled)
+            polygonTool.setActive(true);
+          if (cutTool && state.cutToolEnabled) cutTool.setActive(true);
+        }
+      });
+
+      commit("addOpenLayersMap", map);
     },
-    enableIdentifyTool({ state, rootState, commit, dispatch, getters }) {
-      if (!state.identifyTool) {
-        state.identifyTool = state.openLayersMap.on(
-          ["singleclick", "dblclick"],
-          event => {
-            commit("setIdentifiedFeatures", []);
-            // checking our WFS layers
-            var features = state.openLayersMap.getFeaturesAtPixel(event.pixel, {
-              hitTolerance: 7
-            });
-            if (features) {
-              let identifiedFeatures = [];
+    initIdentifyTool({ state, rootState, commit, dispatch }, map) {
+      map.on(["singleclick", "dblclick"], event => {
+        commit(
+          "identifiedCoordinates",
+          toLonLat(event.coordinate, map.getView().getProjection())
+        );
+        state.mapPopup.setPosition(undefined);
+        commit("setIdentifiedFeatures", []);
+        // checking our WFS layers
+        var features = map.getFeaturesAtPixel(event.pixel, { hitTolerance: 7 });
+        if (features) {
+          let all = [];
+          let bottlenecks = [];
+          let gauges = [];
+          let stretches = [];
+          let sections = [];
 
-              for (let feature of features) {
-                let id = feature.getId();
-
-                // avoid identifying the same feature twice
-                if (
-                  identifiedFeatures.findIndex(
-                    f => f.getId() === feature.getId()
-                  ) === -1
-                ) {
-                  identifiedFeatures.push(feature);
-                }
+          for (let feature of features) {
+            // avoid identifying the same feature twice
+            if (all.findIndex(f => f.getId() === feature.getId()) === -1)
+              all.push(feature);
 
-                // get selected bottleneck
-                // RegExp.prototype.test() works with number, str and undefined
-                if (/^bottlenecks/.test(id)) {
-                  if (
-                    rootState.bottlenecks.selectedBottleneck !=
-                    feature.get("objnam")
-                  ) {
-                    dispatch(
-                      "bottlenecks/setSelectedBottleneck",
-                      feature.get("objnam"),
-                      { root: true }
-                    ).then(() => {
-                      this.commit("bottlenecks/setFirstSurveySelected");
-                    });
-                    commit("moveMap", {
-                      coordinates: getCenter(
-                        feature
-                          .getGeometry()
-                          .clone()
-                          .transform("EPSG:3857", "EPSG:4326")
-                          .getExtent()
-                      ),
-                      zoom: 17,
-                      preventZoomOut: true
-                    });
-                  }
-                }
+            let id = feature.getId();
+            // RegExp.prototype.test() works with number, str and undefined
+            // get selected bottleneck
+            if (/^bottlenecks/.test(id)) bottlenecks.push(feature);
+            // get selected gauge
+            if (/^gauges/.test(id)) gauges.push(feature);
+            // get selected stretch
+            if (/^stretches/.test(id)) stretches.push(feature);
+            // get selected section
+            if (/^sections/.test(id)) sections.push(feature);
+          }
+
+          commit("setIdentifiedFeatures", all);
 
-                // get selected gauge
-                if (/^gauges/.test(id)) {
-                  if (
-                    rootState.gauges.selectedGaugeISRS !==
-                    feature.get("isrs_code")
-                  ) {
-                    dispatch(
-                      "gauges/selectedGaugeISRS",
-                      feature.get("isrs_code"),
-                      {
-                        root: true
-                      }
-                    );
-                    commit("moveMap", {
-                      coordinates: getCenter(
-                        feature
-                          .getGeometry()
-                          .clone()
-                          .transform("EPSG:3857", "EPSG:4326")
-                          .getExtent()
-                      ),
-                      zoom: null,
-                      preventZoomOut: true
-                    });
-                  }
-                }
-              }
+          // Decide whether we open a related dialog immediately or show the
+          // popup with possible options first.
+          // The following cases require a manual decision via the popup because
+          // the targeted feature is not clear.
+          if (
+            (bottlenecks.length ||
+              gauges.length > 1 ||
+              stretches.length > 1 ||
+              sections.length > 1 ||
+              (sections.length && stretches.length) ||
+              (gauges.length && sections.length) ||
+              (gauges.length && stretches.length)) &&
+            state.mapPopupEnabled
+          ) {
+            state.mapPopup.setMap(map);
+            state.mapPopup.setPosition(event.coordinate);
+          }
+          // The following scenarios lead to a distinct action without popup.
+          if (
+            gauges.length === 1 &&
+            !bottlenecks.length &&
+            !sections.length &&
+            !stretches.length
+          ) {
+            commit("application/showGauges", true, { root: true });
+            dispatch("gauges/selectedGaugeISRS", gauges[0].get("isrs_code"), {
+              root: true
+            });
+          }
+          if (
+            stretches.length === 1 &&
+            !sections.length &&
+            !bottlenecks.length &&
+            !gauges.length
+          ) {
+            if (rootState.imports.selectedStretchId === stretches[0].getId()) {
+              commit("imports/selectedStretchId", null, {
+                root: true
+              });
+            } else {
+              commit("imports/selectedStretchId", stretches[0].getId(), {
+                root: true
+              });
+              commit("fairwayavailability/type", "stretches", { root: true });
+              commit("application/showFairwayDepth", true, { root: true });
+              dispatch("moveToFeauture", { feature: stretches[0], zoom: 17 });
+            }
+          }
+          if (
+            sections.length === 1 &&
+            !stretches.length &&
+            !bottlenecks.length &&
+            !gauges.length
+          ) {
+            if (rootState.imports.selectedSectionId === sections[0].getId()) {
+              commit("imports/selectedSectionId", null, {
+                root: true
+              });
+            } else {
+              commit("imports/selectedSectionId", sections[0].getId(), {
+                root: true
+              });
+              commit("fairwayavailability/type", "sections", { root: true });
+              commit("application/showFairwayDepth", true, { root: true });
+              dispatch("moveToFeauture", { feature: sections[0], zoom: 17 });
+            }
+          }
+        }
 
-              commit("setIdentifiedFeatures", identifiedFeatures);
-            }
-
-            // DEBUG output and example how to remove the GeometryName
-            /*
-            for (let feature of features) {
-              console.log("Identified:", feature.getId());
-              for (let key of feature.getKeys()) {
-                if (key != feature.getGeometryName()) {
-                  console.log(key, feature.get(key));
-                }
+        // DEBUG output and example how to remove the GeometryName
+        /*
+          for (let feature of features) {
+            console.log("Identified:", feature.getId());
+            for (let key of feature.getKeys()) {
+              if (key != feature.getGeometryName()) {
+                console.log(key, feature.get(key));
               }
             }
-            */
+          }
+          */
+
+        let currentResolution = map.getView().getResolution();
+
+        var waterwayAxisSource = map.getLayer("WATERWAYAXIS").getSource();
+        var waxisUrl = waterwayAxisSource.getGetFeatureInfoUrl(
+          event.coordinate,
+          currentResolution /* resolution */,
+          "EPSG:3857",
+          // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d
+          { INFO_FORMAT: "application/json" }
+        );
+
+        if (waxisUrl) {
+          // cannot directly query here because of SOP
+          HTTP.get(waxisUrl, {
+            headers: {
+              "X-Gemma-Auth": localStorage.getItem("token")
+            }
+          }).then(response => {
+            let features = response.data.features.map(f => {
+              let feat = new Feature({
+                geometry: new Point(f.geometry.coordinates)
+              });
+              feat.setId(f.id);
+              feat.setProperties(f.properties);
+              return feat;
+            });
+            commit("addIdentifiedFeatures", features);
+          });
+        }
+        var waterwayAreaSource = map.getLayer("WATERWAYAREA").getSource();
+        var wareaUrl = waterwayAreaSource.getGetFeatureInfoUrl(
+          event.coordinate,
+          currentResolution /* resolution */,
+          "EPSG:3857",
+          // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d
+          { INFO_FORMAT: "application/json" }
+        );
+
+        if (wareaUrl) {
+          HTTP.get(wareaUrl, {
+            headers: {
+              "X-Gemma-Auth": localStorage.getItem("token")
+            }
+          }).then(response => {
+            let features = response.data.features.map(f => {
+              let feat = new Feature({
+                geometry: new Point(f.geometry.coordinates)
+              });
+              feat.setId(f.id);
+              feat.setProperties(f.properties);
+              return feat;
+            });
+            commit("addIdentifiedFeatures", features);
+          });
+        }
+        var dmSource = map.getLayer("DISTANCEMARKS").getSource();
+        var dmUrl = dmSource.getGetFeatureInfoUrl(
+          event.coordinate,
+          currentResolution /* resolution */,
+          "EPSG:3857",
+          // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d
+          { INFO_FORMAT: "application/json" }
+        );
 
-            // trying the GetFeatureInfo way for WMS
-            var wmsSource = getters.getVSourceByName(
-              "Inland ECDIS chart Danube"
-            );
-            var url = wmsSource.getGetFeatureInfoUrl(
-              event.coordinate,
-              100 /* resolution */,
-              "EPSG:3857",
-              // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d
-              { INFO_FORMAT: "text/plain" }
-            );
+        if (dmUrl) {
+          HTTP.get(dmUrl + "&BUFFER=5", {
+            headers: {
+              "X-Gemma-Auth": localStorage.getItem("token")
+            }
+          }).then(response => {
+            let features = response.data.features.map(f => {
+              let feat = new Feature({
+                geometry: new Point(f.geometry.coordinates)
+              });
+              feat.setId(f.id);
+              feat.setProperties(f.properties);
+              return feat;
+            });
+            commit("addIdentifiedFeatures", features);
+          });
+        }
+        var dmaSource = map.getLayer("DISTANCEMARKSAXIS").getSource();
+        var dmaUrl = dmaSource.getGetFeatureInfoUrl(
+          event.coordinate,
+          currentResolution /* resolution */,
+          "EPSG:3857",
+          // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d
+          { INFO_FORMAT: "application/json" }
+        );
 
-            if (url) {
-              // cannot directly query here because of SOP
-              console.log("GetFeatureInfo url:", url);
+        if (dmaUrl) {
+          HTTP.get(dmaUrl + "&BUFFER=5", {
+            headers: {
+              "X-Gemma-Auth": localStorage.getItem("token")
             }
-          }
+          }).then(response => {
+            let features = response.data.features.map(f => {
+              let feat = new Feature({
+                geometry: new Point(f.geometry.coordinates)
+              });
+              feat.setId(f.id);
+              feat.setProperties(f.properties);
+              return feat;
+            });
+            commit("addIdentifiedFeatures", features);
+          });
+        }
+        // trying the GetFeatureInfo way for WMS
+        var iecdisSource = map.getLayer("INLANDECDIS").getSource();
+        var iecdisUrl = iecdisSource.getGetFeatureInfoUrl(
+          event.coordinate,
+          currentResolution /* resolution */,
+          "EPSG:3857",
+          // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d
+          { INFO_FORMAT: "text/plain" }
         );
-      }
+
+        if (iecdisUrl) {
+          // cannot directly query here because of SOP
+          console.log("GetFeatureInfo url:", iecdisUrl);
+        }
+      });
+    },
+    refreshLayers({ state }) {
+      state.openLayersMaps.forEach(map => {
+        let layers = map.getLayers().getArray();
+        for (let i = 0; i < layers.length; i++) {
+          let layer = layers[i];
+          if (
+            layer instanceof VectorLayer &&
+            layer.get("source").loader_.name != "VOID"
+          ) {
+            layer.getSource().clear(true);
+            layer.getSource().refresh({ force: true });
+          }
+        }
+      });
+    },
+    moveToBoundingBox(
+      { state },
+      { boundingBox, zoom, preventZoomOut, duration }
+    ) {
+      const extent = transformExtent(boundingBox, "EPSG:4326", "EPSG:3857");
+      const currentZoom = state.syncedView.getZoom();
+      zoom = zoom || currentZoom;
+      state.syncedView.fit(extent, {
+        maxZoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom,
+        duration: duration || 700
+      });
+    },
+    moveToFeauture({ dispatch }, { feature, zoom, preventZoomOut }) {
+      const boundingBox = feature.hasOwnProperty("geometry")
+        ? bbox(feature.geometry)
+        : feature
+            .getGeometry()
+            .clone()
+            .transform("EPSG:3857", "EPSG:4326")
+            .getExtent();
+      dispatch("moveToBoundingBox", { boundingBox, zoom, preventZoomOut });
+    },
+    moveMap({ state }, { coordinates, zoom, preventZoomOut }) {
+      const currentZoom = state.syncedView.getZoom();
+      zoom = zoom || currentZoom;
+      state.syncedView.animate({
+        zoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom,
+        center: fromLonLat(coordinates, state.syncedView.getProjection()),
+        duration: 700
+      });
     }
   }
 };
-
-export { LAYERS };
--- a/client/src/store/user.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/store/user.js	Mon Jun 03 10:19:18 2019 +0200
@@ -13,8 +13,8 @@
  * Markus Kottländer <markus@intevation.de>
  */
 
-import { HTTP } from "../lib/http";
-import { toMillisFromString } from "../lib/session";
+import { HTTP } from "@/lib/http";
+import { toMillisFromString } from "@/lib/session";
 
 const init = () => {
   return {
--- a/client/src/store/usermanagement.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/src/store/usermanagement.js	Mon Jun 03 10:19:18 2019 +0200
@@ -13,7 +13,7 @@
  * Markus Kottländer <markus@intevation.de>
  */
 
-import { HTTP } from "../lib/http";
+import { HTTP } from "@/lib/http";
 
 // initial state
 const init = () => {
@@ -41,6 +41,12 @@
   namespaced: true,
   state: init(),
   getters: {
+    userCountries: state => {
+      return state.users.reduce((o, n) => {
+        o[n.user] = n.role !== "sys_admin" ? n.country : "global";
+        return o;
+      }, {});
+    },
     isUserDetailsVisible: state => {
       return state.userDetailsVisible;
     },
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/tests/e2e/reports/import.xml	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuites errors="0"
+            failures="0"
+            tests="6">
+
+  <testsuite name="import"
+    errors="0" failures="0" hostname="" id="" package="import" skipped="6"
+    tests="6" time="0.000" timestamp="Thu, 04 Apr 2019 10:16:03 GMT">
+  
+
+  
+    <system-err>
+        Error while running [Protocols / Loading protocols]:
+
+  TypeError: browser.url(...).waitForElementVisible(...).setValue(...).setValue(...).click(...).pause(...).click(...).pause(...).click(...).pause(...).url.contains is not a function
+    at Object.Loading protocols (/home/thomas/go/src/gemma.intevation.de/gemma/client/tests/e2e/specs/protocols.js:31:12)
+    at Module.call (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/module.js:62:34)
+    at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/testcase.js:70:29
+    at _fulfilled (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:834:54)
+    at self.promiseDispatch.done (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:863:30)
+    at Promise.promise.promiseDispatch (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:796:13)
+    at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:556:49
+    at runSingle (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:137:13)
+    at flush (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:125:13)
+    at _combinedTickCallback (internal/process/next_tick.js:132:7)
+    </system-err>
+  
+
+  
+    
+    <testcase
+      name="Bottleneck import" classname="import">
+      <skipped />
+    </testcase>
+    
+    <testcase
+      name="Available fairwaydepth" classname="import">
+      <skipped />
+    </testcase>
+    
+    <testcase
+      name="Gauge measurement" classname="import">
+      <skipped />
+    </testcase>
+    
+    <testcase
+      name="Import Fairway Dimensions" classname="import">
+      <skipped />
+    </testcase>
+    
+    <testcase
+      name="Import Waterway Axis" classname="import">
+      <skipped />
+    </testcase>
+    
+    <testcase
+      name="Import Waterway Area " classname="import">
+      <skipped />
+    </testcase>
+    
+  
+  </testsuite>
+</testsuites>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/tests/e2e/reports/login.xml	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuites errors="0"
+            failures="0"
+            tests="5">
+
+  <testsuite name="login"
+    errors="0" failures="0" hostname="" id="" package="login" skipped="5"
+    tests="5" time="0.000" timestamp="Thu, 04 Apr 2019 10:16:03 GMT">
+  
+
+  
+    <system-err>
+        Error while running [Protocols / Loading protocols]:
+
+  TypeError: browser.url(...).waitForElementVisible(...).setValue(...).setValue(...).click(...).pause(...).click(...).pause(...).click(...).pause(...).url.contains is not a function
+    at Object.Loading protocols (/home/thomas/go/src/gemma.intevation.de/gemma/client/tests/e2e/specs/protocols.js:31:12)
+    at Module.call (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/module.js:62:34)
+    at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/testcase.js:70:29
+    at _fulfilled (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:834:54)
+    at self.promiseDispatch.done (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:863:30)
+    at Promise.promise.promiseDispatch (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:796:13)
+    at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:556:49
+    at runSingle (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:137:13)
+    at flush (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:125:13)
+    at _combinedTickCallback (internal/process/next_tick.js:132:7)
+    </system-err>
+  
+
+  
+    
+    <testcase
+      name="Page Load" classname="login">
+      <skipped />
+    </testcase>
+    
+    <testcase
+      name="Login failed" classname="login">
+      <skipped />
+    </testcase>
+    
+    <testcase
+      name="Login oana success" classname="login">
+      <skipped />
+    </testcase>
+    
+    <testcase
+      name="Login oana switch url" classname="login">
+      <skipped />
+    </testcase>
+    
+    <testcase
+      name="Login switch user from oana to sophie" classname="login">
+      <skipped />
+    </testcase>
+    
+  
+  </testsuite>
+</testsuites>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/tests/e2e/reports/protocols.xml	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuites errors="1"
+            failures="0"
+            tests="1">
+
+  <testsuite name="protocols"
+    errors="1" failures="0" hostname="" id="" package="protocols" skipped="0"
+    tests="1" time="0.005000" timestamp="Thu, 04 Apr 2019 10:16:03 GMT">
+  
+    <testcase name="Loading protocols" classname="protocols" time="0.005000" assertions="0">
+    </testcase>
+  
+
+  
+    <system-err>
+        Error while running [Protocols / Loading protocols]:
+
+  TypeError: browser.url(...).waitForElementVisible(...).setValue(...).setValue(...).click(...).pause(...).click(...).pause(...).click(...).pause(...).url.contains is not a function
+    at Object.Loading protocols (/home/thomas/go/src/gemma.intevation.de/gemma/client/tests/e2e/specs/protocols.js:31:12)
+    at Module.call (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/module.js:62:34)
+    at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/testcase.js:70:29
+    at _fulfilled (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:834:54)
+    at self.promiseDispatch.done (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:863:30)
+    at Promise.promise.promiseDispatch (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:796:13)
+    at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:556:49
+    at runSingle (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:137:13)
+    at flush (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:125:13)
+    at _combinedTickCallback (internal/process/next_tick.js:132:7)
+    </system-err>
+  
+
+  
+  </testsuite>
+</testsuites>
--- a/client/tests/e2e/specs/import.js	Wed May 29 10:58:45 2019 +0200
+++ b/client/tests/e2e/specs/import.js	Mon Jun 03 10:19:18 2019 +0200
@@ -16,6 +16,7 @@
 // http://nightwatchjs.org/guide#usage
 
 module.exports = {
+  "@disabled": true,
   "Bottleneck import": browser => {
     browser
       .url(process.env.VUE_DEV_SERVER_URL)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/tests/e2e/specs/protocols.js	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,37 @@
+/* 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>
+ */
+
+// For authoring Nightwatch tests, see
+// http://nightwatchjs.org/guide#usage
+
+module.exports = {
+  "Loading protocols": browser => {
+    browser
+      .url(process.env.VUE_DEV_SERVER_URL)
+      .waitForElementVisible("#app", 5000)
+      .setValue("input[id='inputUsername']", "sophie")
+      .setValue("input[id='inputPassword']", "so2Phie4")
+      .click("button[type='submit']")
+      .pause(1000)
+      .click(".menubutton")
+      .pause(1000)
+      .click("a[href='#/logs']")
+      .pause(1000)
+      .assert.urlContains("#/logs")
+      .click("#accesslog")
+      .click("#errorlog")
+      .assert.urlContains("#/logs")
+      .end();
+  }
+};
--- a/client/yarn.lock	Wed May 29 10:58:45 2019 +0200
+++ b/client/yarn.lock	Mon Jun 03 10:19:18 2019 +0200
@@ -10,33 +10,33 @@
     "@babel/highlight" "^7.0.0"
 
 "@babel/core@^7.0.0":
-  version "7.2.2"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.2.2.tgz#07adba6dde27bb5ad8d8672f15fde3e08184a687"
-  integrity sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.4.tgz#84055750b05fcd50f9915a826b44fa347a825250"
+  integrity sha512-lQgGX3FPRgbz2SKmhMtYgJvVzGZrmjaF4apZ2bLwofAKiSjxU0drPh4S/VasyYXwaTs+A1gvQ45BN8SQJzHsQQ==
   dependencies:
     "@babel/code-frame" "^7.0.0"
-    "@babel/generator" "^7.2.2"
-    "@babel/helpers" "^7.2.0"
-    "@babel/parser" "^7.2.2"
-    "@babel/template" "^7.2.2"
-    "@babel/traverse" "^7.2.2"
-    "@babel/types" "^7.2.2"
+    "@babel/generator" "^7.4.4"
+    "@babel/helpers" "^7.4.4"
+    "@babel/parser" "^7.4.4"
+    "@babel/template" "^7.4.4"
+    "@babel/traverse" "^7.4.4"
+    "@babel/types" "^7.4.4"
     convert-source-map "^1.1.0"
     debug "^4.1.0"
     json5 "^2.1.0"
-    lodash "^4.17.10"
+    lodash "^4.17.11"
     resolve "^1.3.2"
     semver "^5.4.1"
     source-map "^0.5.0"
 
-"@babel/generator@^7.2.2":
-  version "7.3.2"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.2.tgz#fff31a7b2f2f3dad23ef8e01be45b0d5c2fc0132"
-  integrity sha512-f3QCuPppXxtZOEm5GWPra/uYUjmNQlu9pbAD8D/9jze4pTY83rTtB1igTBSwvkeNlC5gR24zFFkz+2WHLFQhqQ==
-  dependencies:
-    "@babel/types" "^7.3.2"
+"@babel/generator@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041"
+  integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==
+  dependencies:
+    "@babel/types" "^7.4.4"
     jsesc "^2.5.1"
-    lodash "^4.17.10"
+    lodash "^4.17.11"
     source-map "^0.5.0"
     trim-right "^1.0.1"
 
@@ -55,34 +55,35 @@
     "@babel/helper-explode-assignable-expression" "^7.1.0"
     "@babel/types" "^7.0.0"
 
-"@babel/helper-call-delegate@^7.1.0":
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz#6a957f105f37755e8645343d3038a22e1449cc4a"
-  integrity sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==
-  dependencies:
-    "@babel/helper-hoist-variables" "^7.0.0"
-    "@babel/traverse" "^7.1.0"
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-create-class-features-plugin@^7.3.0":
-  version "7.3.2"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.2.tgz#ba1685603eb1c9f2f51c9106d5180135c163fe73"
-  integrity sha512-tdW8+V8ceh2US4GsYdNVNoohq5uVwOf9k6krjwW4E1lINcHgttnWcNqgdoessn12dAy8QkbezlbQh2nXISNY+A==
+"@babel/helper-call-delegate@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz#87c1f8ca19ad552a736a7a27b1c1fcf8b1ff1f43"
+  integrity sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==
+  dependencies:
+    "@babel/helper-hoist-variables" "^7.4.4"
+    "@babel/traverse" "^7.4.4"
+    "@babel/types" "^7.4.4"
+
+"@babel/helper-create-class-features-plugin@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz#fc3d690af6554cc9efc607364a82d48f58736dba"
+  integrity sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==
   dependencies:
     "@babel/helper-function-name" "^7.1.0"
     "@babel/helper-member-expression-to-functions" "^7.0.0"
     "@babel/helper-optimise-call-expression" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-replace-supers" "^7.2.3"
-
-"@babel/helper-define-map@^7.1.0":
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c"
-  integrity sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==
+    "@babel/helper-replace-supers" "^7.4.4"
+    "@babel/helper-split-export-declaration" "^7.4.4"
+
+"@babel/helper-define-map@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz#6969d1f570b46bdc900d1eba8e5d59c48ba2c12a"
+  integrity sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==
   dependencies:
     "@babel/helper-function-name" "^7.1.0"
-    "@babel/types" "^7.0.0"
-    lodash "^4.17.10"
+    "@babel/types" "^7.4.4"
+    lodash "^4.17.11"
 
 "@babel/helper-explode-assignable-expression@^7.1.0":
   version "7.1.0"
@@ -108,12 +109,12 @@
   dependencies:
     "@babel/types" "^7.0.0"
 
-"@babel/helper-hoist-variables@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz#46adc4c5e758645ae7a45deb92bab0918c23bb88"
-  integrity sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w==
-  dependencies:
-    "@babel/types" "^7.0.0"
+"@babel/helper-hoist-variables@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz#0298b5f25c8c09c53102d52ac4a98f773eb2850a"
+  integrity sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==
+  dependencies:
+    "@babel/types" "^7.4.4"
 
 "@babel/helper-member-expression-to-functions@^7.0.0":
   version "7.0.0"
@@ -129,17 +130,17 @@
   dependencies:
     "@babel/types" "^7.0.0"
 
-"@babel/helper-module-transforms@^7.1.0":
-  version "7.2.2"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.2.2.tgz#ab2f8e8d231409f8370c883d20c335190284b963"
-  integrity sha512-YRD7I6Wsv+IHuTPkAmAS4HhY0dkPobgLftHp0cRGZSdrRvmZY8rFvae/GVu3bD00qscuvK3WPHB3YdNpBXUqrA==
+"@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz#96115ea42a2f139e619e98ed46df6019b94414b8"
+  integrity sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==
   dependencies:
     "@babel/helper-module-imports" "^7.0.0"
     "@babel/helper-simple-access" "^7.1.0"
-    "@babel/helper-split-export-declaration" "^7.0.0"
-    "@babel/template" "^7.2.2"
-    "@babel/types" "^7.2.2"
-    lodash "^4.17.10"
+    "@babel/helper-split-export-declaration" "^7.4.4"
+    "@babel/template" "^7.4.4"
+    "@babel/types" "^7.4.4"
+    lodash "^4.17.11"
 
 "@babel/helper-optimise-call-expression@^7.0.0":
   version "7.0.0"
@@ -153,12 +154,12 @@
   resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250"
   integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==
 
-"@babel/helper-regex@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.0.0.tgz#2c1718923b57f9bbe64705ffe5640ac64d9bdb27"
-  integrity sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==
-  dependencies:
-    lodash "^4.17.10"
+"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.4.4.tgz#a47e02bc91fb259d2e6727c2a30013e3ac13c4a2"
+  integrity sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==
+  dependencies:
+    lodash "^4.17.11"
 
 "@babel/helper-remap-async-to-generator@^7.1.0":
   version "7.1.0"
@@ -171,15 +172,15 @@
     "@babel/traverse" "^7.1.0"
     "@babel/types" "^7.0.0"
 
-"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.2.3":
-  version "7.2.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz#19970020cf22677d62b3a689561dbd9644d8c5e5"
-  integrity sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==
+"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz#aee41783ebe4f2d3ab3ae775e1cc6f1a90cefa27"
+  integrity sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==
   dependencies:
     "@babel/helper-member-expression-to-functions" "^7.0.0"
     "@babel/helper-optimise-call-expression" "^7.0.0"
-    "@babel/traverse" "^7.2.3"
-    "@babel/types" "^7.0.0"
+    "@babel/traverse" "^7.4.4"
+    "@babel/types" "^7.4.4"
 
 "@babel/helper-simple-access@^7.1.0":
   version "7.1.0"
@@ -189,12 +190,12 @@
     "@babel/template" "^7.1.0"
     "@babel/types" "^7.0.0"
 
-"@babel/helper-split-export-declaration@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813"
-  integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==
-  dependencies:
-    "@babel/types" "^7.0.0"
+"@babel/helper-split-export-declaration@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677"
+  integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==
+  dependencies:
+    "@babel/types" "^7.4.4"
 
 "@babel/helper-wrap-function@^7.1.0":
   version "7.2.0"
@@ -206,14 +207,14 @@
     "@babel/traverse" "^7.1.0"
     "@babel/types" "^7.2.0"
 
-"@babel/helpers@^7.2.0":
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.3.1.tgz#949eec9ea4b45d3210feb7dc1c22db664c9e44b9"
-  integrity sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==
-  dependencies:
-    "@babel/template" "^7.1.2"
-    "@babel/traverse" "^7.1.5"
-    "@babel/types" "^7.3.0"
+"@babel/helpers@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.4.tgz#868b0ef59c1dd4e78744562d5ce1b59c89f2f2a5"
+  integrity sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==
+  dependencies:
+    "@babel/template" "^7.4.4"
+    "@babel/traverse" "^7.4.4"
+    "@babel/types" "^7.4.4"
 
 "@babel/highlight@^7.0.0":
   version "7.0.0"
@@ -224,10 +225,10 @@
     esutils "^2.0.2"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.2.3":
-  version "7.3.2"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.2.tgz#95cdeddfc3992a6ca2a1315191c1679ca32c55cd"
-  integrity sha512-QzNUC2RO1gadg+fs21fi0Uu0OuGNzRKEmgCxoLNzbCdoprLwjfmZwzUrpUNfJPaVRwBpDY47A17yYEGWyRelnQ==
+"@babel/parser@^7.0.0", "@babel/parser@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.4.tgz#5977129431b8fe33471730d255ce8654ae1250b6"
+  integrity sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==
 
 "@babel/plugin-proposal-async-generator-functions@^7.2.0":
   version "7.2.0"
@@ -239,19 +240,19 @@
     "@babel/plugin-syntax-async-generators" "^7.2.0"
 
 "@babel/plugin-proposal-class-properties@^7.0.0":
-  version "7.3.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz#272636bc0fa19a0bc46e601ec78136a173ea36cd"
-  integrity sha512-wNHxLkEKTQ2ay0tnsam2z7fGZUi+05ziDJflEt3AZTP3oXLKHJp9HqhfroB/vdMvt3sda9fAbq7FsG8QPDrZBg==
-  dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.3.0"
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz#93a6486eed86d53452ab9bab35e368e9461198ce"
+  integrity sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg==
+  dependencies:
+    "@babel/helper-create-class-features-plugin" "^7.4.4"
     "@babel/helper-plugin-utils" "^7.0.0"
 
 "@babel/plugin-proposal-decorators@^7.1.0":
-  version "7.3.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.3.0.tgz#637ba075fa780b1f75d08186e8fb4357d03a72a7"
-  integrity sha512-3W/oCUmsO43FmZIqermmq6TKaRSYhmh/vybPfVFwQWdSb8xwki38uAIvknCRzuyHRuYfCYmJzL9or1v0AffPjg==
-  dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.3.0"
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.4.tgz#de9b2a1a8ab0196f378e2a82f10b6e2a36f21cc0"
+  integrity sha512-z7MpQz3XC/iQJWXH9y+MaWcLPNSMY9RQSthrLzak8R8hCj0fuyNk+Dzi9kfNe/JxxlWQ2g7wkABbgWjW36MTcw==
+  dependencies:
+    "@babel/helper-create-class-features-plugin" "^7.4.4"
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-syntax-decorators" "^7.2.0"
 
@@ -263,10 +264,10 @@
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-syntax-json-strings" "^7.2.0"
 
-"@babel/plugin-proposal-object-rest-spread@^7.3.1":
-  version "7.3.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.2.tgz#6d1859882d4d778578e41f82cc5d7bf3d5daf6c1"
-  integrity sha512-DjeMS+J2+lpANkYLLO+m6GjoTMygYglKmRe6cDTbFv3L9i6mmiE8fe6B8MtCSLZpVXscD5kn7s6SgtHrDoBWoA==
+"@babel/plugin-proposal-object-rest-spread@^7.3.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz#1ef173fcf24b3e2df92a678f027673b55e7e3005"
+  integrity sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-syntax-object-rest-spread" "^7.2.0"
@@ -280,13 +281,13 @@
     "@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
 
 "@babel/plugin-proposal-unicode-property-regex@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz#abe7281fe46c95ddc143a65e5358647792039520"
-  integrity sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78"
+  integrity sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-regex" "^7.0.0"
-    regexpu-core "^4.2.0"
+    "@babel/helper-regex" "^7.4.4"
+    regexpu-core "^4.5.4"
 
 "@babel/plugin-syntax-async-generators@^7.2.0":
   version "7.2.0"
@@ -344,10 +345,10 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
-"@babel/plugin-transform-async-to-generator@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz#68b8a438663e88519e65b776f8938f3445b1a2ff"
-  integrity sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ==
+"@babel/plugin-transform-async-to-generator@^7.3.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz#a3f1d01f2f21cadab20b33a82133116f14fb5894"
+  integrity sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==
   dependencies:
     "@babel/helper-module-imports" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
@@ -360,26 +361,26 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
-"@babel/plugin-transform-block-scoping@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz#f17c49d91eedbcdf5dd50597d16f5f2f770132d4"
-  integrity sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q==
+"@babel/plugin-transform-block-scoping@^7.3.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz#c13279fabf6b916661531841a23c4b7dae29646d"
+  integrity sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
-    lodash "^4.17.10"
-
-"@babel/plugin-transform-classes@^7.2.0":
-  version "7.2.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.2.tgz#6c90542f210ee975aa2aa8c8b5af7fa73a126953"
-  integrity sha512-gEZvgTy1VtcDOaQty1l10T3jQmJKlNVxLDCs+3rCVPr6nMkODLELxViq5X9l+rfxbie3XrfrMCYYY6eX3aOcOQ==
+    lodash "^4.17.11"
+
+"@babel/plugin-transform-classes@^7.3.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz#0ce4094cdafd709721076d3b9c38ad31ca715eb6"
+  integrity sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==
   dependencies:
     "@babel/helper-annotate-as-pure" "^7.0.0"
-    "@babel/helper-define-map" "^7.1.0"
+    "@babel/helper-define-map" "^7.4.4"
     "@babel/helper-function-name" "^7.1.0"
     "@babel/helper-optimise-call-expression" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-replace-supers" "^7.1.0"
-    "@babel/helper-split-export-declaration" "^7.0.0"
+    "@babel/helper-replace-supers" "^7.4.4"
+    "@babel/helper-split-export-declaration" "^7.4.4"
     globals "^11.1.0"
 
 "@babel/plugin-transform-computed-properties@^7.2.0":
@@ -390,20 +391,20 @@
     "@babel/helper-plugin-utils" "^7.0.0"
 
 "@babel/plugin-transform-destructuring@^7.2.0":
-  version "7.3.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.3.2.tgz#f2f5520be055ba1c38c41c0e094d8a461dd78f2d"
-  integrity sha512-Lrj/u53Ufqxl/sGxyjsJ2XNtNuEjDyjpqdhMNh5aZ+XFOdThL46KBj27Uem4ggoezSYBxKWAil6Hu8HtwqesYw==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz#9d964717829cc9e4b601fc82a26a71a4d8faf20f"
+  integrity sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
 "@babel/plugin-transform-dotall-regex@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz#f0aabb93d120a8ac61e925ea0ba440812dbe0e49"
-  integrity sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz#361a148bc951444312c69446d76ed1ea8e4450c3"
+  integrity sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-regex" "^7.0.0"
-    regexpu-core "^4.1.3"
+    "@babel/helper-regex" "^7.4.4"
+    regexpu-core "^4.5.4"
 
 "@babel/plugin-transform-duplicate-keys@^7.2.0":
   version "7.2.0"
@@ -421,16 +422,16 @@
     "@babel/helper-plugin-utils" "^7.0.0"
 
 "@babel/plugin-transform-for-of@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz#ab7468befa80f764bb03d3cb5eef8cc998e1cad9"
-  integrity sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556"
+  integrity sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
 "@babel/plugin-transform-function-name@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz#f7930362829ff99a3174c39f0afcc024ef59731a"
-  integrity sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz#e1436116abb0610c2259094848754ac5230922ad"
+  integrity sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==
   dependencies:
     "@babel/helper-function-name" "^7.1.0"
     "@babel/helper-plugin-utils" "^7.0.0"
@@ -451,20 +452,20 @@
     "@babel/helper-plugin-utils" "^7.0.0"
 
 "@babel/plugin-transform-modules-commonjs@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz#c4f1933f5991d5145e9cfad1dfd848ea1727f404"
-  integrity sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ==
-  dependencies:
-    "@babel/helper-module-transforms" "^7.1.0"
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz#0bef4713d30f1d78c2e59b3d6db40e60192cac1e"
+  integrity sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==
+  dependencies:
+    "@babel/helper-module-transforms" "^7.4.4"
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/helper-simple-access" "^7.1.0"
 
-"@babel/plugin-transform-modules-systemjs@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.2.0.tgz#912bfe9e5ff982924c81d0937c92d24994bb9068"
-  integrity sha512-aYJwpAhoK9a+1+O625WIjvMY11wkB/ok0WClVwmeo3mCjcNRjt+/8gHWrB5i+00mUju0gWsBkQnPpdvQ7PImmQ==
-  dependencies:
-    "@babel/helper-hoist-variables" "^7.0.0"
+"@babel/plugin-transform-modules-systemjs@^7.3.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz#dc83c5665b07d6c2a7b224c00ac63659ea36a405"
+  integrity sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ==
+  dependencies:
+    "@babel/helper-hoist-variables" "^7.4.4"
     "@babel/helper-plugin-utils" "^7.0.0"
 
 "@babel/plugin-transform-modules-umd@^7.2.0":
@@ -476,16 +477,16 @@
     "@babel/helper-plugin-utils" "^7.0.0"
 
 "@babel/plugin-transform-named-capturing-groups-regex@^7.3.0":
-  version "7.3.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.3.0.tgz#140b52985b2d6ef0cb092ef3b29502b990f9cd50"
-  integrity sha512-NxIoNVhk9ZxS+9lSoAQ/LM0V2UEvARLttEHUrRDGKFaAxOYQcrkN/nLRE+BbbicCAvZPl7wMP0X60HsHE5DtQw==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.4.tgz#5611d96d987dfc4a3a81c4383bb173361037d68d"
+  integrity sha512-Ki+Y9nXBlKfhD+LXaRS7v95TtTGYRAf9Y1rTDiE75zf8YQz4GDaWRXosMfJBXxnk88mGFjWdCRIeqDbon7spYA==
   dependencies:
     regexp-tree "^0.1.0"
 
 "@babel/plugin-transform-new-target@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz#ae8fbd89517fa7892d20e6564e641e8770c3aa4a"
-  integrity sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz#18d120438b0cc9ee95a47f2c72bc9768fbed60a5"
+  integrity sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
@@ -498,25 +499,25 @@
     "@babel/helper-replace-supers" "^7.1.0"
 
 "@babel/plugin-transform-parameters@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz#0d5ad15dc805e2ea866df4dd6682bfe76d1408c2"
-  integrity sha512-kB9+hhUidIgUoBQ0MsxMewhzr8i60nMa2KgeJKQWYrqQpqcBYtnpR+JgkadZVZoaEZ/eKu9mclFaVwhRpLNSzA==
-  dependencies:
-    "@babel/helper-call-delegate" "^7.1.0"
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz#7556cf03f318bd2719fe4c922d2d808be5571e16"
+  integrity sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==
+  dependencies:
+    "@babel/helper-call-delegate" "^7.4.4"
     "@babel/helper-get-function-arity" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
 
-"@babel/plugin-transform-regenerator@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz#5b41686b4ed40bef874d7ed6a84bdd849c13e0c1"
-  integrity sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw==
-  dependencies:
-    regenerator-transform "^0.13.3"
-
-"@babel/plugin-transform-runtime@^7.0.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.2.0.tgz#566bc43f7d0aedc880eaddbd29168d0f248966ea"
-  integrity sha512-jIgkljDdq4RYDnJyQsiWbdvGeei/0MOTtSHKO/rfbd/mXBxNpdlulMx49L0HQ4pug1fXannxoqCI+fYSle9eSw==
+"@babel/plugin-transform-regenerator@^7.3.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.4.tgz#5b4da4df79391895fca9e28f99e87e22cfc02072"
+  integrity sha512-Zz3w+pX1SI0KMIiqshFZkwnVGUhDZzpX2vtPzfJBKQQq8WsP/Xy9DNdELWivxcKOCX/Pywge4SiEaPaLtoDT4g==
+  dependencies:
+    regenerator-transform "^0.13.4"
+
+"@babel/plugin-transform-runtime@^7.4.0":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.4.tgz#a50f5d16e9c3a4ac18a1a9f9803c107c380bce08"
+  integrity sha512-aMVojEjPszvau3NRg+TIH14ynZLvPewH4xhlCW1w6A3rkxTS1m4uwzRclYR9oS+rl/dr+kT+pzbfHuAWP/lc7Q==
   dependencies:
     "@babel/helper-module-imports" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
@@ -546,9 +547,9 @@
     "@babel/helper-regex" "^7.0.0"
 
 "@babel/plugin-transform-template-literals@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz#d87ed01b8eaac7a92473f608c97c089de2ba1e5b"
-  integrity sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz#9d28fea7bbce637fb7612a0750989d8321d4bcb0"
+  integrity sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==
   dependencies:
     "@babel/helper-annotate-as-pure" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
@@ -561,24 +562,24 @@
     "@babel/helper-plugin-utils" "^7.0.0"
 
 "@babel/plugin-transform-unicode-regex@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz#4eb8db16f972f8abb5062c161b8b115546ade08b"
-  integrity sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz#ab4634bb4f14d36728bf5978322b35587787970f"
+  integrity sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-regex" "^7.0.0"
-    regexpu-core "^4.1.3"
-
-"@babel/preset-env@^7.0.0":
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.1.tgz#389e8ca6b17ae67aaf9a2111665030be923515db"
-  integrity sha512-FHKrD6Dxf30e8xgHQO0zJZpUPfVZg+Xwgz5/RdSWCbza9QLNk4Qbp40ctRoqDxml3O8RMzB1DU55SXeDG6PqHQ==
+    "@babel/helper-regex" "^7.4.4"
+    regexpu-core "^4.5.4"
+
+"@babel/preset-env@^7.0.0 < 7.4.0":
+  version "7.3.4"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.4.tgz#887cf38b6d23c82f19b5135298bdb160062e33e1"
+  integrity sha512-2mwqfYMK8weA0g0uBKOt4FE3iEodiHy9/CW0b+nWXcbL+pGzLx8ESYc+j9IIxr6LTDHWKgPm71i9smo02bw+gA==
   dependencies:
     "@babel/helper-module-imports" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-proposal-async-generator-functions" "^7.2.0"
     "@babel/plugin-proposal-json-strings" "^7.2.0"
-    "@babel/plugin-proposal-object-rest-spread" "^7.3.1"
+    "@babel/plugin-proposal-object-rest-spread" "^7.3.4"
     "@babel/plugin-proposal-optional-catch-binding" "^7.2.0"
     "@babel/plugin-proposal-unicode-property-regex" "^7.2.0"
     "@babel/plugin-syntax-async-generators" "^7.2.0"
@@ -586,10 +587,10 @@
     "@babel/plugin-syntax-object-rest-spread" "^7.2.0"
     "@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
     "@babel/plugin-transform-arrow-functions" "^7.2.0"
-    "@babel/plugin-transform-async-to-generator" "^7.2.0"
+    "@babel/plugin-transform-async-to-generator" "^7.3.4"
     "@babel/plugin-transform-block-scoped-functions" "^7.2.0"
-    "@babel/plugin-transform-block-scoping" "^7.2.0"
-    "@babel/plugin-transform-classes" "^7.2.0"
+    "@babel/plugin-transform-block-scoping" "^7.3.4"
+    "@babel/plugin-transform-classes" "^7.3.4"
     "@babel/plugin-transform-computed-properties" "^7.2.0"
     "@babel/plugin-transform-destructuring" "^7.2.0"
     "@babel/plugin-transform-dotall-regex" "^7.2.0"
@@ -600,13 +601,13 @@
     "@babel/plugin-transform-literals" "^7.2.0"
     "@babel/plugin-transform-modules-amd" "^7.2.0"
     "@babel/plugin-transform-modules-commonjs" "^7.2.0"
-    "@babel/plugin-transform-modules-systemjs" "^7.2.0"
+    "@babel/plugin-transform-modules-systemjs" "^7.3.4"
     "@babel/plugin-transform-modules-umd" "^7.2.0"
     "@babel/plugin-transform-named-capturing-groups-regex" "^7.3.0"
     "@babel/plugin-transform-new-target" "^7.0.0"
     "@babel/plugin-transform-object-super" "^7.2.0"
     "@babel/plugin-transform-parameters" "^7.2.0"
-    "@babel/plugin-transform-regenerator" "^7.0.0"
+    "@babel/plugin-transform-regenerator" "^7.3.4"
     "@babel/plugin-transform-shorthand-properties" "^7.2.0"
     "@babel/plugin-transform-spread" "^7.2.0"
     "@babel/plugin-transform-sticky-regex" "^7.2.0"
@@ -619,90 +620,90 @@
     semver "^5.3.0"
 
 "@babel/runtime-corejs2@^7.2.0":
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.3.1.tgz#0c113242e2328f9674d42703a89bee6ebebe9a82"
-  integrity sha512-YpO13776h3e6Wy8dl2J8T9Qwlvopr+b4trCEhHE+yek6yIqV8sx6g3KozdHMbXeBpjosbPi+Ii5Z7X9oXFHUKA==
-  dependencies:
-    core-js "^2.5.7"
-    regenerator-runtime "^0.12.0"
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.4.4.tgz#4d4519a4c85e9d98fdff59f5371758a34ae07923"
+  integrity sha512-hE7oVwVsRws84u5/nkaWWdN2J4SXEGuXKjrAsP0E4nkYImjSbpdHfGTS2nvFc82aDGIuG6OzhAQMpIzTHuZeKA==
+  dependencies:
+    core-js "^2.6.5"
+    regenerator-runtime "^0.13.2"
 
 "@babel/runtime@^7.0.0":
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a"
-  integrity sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==
-  dependencies:
-    regenerator-runtime "^0.12.0"
-
-"@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2":
-  version "7.2.2"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907"
-  integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.4.tgz#dc2e34982eb236803aa27a07fea6857af1b9171d"
+  integrity sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg==
+  dependencies:
+    regenerator-runtime "^0.13.2"
+
+"@babel/template@^7.1.0", "@babel/template@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237"
+  integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==
   dependencies:
     "@babel/code-frame" "^7.0.0"
-    "@babel/parser" "^7.2.2"
-    "@babel/types" "^7.2.2"
-
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.2.2", "@babel/traverse@^7.2.3":
-  version "7.2.3"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8"
-  integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==
+    "@babel/parser" "^7.4.4"
+    "@babel/types" "^7.4.4"
+
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.4.tgz#0776f038f6d78361860b6823887d4f3937133fe8"
+  integrity sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==
   dependencies:
     "@babel/code-frame" "^7.0.0"
-    "@babel/generator" "^7.2.2"
+    "@babel/generator" "^7.4.4"
     "@babel/helper-function-name" "^7.1.0"
-    "@babel/helper-split-export-declaration" "^7.0.0"
-    "@babel/parser" "^7.2.3"
-    "@babel/types" "^7.2.2"
+    "@babel/helper-split-export-declaration" "^7.4.4"
+    "@babel/parser" "^7.4.4"
+    "@babel/types" "^7.4.4"
     debug "^4.1.0"
     globals "^11.1.0"
-    lodash "^4.17.10"
-
-"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.2":
-  version "7.3.2"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.2.tgz#424f5be4be633fff33fb83ab8d67e4a8290f5a2f"
-  integrity sha512-3Y6H8xlUlpbGR+XvawiH0UXehqydTmNmEpozWcXymqwcrwYAl5KMvKtQ+TF6f6E08V6Jur7v/ykdDSF+WDEIXQ==
+    lodash "^4.17.11"
+
+"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0"
+  integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==
   dependencies:
     esutils "^2.0.2"
-    lodash "^4.17.10"
+    lodash "^4.17.11"
     to-fast-properties "^2.0.0"
 
-"@fortawesome/fontawesome-common-types@^0.2.14":
-  version "0.2.14"
-  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.14.tgz#0eb86a77ac88e8c12c48591735283f0bf0ea5606"
-  integrity sha512-LOcvERCI96KioXSIfOYN4IATv2ROJOyf72dYnpoWfOIkuyLp45oMej1kL03kYdzvktLCzYhykgHwiu6nkg9Xbw==
+"@fortawesome/fontawesome-common-types@^0.2.17":
+  version "0.2.17"
+  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.17.tgz#d8c36e6f6f3b3415fa1f83eaffe4f41bd313715c"
+  integrity sha512-DEYsEb/iiGVoMPQGjhG2uOylLVuMzTxOxysClkabZ5n80Q3oFDWGnshCLKvOvKoeClsgmKhWVrnnqvsMI1cAbw==
 
 "@fortawesome/fontawesome-svg-core@^1.2.8":
-  version "1.2.14"
-  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.14.tgz#48fde5fbfa223cffd5fac9c0402cfbe87605a7d0"
-  integrity sha512-T1qCqkwm9PuvK53J64D1ovfrOTa1kG+SrHNj5cFst/rrskhCnbxpRdbqFIdc/thmXC0ebBX8nOUyja2/mrxe4g==
-  dependencies:
-    "@fortawesome/fontawesome-common-types" "^0.2.14"
+  version "1.2.17"
+  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.17.tgz#8fce4402e824ebe99a04b1949d56d696eeae2e6d"
+  integrity sha512-TORMW/wIX2QyyGBd4XwHGPir4/0U18Wxf+iDBAUW3EIJ0/VC/ZMpJOiyiCe1f8g9h0PPzA7sqVtl8JtTUtm4uA==
+  dependencies:
+    "@fortawesome/fontawesome-common-types" "^0.2.17"
 
 "@fortawesome/free-brands-svg-icons@^5.5.0":
-  version "5.7.1"
-  resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.7.1.tgz#20711fe4d6a459161d052171169baa175b90fca1"
-  integrity sha512-YU+np8UJGjHUmzfGS5yyK0wWR0QHbx5lTFRSylBfEkm8QXvOkRxB03sUhOSIWVXU7iPiePuqrsglQRgxoG4nrw==
-  dependencies:
-    "@fortawesome/fontawesome-common-types" "^0.2.14"
+  version "5.8.1"
+  resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.8.1.tgz#8f188a4699adb31e505abca64740d7046222b9a1"
+  integrity sha512-NN5Nap2D5e7Lusa5uarAUkcaO7PMbme5wmUF8kofZzPUZR753zDg/UFffi+LLE2Mi9zRXCJEYmIRfMON9SxLPg==
+  dependencies:
+    "@fortawesome/fontawesome-common-types" "^0.2.17"
 
 "@fortawesome/free-regular-svg-icons@^5.5.0":
-  version "5.7.1"
-  resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.7.1.tgz#eab182769eef910f961ed4c3a581ebd9934b4b38"
-  integrity sha512-JFLJ4M11lZEfi+bmfJdWGVUe5fvmr5k/bqshN7VbJZvEJ6i12Yr6uaByQUM0U1tgw+hJkd8xAwVvKxpJ2HDVTA==
-  dependencies:
-    "@fortawesome/fontawesome-common-types" "^0.2.14"
+  version "5.8.1"
+  resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.8.1.tgz#9477a973fe3f681f871375fa95ff32a87dcb5111"
+  integrity sha512-U+tFjDyQpVdD0UPWoKRBVLhh0J1/q3iaWDrnxNMJKuKRmerc4d0jfiZdM2X7agOTcG7amvcllRBiWCu2FwYlMA==
+  dependencies:
+    "@fortawesome/fontawesome-common-types" "^0.2.17"
 
 "@fortawesome/free-solid-svg-icons@^5.5.0":
-  version "5.7.1"
-  resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.7.1.tgz#df41b8643383862a2af93456e7129e5ffc0fb7ae"
-  integrity sha512-5V/Q+JoPzuiIHW2JwmZGvE9bHguvNJKa7611DPo51uIvYv9LweX/SnDF+HC23X2W5T3myHhnGi+EZJTmidAmyg==
-  dependencies:
-    "@fortawesome/fontawesome-common-types" "^0.2.14"
+  version "5.8.1"
+  resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.8.1.tgz#086c70f95b34a4bcf6f50ff1078d46e53486eb52"
+  integrity sha512-FUcxR75PtMOo3ihRHJOZz64IsWIVdWgB2vCMLJjquTv487wVVCMH5H5gWa72et2oI9lKKD2jvjQ+y+7mxhscVQ==
+  dependencies:
+    "@fortawesome/fontawesome-common-types" "^0.2.17"
 
 "@fortawesome/vue-fontawesome@^0.1.2":
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-0.1.5.tgz#ea70a4d4ad0d6d617048a5b2a2ff33e21fe47d61"
-  integrity sha512-tiNZCgh+ZkUsyFfm2MQMMdHKRrKj82M7g0XFPSNNY+s5nRB82soy0US+xj0jGRy433b0c4WpylCOhgle3294Uw==
+  version "0.1.6"
+  resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-0.1.6.tgz#18a0f4263b90f65180fc927325ba01896276ea04"
+  integrity sha512-HAGRbrOuGDwwUmCYdpzR0hhNQ3EE30dOS4JiJKcoZ+S4M210CxyU0OXCgzIg3HzK/23rlpHbV8zi9PDDZDnuIw==
 
 "@intervolga/optimize-cssnano-plugin@^1.0.5":
   version "1.0.6"
@@ -962,41 +963,55 @@
     "@turf/meta" "^5.1.5"
 
 "@types/babel-types@*", "@types/babel-types@^7.0.0":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.4.tgz#bfd5b0d0d1ba13e351dff65b6e52783b816826c8"
-  integrity sha512-WiZhq3SVJHFRgRYLXvpf65XnV6ipVHhnNaNvE8yCimejrGglkg38kEj0JcizqwSHxmPSjcTlig/6JouxLGEhGw==
+  version "7.0.7"
+  resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.7.tgz#667eb1640e8039436028055737d2b9986ee336e3"
+  integrity sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ==
 
 "@types/babylon@^6.16.2":
-  version "6.16.4"
-  resolved "https://registry.yarnpkg.com/@types/babylon/-/babylon-6.16.4.tgz#d3df72518b34a6a015d0dc58745cd238b5bb8ad2"
-  integrity sha512-8dZMcGPno3g7pJ/d0AyJERo+lXh9i1JhDuCUs+4lNIN9eUe5Yh6UCLrpgSEi05Ve2JMLauL2aozdvKwNL0px1Q==
+  version "6.16.5"
+  resolved "https://registry.yarnpkg.com/@types/babylon/-/babylon-6.16.5.tgz#1c5641db69eb8cdf378edd25b4be7754beeb48b4"
+  integrity sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==
   dependencies:
     "@types/babel-types" "*"
 
+"@types/events@*":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
+  integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
+
+"@types/glob@^7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
+  integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
+  dependencies:
+    "@types/events" "*"
+    "@types/minimatch" "*"
+    "@types/node" "*"
+
+"@types/minimatch@*":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
+  integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
+
 "@types/node@*":
-  version "11.9.0"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.0.tgz#35fea17653490dab82e1d5e69731abfdbf13160d"
-  integrity sha512-ry4DOrC+xenhQbzk1iIPzCZGhhPGEFv7ia7Iu6XXSLVluiJIe9FfG7Iu3mObH9mpxEXCWLCMU4JWbCCR9Oy1Zg==
-
-"@types/node@^10.11.7":
-  version "10.12.25"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.25.tgz#0d01a7dd6127de60d861ece4a650963042abb538"
-  integrity sha512-IcvnGLGSQFDvC07Bz2I8SX+QKErDZbUdiQq7S2u3XyzTyJfUmT0sWJMbeQkMzpTAkO7/N7sZpW/arUM2jfKsbQ==
+  version "12.0.0"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.0.tgz#d11813b9c0ff8aaca29f04cbc12817f4c7d656e5"
+  integrity sha512-Jrb/x3HT4PTJp6a4avhmJCDEVrPdqLfl3e8GGMbpkGGdwAV5UGlIs4vVEfsHHfylZVOKZWpOqmqFH8CbfOZ6kg==
 
 "@types/node@^8.0.7":
-  version "8.10.40"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.40.tgz#4314888d5cd537945d73e9ce165c04cc550144a4"
-  integrity sha512-RRSjdwz63kS4u7edIwJUn8NqKLLQ6LyqF/X4+4jp38MBT3Vwetewi2N4dgJEshLbDwNgOJXNYoOwzVZUSSLhkQ==
+  version "8.10.48"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.48.tgz#e385073561643a9ba6199a1985ffc03530f90781"
+  integrity sha512-c35YEBTkL4rzXY2ucpSKy+UYHjUBIIkuJbWYbsGIrKLEWU5dgJMmLkkIb3qeC3O3Tpb1ZQCwecscvJTDjDjkRw==
+
+"@types/normalize-package-data@^2.4.0":
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
+  integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
 
 "@types/q@^1.5.1":
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.1.tgz#48fd98c1561fe718b61733daed46ff115b496e18"
-  integrity sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA==
-
-"@types/semver@^5.5.0":
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
-  integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
+  version "1.5.2"
+  resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
+  integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
 
 "@types/strip-bom@^3.0.0":
   version "3.0.0"
@@ -1008,202 +1023,211 @@
   resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
   integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
 
-"@vue/babel-helper-vue-jsx-merge-props@^1.0.0-beta.2":
-  version "1.0.0-beta.2"
-  resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0-beta.2.tgz#f3e20d77b89ddb7a4b9b7a75372f05cd3ac22d92"
-  integrity sha512-Yj92Q1GcGjjctecBfnBmVqKSlMdyZaVq10hlZB4HSd1DJgu4cWgpEImJSzcJRUCZmas6UigwE7f4IjJuQs+JvQ==
-
-"@vue/babel-plugin-transform-vue-jsx@^1.0.0-beta.2":
-  version "1.0.0-beta.2"
-  resolved "https://registry.yarnpkg.com/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.0.0-beta.2.tgz#6f7903fe66a34a02163f418c426cf419e862d97e"
-  integrity sha512-fvAymRZAPHitomRE+jIipWRj0STXNSMqeOSdOFu9Ffjqg9WGOxSdCjORxexManfZ2y5QDv7gzI1xfgprsK3nlw==
+"@vue/babel-helper-vue-jsx-merge-props@^1.0.0-beta.3":
+  version "1.0.0-beta.3"
+  resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0-beta.3.tgz#e4c2e7125b3e0d2a9d493e457850b2abb0fd3cad"
+  integrity sha512-cbFQnd3dDPsfWuxbWW2phynX2zsckwC4GfAkcE1QH1lZL2ZAD2V97xY3BmvTowMkjeFObRKQt1P3KKA6AoB0hQ==
+
+"@vue/babel-plugin-transform-vue-jsx@^1.0.0-beta.3":
+  version "1.0.0-beta.3"
+  resolved "https://registry.yarnpkg.com/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.0.0-beta.3.tgz#a1a44e801d8ed615e49f145ef1b3eaca2c16e2e6"
+  integrity sha512-yn+j2B/2aEagaxXrMSK3qcAJnlidfXg9v+qmytqrjUXc4zfi8QVC/b4zCev1FDmTip06/cs/csENA4law6Xhpg==
   dependencies:
     "@babel/helper-module-imports" "^7.0.0"
     "@babel/plugin-syntax-jsx" "^7.2.0"
-    "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0-beta.2"
+    "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0-beta.3"
     html-tags "^2.0.0"
     lodash.kebabcase "^4.1.1"
     svg-tags "^1.0.0"
 
-"@vue/babel-preset-app@^3.4.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-3.4.0.tgz#926c718066babc0117ee70cebf890d3aaa812bfa"
-  integrity sha512-P7IaOFtMUd5iic2PH/iY6YPgtPnyd7SzA+ACv1283F5RcLutTURhl2smC1cWUJFGVrUhTmsYEcbS4+06wKymWw==
-  dependencies:
+"@vue/babel-preset-app@^3.7.0":
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-3.7.0.tgz#f37535ea60b71732ddd4395ec143aaa0b10d4c67"
+  integrity sha512-6PHZ1TYO8OGy22TLyKm/+VmCzLB9L1UxaA3CFxXJH0h/YUOmgdmuAk3AWhomYSwk2GF51On3aQzYouoaWhvBDQ==
+  dependencies:
+    "@babel/helper-module-imports" "^7.0.0"
     "@babel/plugin-proposal-class-properties" "^7.0.0"
     "@babel/plugin-proposal-decorators" "^7.1.0"
     "@babel/plugin-syntax-dynamic-import" "^7.0.0"
     "@babel/plugin-syntax-jsx" "^7.0.0"
-    "@babel/plugin-transform-runtime" "^7.0.0"
-    "@babel/preset-env" "^7.0.0"
+    "@babel/plugin-transform-runtime" "^7.4.0"
+    "@babel/preset-env" "^7.0.0 < 7.4.0"
     "@babel/runtime" "^7.0.0"
     "@babel/runtime-corejs2" "^7.2.0"
-    "@vue/babel-preset-jsx" "^1.0.0-beta.2"
+    "@vue/babel-preset-jsx" "^1.0.0-beta.3"
     babel-plugin-dynamic-import-node "^2.2.0"
-    core-js "^2.6.3"
-
-"@vue/babel-preset-jsx@^1.0.0-beta.2":
-  version "1.0.0-beta.2"
-  resolved "https://registry.yarnpkg.com/@vue/babel-preset-jsx/-/babel-preset-jsx-1.0.0-beta.2.tgz#3e5dc2b73da58391c1c7327c2bd2ef154fe4e46e"
-  integrity sha512-nZoAKBR/h6iPMQ66ieQcIdlpPBmqhtUUcgjBS541jIVxSog1rwzrc00jlsuecLonzUMWPU0PabyitsG74vhN1w==
-  dependencies:
-    "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0-beta.2"
-    "@vue/babel-plugin-transform-vue-jsx" "^1.0.0-beta.2"
-    "@vue/babel-sugar-functional-vue" "^1.0.0-beta.2"
-    "@vue/babel-sugar-inject-h" "^1.0.0-beta.2"
-    "@vue/babel-sugar-v-model" "^1.0.0-beta.2"
-    "@vue/babel-sugar-v-on" "^1.0.0-beta.2"
-
-"@vue/babel-sugar-functional-vue@^1.0.0-beta.2":
-  version "1.0.0-beta.2"
-  resolved "https://registry.yarnpkg.com/@vue/babel-sugar-functional-vue/-/babel-sugar-functional-vue-1.0.0-beta.2.tgz#8831f686e7614f282d5170b902483ef538deef38"
-  integrity sha512-5qvi4hmExgjtrESDk0vflL69dIxkDAukJcYH9o4663E8Nh12Jpbmr+Ja8WmgkAPtTVhk90UVcVUFCCZLHBmhkQ==
+    babel-plugin-module-resolver "3.2.0"
+    core-js "^2.6.5"
+
+"@vue/babel-preset-jsx@^1.0.0-beta.3":
+  version "1.0.0-beta.3"
+  resolved "https://registry.yarnpkg.com/@vue/babel-preset-jsx/-/babel-preset-jsx-1.0.0-beta.3.tgz#15c584bd62c0286a80f0196749ae38cde5cd703b"
+  integrity sha512-qMKGRorTI/0nE83nLEM7MyQiBZUqc62sZyjkBdVaaU7S61MHI8RKHPtbLMMZlWXb2NCJ0fQci8xJWUK5JE+TFA==
+  dependencies:
+    "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0-beta.3"
+    "@vue/babel-plugin-transform-vue-jsx" "^1.0.0-beta.3"
+    "@vue/babel-sugar-functional-vue" "^1.0.0-beta.3"
+    "@vue/babel-sugar-inject-h" "^1.0.0-beta.3"
+    "@vue/babel-sugar-v-model" "^1.0.0-beta.3"
+    "@vue/babel-sugar-v-on" "^1.0.0-beta.3"
+
+"@vue/babel-sugar-functional-vue@^1.0.0-beta.3":
+  version "1.0.0-beta.3"
+  resolved "https://registry.yarnpkg.com/@vue/babel-sugar-functional-vue/-/babel-sugar-functional-vue-1.0.0-beta.3.tgz#41a855786971dacbbe8044858eefe98de089bf12"
+  integrity sha512-CBIa0sQWn3vfBS2asfTgv0WwdyKvNTKtE/cCfulZ7MiewLBh0RlvvSmdK9BIMTiHErdeZNSGUGlU6JuSHLyYkQ==
   dependencies:
     "@babel/plugin-syntax-jsx" "^7.2.0"
 
-"@vue/babel-sugar-inject-h@^1.0.0-beta.2":
-  version "1.0.0-beta.2"
-  resolved "https://registry.yarnpkg.com/@vue/babel-sugar-inject-h/-/babel-sugar-inject-h-1.0.0-beta.2.tgz#5f92f994bf4b4126fad8633f554e8a426b51b413"
-  integrity sha512-qGXZ6yE+1trk82xCVJ9j3shsgI+R2ePj3+o8b2Ee7JNaRqQvMfTwpgx5BRlk4q1+CTjvYexdqBS+q4Kg7sSxcg==
+"@vue/babel-sugar-inject-h@^1.0.0-beta.3":
+  version "1.0.0-beta.3"
+  resolved "https://registry.yarnpkg.com/@vue/babel-sugar-inject-h/-/babel-sugar-inject-h-1.0.0-beta.3.tgz#be1d00b74a1a89fed35a9b1415a738c36f125966"
+  integrity sha512-HKMBMmFfdK9GBp3rX2bHIwILBdgc5F3ahmCB72keJxzaAQrgDAnD+ho70exUge+inAGlNF34WsQcGPElTf9QZg==
   dependencies:
     "@babel/plugin-syntax-jsx" "^7.2.0"
 
-"@vue/babel-sugar-v-model@^1.0.0-beta.2":
-  version "1.0.0-beta.2"
-  resolved "https://registry.yarnpkg.com/@vue/babel-sugar-v-model/-/babel-sugar-v-model-1.0.0-beta.2.tgz#051d3ae3ef5e70d514e09058ec5790f6a42e8c28"
-  integrity sha512-63US3IMEtATJzzK2le/Na53Sk2bp3LHfwZ8eMFwbTaz6e2qeV9frBl3ZYaha64ghT4IDSbrDXUmm0J09EAzFfA==
+"@vue/babel-sugar-v-model@^1.0.0-beta.3":
+  version "1.0.0-beta.3"
+  resolved "https://registry.yarnpkg.com/@vue/babel-sugar-v-model/-/babel-sugar-v-model-1.0.0-beta.3.tgz#ea935b0e08bf58c125a1349b819156059590993c"
+  integrity sha512-et39eTEh7zW4wfZoSl9Jf0/n2r9OTT8U02LtSbXsjgYcqaDQFusN0+n7tw4bnOqvnnSVjEp7bVsQCWwykC3Wgg==
   dependencies:
     "@babel/plugin-syntax-jsx" "^7.2.0"
-    "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0-beta.2"
-    "@vue/babel-plugin-transform-vue-jsx" "^1.0.0-beta.2"
+    "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0-beta.3"
+    "@vue/babel-plugin-transform-vue-jsx" "^1.0.0-beta.3"
     camelcase "^5.0.0"
     html-tags "^2.0.0"
     svg-tags "^1.0.0"
 
-"@vue/babel-sugar-v-on@^1.0.0-beta.2":
-  version "1.0.0-beta.2"
-  resolved "https://registry.yarnpkg.com/@vue/babel-sugar-v-on/-/babel-sugar-v-on-1.0.0-beta.2.tgz#3e2d122e229b10017f091d178346b601d9245260"
-  integrity sha512-XH/m3k11EKdMY0MrTg4+hQv8BFM8juzHT95chYkgxDmvDdVJnSCuf9+mcysEJttWD4PVuUGN7EHoIWsIhC0dRw==
+"@vue/babel-sugar-v-on@^1.0.0-beta.3":
+  version "1.0.0-beta.3"
+  resolved "https://registry.yarnpkg.com/@vue/babel-sugar-v-on/-/babel-sugar-v-on-1.0.0-beta.3.tgz#2f5fedb43883f603fe76010f253b85c7465855fe"
+  integrity sha512-F+GapxCiy50jf2Q2B4exw+KYBzlGdeKMAMW1Dbvb0Oa59SA0CH6tsUOIAsXb0A05jwwg/of0LaVeo+4aLefVxQ==
   dependencies:
     "@babel/plugin-syntax-jsx" "^7.2.0"
-    "@vue/babel-plugin-transform-vue-jsx" "^1.0.0-beta.2"
+    "@vue/babel-plugin-transform-vue-jsx" "^1.0.0-beta.3"
     camelcase "^5.0.0"
 
-"@vue/cli-overlay@^3.4.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-3.4.0.tgz#7fe7cf41eacbaf1f1579efcad93e23b65d4581db"
-  integrity sha512-uLfQZvMChAf3UQNR+WN8a7vAPqvaw2tJs1TrNxPg+Dr7bm7HWoitvFremF0vLWkxIRM5e+VJgYV3wHk9EwWhzg==
+"@vue/cli-overlay@^3.7.0":
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-3.7.0.tgz#0f520c98e1be7618b7a68b768666fffa1f589f94"
+  integrity sha512-QO1rsBVKPZrt+5rHSZXc5UEPVwVgiayOk/cDl+GwSJoR36gnWs1wy1oUX1Awd7QpGiMBK/1+A7aAGhfzKR23Cg==
 
 "@vue/cli-plugin-babel@^3.2.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-3.4.0.tgz#d6e7967995f860b94204bdfb17900fadd4aea2d9"
-  integrity sha512-8ViOzJa8UqUnmMl1422t8EIlCUc5PegSMsdU6xoqfavL83uEGjR+fE4gAI+g7xKo7Qk9+8Z8VvaredXMbmxCzA==
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-3.7.0.tgz#2be01288980b058f097d26812f65d4d4e8136cca"
+  integrity sha512-QysJYerzaGzvJ5iT61KpE4hFHiDU8NQ7QjSwIkOAJAx0KY8o0WCjLpAVvjmKtZqNXPBc5Jc3P+eeaz9qQPWNeQ==
   dependencies:
     "@babel/core" "^7.0.0"
-    "@vue/babel-preset-app" "^3.4.0"
-    "@vue/cli-shared-utils" "^3.4.0"
+    "@vue/babel-preset-app" "^3.7.0"
+    "@vue/cli-shared-utils" "^3.7.0"
     babel-loader "^8.0.5"
     webpack ">=4 < 4.29"
 
 "@vue/cli-plugin-e2e-nightwatch@^3.2.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-e2e-nightwatch/-/cli-plugin-e2e-nightwatch-3.4.0.tgz#0726fa46ccb2735adc3fd1cdf1ef43c79aaf56b1"
-  integrity sha512-XIL6NKD/N4ZhbniAZLYB46OmzAg+ibAlUg3W1JURpoBBs9+EP6XV+vzI9lUaS8JNU3Cuj45PqB455fDwUrHt1g==
-  dependencies:
-    "@vue/cli-shared-utils" "^3.4.0"
-    chromedriver "^2.45.0"
-    deepmerge "^3.1.0"
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-e2e-nightwatch/-/cli-plugin-e2e-nightwatch-3.7.0.tgz#3a6ed55eb057a9a328d52faf7a4920055cd1333c"
+  integrity sha512-mjxjfYko3/tamdCcPZTabaYnhiC2HuEXc+AXt+ek/m054ZOEysRhqWgbAOHqh5PPqcaytSIuVvGtJelp7IVwDQ==
+  dependencies:
+    "@vue/cli-shared-utils" "^3.7.0"
+    chromedriver "^2.46.0"
+    deepmerge "^3.2.0"
     execa "^1.0.0"
     nightwatch "^0.9.21"
     selenium-server "^3.141.59"
 
 "@vue/cli-plugin-eslint@^3.2.1":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-3.4.0.tgz#e9fceff873661848be7e1341b31dad23302cd0ac"
-  integrity sha512-KbUpN3Zd/V5zCah9nT9cukTHmd9g4IRskyuIeBw5KZqRDoUgCS7I2+OWlcAMneRuqZwgFbTFYmr9N3s6gz4SVg==
-  dependencies:
-    "@vue/cli-shared-utils" "^3.4.0"
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-3.7.0.tgz#6b495fe3c82ec94347c424a9de3cca467a53f90e"
+  integrity sha512-oFdOLQu6PQKbxinF55XH1lH8hgiDRyb3gIvSKu5YV5r6dnsRdKDxOKLE1PTbaZzQot3Ny/Y7gk025x1qpni3IA==
+  dependencies:
+    "@vue/cli-shared-utils" "^3.7.0"
     babel-eslint "^10.0.1"
+    eslint-loader "^2.1.2"
+    globby "^9.2.0"
+    webpack ">=4 < 4.29"
+  optionalDependencies:
     eslint "^4.19.1"
-    eslint-loader "^2.1.1"
     eslint-plugin-vue "^4.7.1"
-    globby "^9.0.0"
-    webpack ">=4 < 4.29"
 
 "@vue/cli-plugin-unit-jest@^3.2.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-unit-jest/-/cli-plugin-unit-jest-3.4.0.tgz#f501d16344aef5d7c83d9a8821dc372d2a362afe"
-  integrity sha512-F1FzKG2JQmVPXH5OKFN4htBkGERDj5Kxd47Wmts2H0rhmtHR4a+k0X7+WyCzbb1aSRKNYdG4f2eSwyu6tSq28A==
-  dependencies:
-    "@vue/cli-shared-utils" "^3.4.0"
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-unit-jest/-/cli-plugin-unit-jest-3.7.0.tgz#94cd7928f7f9e134ee32e6621742be8cdf898c9f"
+  integrity sha512-3z8yCAhgwsUc6hpghN8Ej5xBGIaxQTC/g3Ry5QPjjZ4up4G3lKukzvwMk7JFzO+Qj+mt4xAbhR9+stOI4Qyk/Q==
+  dependencies:
+    "@vue/cli-shared-utils" "^3.7.0"
     babel-jest "^23.6.0"
     babel-plugin-transform-es2015-modules-commonjs "^6.26.2"
     jest "^23.6.0"
     jest-serializer-vue "^2.0.2"
-    jest-transform-stub "^1.0.0"
-    vue-jest "^3.0.2"
+    jest-transform-stub "^2.0.0"
+    jest-watch-typeahead "0.2.1"
+    vue-jest "^3.0.4"
 
 "@vue/cli-service@^3.2.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-3.4.0.tgz#d2160ee4cf9de8dc4e9d780ccd61cc15862f2950"
-  integrity sha512-AtLiin5Jlw0ULKXJtBhUaykz0VzDgYq2RCf7nxfB7Vsi5fTbJyOVeWYe9KsnsM6VTRBWRUI8NzPPMYxV2uxtQA==
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-3.7.0.tgz#af56526cea64042b48c50a15a9d33c84a71abd31"
+  integrity sha512-RMVwpCE3EB9cL9VAgu1Dy/tGxz5zrVG4UMPk5t4KDu8jJhHxvcAzgIEIfS6KRp0AKfA6iDW4J0NU0fopnpyL+g==
   dependencies:
     "@intervolga/optimize-cssnano-plugin" "^1.0.5"
     "@soda/friendly-errors-webpack-plugin" "^1.7.1"
-    "@vue/cli-overlay" "^3.4.0"
-    "@vue/cli-shared-utils" "^3.4.0"
-    "@vue/component-compiler-utils" "^2.5.2"
+    "@vue/cli-overlay" "^3.7.0"
+    "@vue/cli-shared-utils" "^3.7.0"
+    "@vue/component-compiler-utils" "^2.6.0"
     "@vue/preload-webpack-plugin" "^1.1.0"
     "@vue/web-component-wrapper" "^1.2.0"
-    acorn "^6.0.6"
+    acorn "^6.1.1"
     acorn-walk "^6.1.1"
     address "^1.0.3"
-    autoprefixer "^9.4.7"
+    autoprefixer "^9.5.1"
+    browserslist "^4.5.4"
     cache-loader "^2.0.1"
     case-sensitive-paths-webpack-plugin "^2.2.0"
     chalk "^2.4.2"
-    clipboardy "^1.2.3"
-    cliui "^4.1.0"
+    cli-highlight "^2.1.0"
+    clipboardy "^2.0.0"
+    cliui "^5.0.0"
     copy-webpack-plugin "^4.6.0"
     css-loader "^1.0.1"
-    cssnano "^4.1.8"
+    cssnano "^4.1.10"
+    current-script-polyfill "^1.0.0"
     debug "^4.1.1"
+    dotenv "^7.0.0"
+    dotenv-expand "^5.1.0"
     escape-string-regexp "^1.0.5"
     file-loader "^3.0.1"
     fs-extra "^7.0.1"
-    globby "^9.0.0"
+    globby "^9.2.0"
     hash-sum "^1.0.2"
     html-webpack-plugin "^3.2.0"
     launch-editor-middleware "^2.2.1"
     lodash.defaultsdeep "^4.6.0"
     lodash.mapvalues "^4.6.0"
     lodash.transform "^4.6.0"
-    mini-css-extract-plugin "^0.5.0"
+    mini-css-extract-plugin "^0.6.0"
     minimist "^1.2.0"
-    ora "^3.0.0"
+    ora "^3.4.0"
     portfinder "^1.0.20"
     postcss-loader "^3.0.0"
-    read-pkg "^4.0.1"
-    semver "^5.6.0"
+    read-pkg "^5.0.0"
+    semver "^6.0.0"
     slash "^2.0.0"
     source-map-url "^0.4.0"
     ssri "^6.0.1"
     string.prototype.padend "^3.0.0"
-    terser-webpack-plugin "^1.2.1"
+    terser-webpack-plugin "^1.2.3"
     thread-loader "^2.1.2"
     url-loader "^1.1.2"
-    vue-loader "^15.6.2"
+    vue-loader "^15.7.0"
     webpack ">=4 < 4.29"
-    webpack-bundle-analyzer "^3.0.3"
+    webpack-bundle-analyzer "^3.3.0"
     webpack-chain "^4.11.0"
-    webpack-dev-server "^3.1.14"
+    webpack-dev-server "^3.3.1"
     webpack-merge "^4.2.1"
     yorkie "^2.0.0"
 
-"@vue/cli-shared-utils@^3.4.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-3.4.0.tgz#4331cb926351e399bf7305a9306bf09d0f2a1b9d"
-  integrity sha512-w9j2qIroUUC2ym4Lb0lLMdlGmYThhwV0OizOEVigB5eZOEUEBV2Mv43K+nWJ6OyRBACnvhJTDi1gIwJo8zUvOw==
+"@vue/cli-shared-utils@^3.7.0":
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-3.7.0.tgz#957dd3c31a31208caf9f119cac6008fd4960d46e"
+  integrity sha512-+LPDAQ1CE3ci1ADOvNqJMPdqyxgJxOq5HUgGDSKCHwviXF6GtynfljZXiSzgWh5ueMFxJphCfeMsTZqFWwsHVg==
   dependencies:
     chalk "^2.4.1"
     execa "^1.0.0"
@@ -1212,10 +1236,10 @@
     lru-cache "^5.1.1"
     node-ipc "^9.1.1"
     opn "^5.3.0"
-    ora "^3.0.0"
+    ora "^3.4.0"
     request "^2.87.0"
-    request-promise-native "^1.0.5"
-    semver "^5.5.0"
+    request-promise-native "^1.0.7"
+    semver "^6.0.0"
     string.prototype.padstart "^3.0.0"
 
 "@vue/component-compiler-utils@^1.2.1":
@@ -1233,10 +1257,10 @@
     source-map "^0.5.6"
     vue-template-es2015-compiler "^1.6.0"
 
-"@vue/component-compiler-utils@^2.5.1", "@vue/component-compiler-utils@^2.5.2":
-  version "2.5.2"
-  resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.5.2.tgz#a8d57e773354ab10e4742c7d6a8dd86184d4d7be"
-  integrity sha512-3exq9O89GXo9E+CGKzgURCbasG15FtFMs8QRrCUVWGaKue4Egpw41MHb3Avtikv1VykKfBq3FvAnf9Nx3sdVJg==
+"@vue/component-compiler-utils@^2.5.1", "@vue/component-compiler-utils@^2.6.0":
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.6.0.tgz#aa46d2a6f7647440b0b8932434d22f12371e543b"
+  integrity sha512-IHjxt7LsOFYc0DkTncB7OXJL7UzwOLPPQCfEUNyxL2qt+tF12THV+EO33O1G2Uk4feMSWua3iD39Itszx0f0bw==
   dependencies:
     consolidate "^0.15.1"
     hash-sum "^1.0.2"
@@ -1246,7 +1270,7 @@
     postcss-selector-parser "^5.0.0"
     prettier "1.16.3"
     source-map "~0.6.1"
-    vue-template-es2015-compiler "^1.8.2"
+    vue-template-es2015-compiler "^1.9.0"
 
 "@vue/eslint-config-prettier@^4.0.1":
   version "4.0.1"
@@ -1444,12 +1468,12 @@
   integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
 
 accepts@~1.3.4, accepts@~1.3.5:
-  version "1.3.5"
-  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2"
-  integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I=
-  dependencies:
-    mime-types "~2.1.18"
-    negotiator "0.6.1"
+  version "1.3.7"
+  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
+  integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
+  dependencies:
+    mime-types "~2.1.24"
+    negotiator "0.6.2"
 
 acorn-bigint@^0.2.0:
   version "0.2.0"
@@ -1487,9 +1511,9 @@
     acorn "^4.0.4"
 
 acorn-globals@^4.1.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.0.tgz#e3b6f8da3c1552a95ae627571f7dd6923bb54103"
-  integrity sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==
+  version "4.3.2"
+  resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.2.tgz#4e2c2313a597fd589720395f6354b41cd5ec8006"
+  integrity sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==
   dependencies:
     acorn "^6.0.1"
     acorn-walk "^6.0.1"
@@ -1571,20 +1595,20 @@
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
   integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=
 
-acorn@^5.0.0, acorn@^5.2.1, acorn@^5.3.0, acorn@^5.4.0, acorn@^5.4.1, acorn@^5.5.0, acorn@^5.5.3, acorn@^5.6.2, acorn@^5.7.3:
+acorn@^5.0.0, acorn@^5.2.1, acorn@^5.3.0, acorn@^5.4.0, acorn@^5.4.1, acorn@^5.5.0, acorn@^5.5.3, acorn@^5.6.2:
   version "5.7.3"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
   integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
 
-acorn@^6.0.1, acorn@^6.0.6:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.0.tgz#b0a3be31752c97a0f7013c5f4903b71a05db6818"
-  integrity sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==
+acorn@^6.0.1, acorn@^6.0.7, acorn@^6.1.1:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f"
+  integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==
 
 address@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9"
-  integrity sha512-z55ocwKBRLryBs394Sm3ushTtBeg6VAeuku7utSoSnsJKvKcnXFIyC6vh27n3rXyxSgkJBBCAvyOn7gSUcTYjg==
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/address/-/address-1.1.0.tgz#ef8e047847fcd2c5b6f50c16965f924fd99fe709"
+  integrity sha512-4diPfzWbLEIElVG4AnqP+00SULlPzNuyJFNnmMrLgyaxG6tZXJ1sn7mjBu4fHrJE+Yp/jgylOweJn2xsLMFggQ==
 
 agent-base@2:
   version "2.1.1"
@@ -1620,9 +1644,9 @@
     json-schema-traverse "^0.3.0"
 
 ajv@^6.1.0, ajv@^6.5.5:
-  version "6.9.1"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1"
-  integrity sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==
+  version "6.10.0"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1"
+  integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==
   dependencies:
     fast-deep-equal "^2.0.1"
     fast-json-stable-stringify "^2.0.0"
@@ -1654,9 +1678,9 @@
   integrity sha512-u3iMXDJr0cxMdQocIciDiou9Au4L5f9uT+/jCtprw3s1j3HcfCuI+khF+90Ps2KdsEhM2soF7SXB4WUvI3HlXg==
 
 ansi-colors@^3.0.0:
-  version "3.2.3"
-  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813"
-  integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
+  integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
 
 ansi-escapes@^3.0.0:
   version "3.2.0"
@@ -1678,10 +1702,10 @@
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
   integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
 
-ansi-regex@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9"
-  integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==
+ansi-regex@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
+  integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
 
 ansi-styles@^2.2.1:
   version "2.2.1"
@@ -1695,6 +1719,11 @@
   dependencies:
     color-convert "^1.9.0"
 
+any-promise@^1.0.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
+  integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
+
 anymatch@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@@ -1715,7 +1744,7 @@
   resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
   integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
 
-arch@^2.1.0:
+arch@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e"
   integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==
@@ -1758,9 +1787,9 @@
   integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
 
 array-differ@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-2.0.3.tgz#0195bb00ccccf271106efee4a4786488b7180712"
-  integrity sha1-AZW7AMzM8nEQbv7kpHhkiLcYBxI=
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-2.1.0.tgz#4b9c1c3f14b906757082925769e8ab904f4801b1"
+  integrity sha512-KbUpJgx909ZscOc/7CLATBFam7P1Z1QRQInvgT0UztM9Q72aGKCunKASAl7WNW0tnPmPyEMeMhdsfWhfmW037w==
 
 array-equal@^1.0.0:
   version "1.0.0"
@@ -1868,9 +1897,9 @@
   integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
 
 ast-types@0.x.x:
-  version "0.12.2"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.12.2.tgz#341656049ee328ac03fc805c156b49ebab1e4462"
-  integrity sha512-8c83xDLJM/dLDyXNLiR6afRRm4dPKN6KAnKqytRK3DBJul9lA+atxdQkNDkSVPdTqea5HiRq3lnnOIZ0MBpvdg==
+  version "0.12.4"
+  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.12.4.tgz#71ce6383800f24efc9a1a3308f3a6e420a0974d1"
+  integrity sha512-ky/YVYCbtVAS8TdMIaTiPFHwEpRB5z1hctepJplTr3UW5q8TDrpIMCILyk8pmLxGtn2KCtC/lSn7zOsaI7nzDw==
 
 astral-regex@^1.0.0:
   version "1.0.0"
@@ -1878,9 +1907,9 @@
   integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
 
 async-each@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
-  integrity sha1-GdOGodntxufByF04iu28xW0zYC0=
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
+  integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
 
 async-foreach@^0.1.3:
   version "0.1.3"
@@ -1897,12 +1926,12 @@
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
   integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
 
-async@^2.1.4, async@^2.5.0:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
-  integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
-  dependencies:
-    lodash "^4.17.10"
+async@^2.1.4:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381"
+  integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==
+  dependencies:
+    lodash "^4.17.11"
 
 asynckit@^0.4.0:
   version "0.4.0"
@@ -1914,13 +1943,13 @@
   resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
   integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
 
-autoprefixer@^9.4.7:
-  version "9.4.7"
-  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.4.7.tgz#f997994f9a810eae47b38fa6d8a119772051c4ff"
-  integrity sha512-qS5wW6aXHkm53Y4z73tFGsUhmZu4aMPV9iHXYlF0c/wxjknXNHuj/1cIQb+6YH692DbJGGWcckAXX+VxKvahMA==
-  dependencies:
-    browserslist "^4.4.1"
-    caniuse-lite "^1.0.30000932"
+autoprefixer@^9.5.1:
+  version "9.5.1"
+  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.5.1.tgz#243b1267b67e7e947f28919d786b50d3bb0fb357"
+  integrity sha512-KJSzkStUl3wP0D5sdMlP82Q52JLy5+atf2MHAre48+ckWkXgixmfHyWmA77wFDy6jTHU6mIgXv6hAQ2mf1PjJQ==
+  dependencies:
+    browserslist "^4.5.4"
+    caniuse-lite "^1.0.30000957"
     normalize-range "^0.1.2"
     num2fraction "^1.2.2"
     postcss "^7.0.14"
@@ -2064,6 +2093,17 @@
   resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
   integrity sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=
 
+babel-plugin-module-resolver@3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-3.2.0.tgz#ddfa5e301e3b9aa12d852a9979f18b37881ff5a7"
+  integrity sha512-tjR0GvSndzPew/Iayf4uICWZqjBwnlMWjSx6brryfQ81F9rxBVqwDJtFCV8oOs0+vJeefK9TmdZtkIFdFe1UnA==
+  dependencies:
+    find-babel-config "^1.1.0"
+    glob "^7.1.2"
+    pkg-up "^2.0.0"
+    reselect "^3.0.1"
+    resolve "^1.4.0"
+
 babel-plugin-syntax-object-rest-spread@^6.13.0:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
@@ -2218,9 +2258,9 @@
   integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
 
 binary-extensions@^1.0.0:
-  version "1.13.0"
-  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.0.tgz#9523e001306a32444b907423f1de2164222f6ab1"
-  integrity sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==
+  version "1.13.1"
+  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
+  integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
 
 block-stream@*:
   version "0.0.9"
@@ -2230,9 +2270,9 @@
     inherits "~2.0.0"
 
 bluebird@^3.1.1, bluebird@^3.5.1, bluebird@^3.5.3:
-  version "3.5.3"
-  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
-  integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
+  version "3.5.4"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714"
+  integrity sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==
 
 bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
   version "4.11.8"
@@ -2273,9 +2313,9 @@
   integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
 
 bootstrap@^4.1.1:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.0.tgz#2559ccb8d45426ac6c54db23eb3d1c9f4257fa22"
-  integrity sha512-M0vqY0Z6UDweV2nLFl5dXcb+GIo53EBCGMMVxCGH5vJxl/jsr+HkULBMd4kn9rdpdBZwd3BduCgMOYOwJybo4Q==
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac"
+  integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==
 
 brace-expansion@^1.0.0, brace-expansion@^1.1.7:
   version "1.1.11"
@@ -2391,14 +2431,14 @@
   dependencies:
     pako "~1.0.5"
 
-browserslist@^4.0.0, browserslist@^4.3.4, browserslist@^4.4.1:
-  version "4.4.1"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.4.1.tgz#42e828954b6b29a7a53e352277be429478a69062"
-  integrity sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==
-  dependencies:
-    caniuse-lite "^1.0.30000929"
-    electron-to-chromium "^1.3.103"
-    node-releases "^1.1.3"
+browserslist@^4.0.0, browserslist@^4.3.4, browserslist@^4.5.4:
+  version "4.5.6"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.6.tgz#ea42e8581ca2513fa7f371d4dd66da763938163d"
+  integrity sha512-o/hPOtbU9oX507lIqon+UvPYqpx3mHc8cV3QemSBTXwkG8gSQSK6UKvXcE/DcleU3+A59XTUHyCvZ5qGy8xVAg==
+  dependencies:
+    caniuse-lite "^1.0.30000963"
+    electron-to-chromium "^1.3.127"
+    node-releases "^1.1.17"
 
 bser@^2.0.0:
   version "2.0.0"
@@ -2441,6 +2481,11 @@
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
   integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
 
+bytes@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
+  integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
+
 cacache@^10.0.4:
   version "10.0.4"
   resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460"
@@ -2579,9 +2624,9 @@
   integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
 
 camelcase@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
-  integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+  integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
 caniuse-api@^3.0.0:
   version "3.0.0"
@@ -2593,10 +2638,10 @@
     lodash.memoize "^4.1.2"
     lodash.uniq "^4.5.0"
 
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000932:
-  version "1.0.30000936"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000936.tgz#5d33b118763988bf721b9b8ad436d0400e4a116b"
-  integrity sha512-orX4IdpbFhdNO7bTBhSbahp1EBpqzBc+qrvTRVUFfZgA4zta7TdM6PN5ZxkEUgDnz36m+PfWGcdX7AVfFWItJw==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000957, caniuse-lite@^1.0.30000963:
+  version "1.0.30000966"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000966.tgz#f3c6fefacfbfbfb981df6dfa68f2aae7bff41b64"
+  integrity sha512-qqLQ/uYrpZmFhPY96VuBkMEo8NhVFBZ9y/Bh+KnvGzGJ5I8hvpIaWlF2pw5gqe4PLAL+ZjsPgMOvoXSpX21Keg==
 
 canvg@1.5.3:
   version "1.5.3"
@@ -2679,21 +2724,21 @@
   integrity sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==
 
 cheerio@^1.0.0-rc.2:
-  version "1.0.0-rc.2"
-  resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db"
-  integrity sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=
+  version "1.0.0-rc.3"
+  resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6"
+  integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==
   dependencies:
     css-select "~1.2.0"
-    dom-serializer "~0.1.0"
+    dom-serializer "~0.1.1"
     entities "~1.1.1"
     htmlparser2 "^3.9.1"
     lodash "^4.15.0"
     parse5 "^3.0.1"
 
-chokidar@^2.0.0, chokidar@^2.0.2:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.1.tgz#adc39ad55a2adf26548bd2afa048f611091f9184"
-  integrity sha512-gfw3p2oQV2wEt+8VuMlNsPjCxDxvvgnm/kz+uATu805mWVF8IJN7uz9DN7iBz+RMJISmiVbCOBFs9qBGMjtPfQ==
+chokidar@^2.0.2, chokidar@^2.1.5:
+  version "2.1.5"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d"
+  integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==
   dependencies:
     anymatch "^2.0.0"
     async-each "^1.0.1"
@@ -2705,7 +2750,7 @@
     normalize-path "^3.0.0"
     path-is-absolute "^1.0.0"
     readdirp "^2.2.1"
-    upath "^1.1.0"
+    upath "^1.1.1"
   optionalDependencies:
     fsevents "^1.2.7"
 
@@ -2721,7 +2766,7 @@
   dependencies:
     tslib "^1.9.0"
 
-chromedriver@^2.45.0:
+chromedriver@^2.46.0:
   version "2.46.0"
   resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-2.46.0.tgz#3d78e7eb9bb65dd804fe327a6bf76fced12be053"
   integrity sha512-dLtKIJW3y/PuFrPmcw6Mb8Nh+HwSqgVrK1rWgTARXhHfWvV822X2VRkx2meU/tg2+YQL6/nNgT6n5qWwIDHbwg==
@@ -2779,20 +2824,21 @@
   dependencies:
     restore-cursor "^2.0.0"
 
-cli-spinners@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a"
-  integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==
-
-cli-table3@^0.5.0:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
-  integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==
-  dependencies:
-    object-assign "^4.1.0"
-    string-width "^2.1.1"
-  optionalDependencies:
-    colors "^1.1.2"
+cli-highlight@^2.1.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.1.tgz#2180223d51618b112f4509cf96e4a6c750b07e97"
+  integrity sha512-0y0VlNmdD99GXZHYnvrQcmHxP8Bi6T00qucGgBgGv4kJ0RyDthNnnFPupHV7PYv/OXSVk+azFbOeaW6+vGmx9A==
+  dependencies:
+    chalk "^2.3.0"
+    highlight.js "^9.6.0"
+    mz "^2.4.0"
+    parse5 "^4.0.0"
+    yargs "^13.0.0"
+
+cli-spinners@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.1.0.tgz#22c34b4d51f573240885b201efda4e4ec9fff3c7"
+  integrity sha512-8B00fJOEh1HPrx4fo5eW16XmE1PcL1tGpGrxy63CXGP9nHdPBN63X75hA1zhvQuhVztJWLqV58Roj2qlNM7cAA==
 
 cli-width@^2.0.0:
   version "2.2.0"
@@ -2808,13 +2854,13 @@
     select "^1.1.2"
     tiny-emitter "^2.0.0"
 
-clipboardy@^1.2.3:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-1.2.3.tgz#0526361bf78724c1f20be248d428e365433c07ef"
-  integrity sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==
-  dependencies:
-    arch "^2.1.0"
-    execa "^0.8.0"
+clipboardy@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.0.0.tgz#3fcee421fdeca4e6a62ce72b66f3eb0c42165acd"
+  integrity sha512-XbVjHMsss0giNUkp/tV/3eEAZe8i1fZTLzmPKqjE1RGIAWOTiF5D014f6R+g53ZAq0IK3cPrJXFvqE8eQjhFYQ==
+  dependencies:
+    arch "^2.1.1"
+    execa "^1.0.0"
 
 cliui@^2.1.0:
   version "2.1.0"
@@ -2834,7 +2880,7 @@
     strip-ansi "^3.0.1"
     wrap-ansi "^2.0.0"
 
-cliui@^4.0.0, cliui@^4.1.0:
+cliui@^4.0.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
   integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==
@@ -2843,6 +2889,15 @@
     strip-ansi "^4.0.0"
     wrap-ansi "^2.0.0"
 
+cliui@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
+  integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
+  dependencies:
+    string-width "^3.1.0"
+    strip-ansi "^5.2.0"
+    wrap-ansi "^5.1.0"
+
 clone-deep@^2.0.1:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713"
@@ -2873,7 +2928,7 @@
   resolved "https://registry.yarnpkg.com/co/-/co-3.0.6.tgz#1445f226c5eb956138e68c9ac30167ea7d2e6bda"
   integrity sha1-FEXyJsXrlWE45oyawwFn6n0ua9o=
 
-coa@~2.0.1:
+coa@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3"
   integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==
@@ -2921,23 +2976,13 @@
     simple-swizzle "^0.2.2"
 
 color@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/color/-/color-3.1.0.tgz#d8e9fb096732875774c84bf922815df0308d0ffc"
-  integrity sha512-CwyopLkuRYO5ei2EpzpIh6LqJMt6Mt+jZhO5VI5f/wJLZriXQE32/SSqzmrh+QB+AZT81Cj8yv+7zwToW8ahZg==
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/color/-/color-3.1.1.tgz#7abf5c0d38e89378284e873c207ae2172dcc8a61"
+  integrity sha512-PvUltIXRjehRKPSy89VnDWFKY58xyhTLyxIg21vwQBI6qLwZNPmC8k3C1uytIgFKEpOIzN4y32iPm8231zFHIg==
   dependencies:
     color-convert "^1.9.1"
     color-string "^1.5.2"
 
-colors@^1.1.2:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
-  integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==
-
-colors@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
-  integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
-
 combined-stream@^1.0.6, combined-stream@~1.0.6:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
@@ -2945,12 +2990,12 @@
   dependencies:
     delayed-stream "~1.0.0"
 
-commander@2, commander@^2.18.0, commander@^2.19.0:
-  version "2.19.0"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
-  integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
-
-commander@2.17.x, commander@~2.17.1:
+commander@2, commander@^2.18.0, commander@^2.19.0, commander@~2.20.0:
+  version "2.20.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
+  integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
+
+commander@2.17.x:
   version "2.17.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
   integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
@@ -2962,33 +3007,38 @@
   dependencies:
     graceful-readlink ">= 1.0.0"
 
+commander@~2.19.0:
+  version "2.19.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
+  integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
+
 commondir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
   integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
 
 component-emitter@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
-  integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
-
-compressible@~2.0.14:
-  version "2.0.15"
-  resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.15.tgz#857a9ab0a7e5a07d8d837ed43fe2defff64fe212"
-  integrity sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==
-  dependencies:
-    mime-db ">= 1.36.0 < 2"
-
-compression@^1.5.2:
-  version "1.7.3"
-  resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.3.tgz#27e0e176aaf260f7f2c2813c3e440adb9f1993db"
-  integrity sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
+  integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
+
+compressible@~2.0.16:
+  version "2.0.17"
+  resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.17.tgz#6e8c108a16ad58384a977f3a482ca20bff2f38c1"
+  integrity sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==
+  dependencies:
+    mime-db ">= 1.40.0 < 2"
+
+compression@^1.7.4:
+  version "1.7.4"
+  resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
+  integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
   dependencies:
     accepts "~1.3.5"
     bytes "3.0.0"
-    compressible "~2.0.14"
+    compressible "~2.0.16"
     debug "2.6.9"
-    on-headers "~1.0.1"
+    on-headers "~1.0.2"
     safe-buffer "5.1.2"
     vary "~1.1.2"
 
@@ -3039,7 +3089,7 @@
     ini "^1.3.4"
     proto-list "~1.2.1"
 
-connect-history-api-fallback@^1.3.0:
+connect-history-api-fallback@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
   integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
@@ -3136,10 +3186,10 @@
     p-limit "^1.0.0"
     serialize-javascript "^1.4.0"
 
-core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.7, core-js@^2.6.3:
-  version "2.6.4"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.4.tgz#b8897c062c4d769dd30a0ac5c73976c47f92ea0d"
-  integrity sha512-05qQ5hXShcqGkPZpXEFLIpxayZscVD2kuMBZewxiIPPEagukO4mqgPA9CWhUvFBJfy3ODdK2p9xyHh7FTU9/7A==
+core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.5:
+  version "2.6.5"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895"
+  integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==
 
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
@@ -3157,13 +3207,13 @@
     require-from-string "^2.0.1"
 
 cosmiconfig@^5.0.0:
-  version "5.0.7"
-  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.7.tgz#39826b292ee0d78eda137dfa3173bd1c21a43b04"
-  integrity sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.0.tgz#45038e4d28a7fe787203aede9c25bca4a08b12c8"
+  integrity sha512-nxt+Nfc3JAqf4WIWd0jXLjTJZmsPLrA9DDc4nRw2KFJQJK7DNooqSXrNI7tzLG50CF8axczly5UV929tBmh/7g==
   dependencies:
     import-fresh "^2.0.0"
     is-directory "^0.3.1"
-    js-yaml "^3.9.0"
+    js-yaml "^3.13.0"
     parse-json "^4.0.0"
 
 create-ecdh@^4.0.0:
@@ -3280,7 +3330,7 @@
     postcss-value-parser "^3.3.0"
     source-list-map "^2.0.0"
 
-css-select-base-adapter@~0.1.0:
+css-select-base-adapter@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
   integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==
@@ -3341,9 +3391,9 @@
   integrity sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=
 
 css-what@2.1, css-what@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d"
-  integrity sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
+  integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
 
 css@^2.1.0:
   version "2.2.4"
@@ -3365,40 +3415,45 @@
   resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703"
   integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==
 
-cssnano-preset-default@^4.0.0, cssnano-preset-default@^4.0.6:
-  version "4.0.6"
-  resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.6.tgz#92379e2a6db4a91c0ea727f5f556eeac693eab6a"
-  integrity sha512-UPboYbFaJFtDUhJ4fqctThWbbyF4q01/7UhsZbLzp35l+nUxtzh1SifoVlEfyLM3n3Z0htd8B1YlCxy9i+bQvg==
+cssesc@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
+  integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
+
+cssnano-preset-default@^4.0.0, cssnano-preset-default@^4.0.7:
+  version "4.0.7"
+  resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76"
+  integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==
   dependencies:
     css-declaration-sorter "^4.0.1"
     cssnano-util-raw-cache "^4.0.1"
     postcss "^7.0.0"
-    postcss-calc "^7.0.0"
-    postcss-colormin "^4.0.2"
+    postcss-calc "^7.0.1"
+    postcss-colormin "^4.0.3"
     postcss-convert-values "^4.0.1"
-    postcss-discard-comments "^4.0.1"
+    postcss-discard-comments "^4.0.2"
     postcss-discard-duplicates "^4.0.2"
     postcss-discard-empty "^4.0.1"
     postcss-discard-overridden "^4.0.1"
-    postcss-merge-longhand "^4.0.10"
-    postcss-merge-rules "^4.0.2"
+    postcss-merge-longhand "^4.0.11"
+    postcss-merge-rules "^4.0.3"
     postcss-minify-font-values "^4.0.2"
-    postcss-minify-gradients "^4.0.1"
-    postcss-minify-params "^4.0.1"
-    postcss-minify-selectors "^4.0.1"
+    postcss-minify-gradients "^4.0.2"
+    postcss-minify-params "^4.0.2"
+    postcss-minify-selectors "^4.0.2"
     postcss-normalize-charset "^4.0.1"
-    postcss-normalize-display-values "^4.0.1"
-    postcss-normalize-positions "^4.0.1"
-    postcss-normalize-repeat-style "^4.0.1"
-    postcss-normalize-string "^4.0.1"
-    postcss-normalize-timing-functions "^4.0.1"
+    postcss-normalize-display-values "^4.0.2"
+    postcss-normalize-positions "^4.0.2"
+    postcss-normalize-repeat-style "^4.0.2"
+    postcss-normalize-string "^4.0.2"
+    postcss-normalize-timing-functions "^4.0.2"
     postcss-normalize-unicode "^4.0.1"
     postcss-normalize-url "^4.0.1"
-    postcss-normalize-whitespace "^4.0.1"
-    postcss-ordered-values "^4.1.1"
-    postcss-reduce-initial "^4.0.2"
-    postcss-reduce-transforms "^4.0.1"
-    postcss-svgo "^4.0.1"
+    postcss-normalize-whitespace "^4.0.2"
+    postcss-ordered-values "^4.1.2"
+    postcss-reduce-initial "^4.0.3"
+    postcss-reduce-transforms "^4.0.2"
+    postcss-svgo "^4.0.2"
     postcss-unique-selectors "^4.0.1"
 
 cssnano-util-get-arguments@^4.0.0:
@@ -3423,17 +3478,17 @@
   resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3"
   integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==
 
-cssnano@^4.0.0, cssnano@^4.1.8:
-  version "4.1.8"
-  resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.8.tgz#8014989679d5fd42491e4499a521dbfb85c95fd1"
-  integrity sha512-5GIY0VzAHORpbKiL3rMXp4w4M1Ki+XlXgEXyuWXVd3h6hlASb+9Vo76dNP56/elLMVBBsUfusCo1q56uW0UWig==
+cssnano@^4.0.0, cssnano@^4.1.10:
+  version "4.1.10"
+  resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2"
+  integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==
   dependencies:
     cosmiconfig "^5.0.0"
-    cssnano-preset-default "^4.0.6"
+    cssnano-preset-default "^4.0.7"
     is-resolvable "^1.0.0"
     postcss "^7.0.0"
 
-csso@^3.5.0:
+csso@^3.5.1:
   version "3.5.1"
   resolved "https://registry.yarnpkg.com/csso/-/csso-3.5.1.tgz#7b9eb8be61628973c1b261e169d2f024008e758b"
   integrity sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==
@@ -3453,12 +3508,17 @@
     cssom "0.3.x"
 
 cssstyle@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.1.1.tgz#18b038a9c44d65f7a8e428a653b9f6fe42faf5fb"
-  integrity sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.2.2.tgz#427ea4d585b18624f6fdbf9de7a2a1a3ba713077"
+  integrity sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==
   dependencies:
     cssom "0.3.x"
 
+current-script-polyfill@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/current-script-polyfill/-/current-script-polyfill-1.0.0.tgz#f31cf7e4f3e218b0726e738ca92a02d3488ef615"
+  integrity sha1-8xz35PPiGLBybnOMqSoC00iO9hU=
+
 currently-unhandled@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
@@ -3552,9 +3612,9 @@
     d3-dsv "1"
 
 d3-force@1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.2.0.tgz#60713f7efe8764f53e685d69433c06914dc4ea4c"
-  integrity sha512-PFLcDnRVANHMudbQlIB87gcfQorEsDIAvRpZ2bNddfM/WxdsEkyrEaOIPoydhH1I1V4HPjNLGOMLXCA0AuGQ9w==
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.2.1.tgz#fd29a5d1ff181c9e7f0669e4bd72bdb0e914ec0b"
+  integrity sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==
   dependencies:
     d3-collection "1"
     d3-dispatch "1"
@@ -3650,9 +3710,9 @@
   integrity sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg==
 
 d3-shape@1, d3-shape@^1.0.3:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.4.tgz#358e76014645321eecc7c364e188f8ae3d2a07d4"
-  integrity sha512-izaz4fOpOnY3CD17hkZWNxbaN70sIGagLR/5jb6RS96Y+6VqX+q1BQf1av6QSBRdfULi3Gb8Js4CzG4+KAPjMg==
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.5.tgz#e81aea5940f59f0a79cfccac012232a8987c6033"
+  integrity sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==
   dependencies:
     d3-path "1"
 
@@ -3702,9 +3762,9 @@
     d3-transition "1"
 
 d3@^5.7.0:
-  version "5.9.1"
-  resolved "https://registry.yarnpkg.com/d3/-/d3-5.9.1.tgz#fde73fa9af7281d2ff0d2a32aa8f306e93a6d1cd"
-  integrity sha512-JceuBn5VVWySPQc9EA0gfq0xQVgEQXGokHhe+359bmgGeUITLK2r2b9idMzquQne9DKxb7JDCE1gDRXe9OIF2Q==
+  version "5.9.2"
+  resolved "https://registry.yarnpkg.com/d3/-/d3-5.9.2.tgz#64e8a7e9c3d96d9e6e4999d2c8a2c829767e67f5"
+  integrity sha512-ydrPot6Lm3nTWH+gJ/Cxf3FcwuvesYQ5uk+j/kXEH/xbuYWYWTMAHTJQkyeuG8Y5WM5RSEYB41EctUrXQQytRQ==
   dependencies:
     d3-array "1"
     d3-axis "1"
@@ -3746,9 +3806,9 @@
     assert-plus "^1.0.0"
 
 data-uri-to-buffer@2:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.0.tgz#0ba23671727349828c32cfafddea411908d13d23"
-  integrity sha512-YbKCNLPPP4inc0E5If4OaalBc7gpaM2MRv77Pv2VThVComLKfbGYtJcdDCViDyp1Wd4SebhHLz94vp91zbK6bw==
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.1.tgz#ca8f56fe38b1fd329473e9d1b4a9afcd8ce1c045"
+  integrity sha512-OkVVLrerfAKZlW2ZZ3Ve2y65jgiWqBKsTfUIAFbn8nVbPcCZg6l6gikKlEYv0kXcmzqGm6mFq/Jf2vriuEkv8A==
   dependencies:
     "@types/node" "^8.0.7"
 
@@ -3781,7 +3841,7 @@
   resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
   integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==
 
-debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
+debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -3809,14 +3869,7 @@
   dependencies:
     ms "^2.1.1"
 
-debug@=3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
-  integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
-  dependencies:
-    ms "2.0.0"
-
-debug@^3.1.0, debug@^3.2.5:
+debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
   integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -3828,13 +3881,6 @@
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
-decamelize@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-2.0.0.tgz#656d7bbc8094c4c788ea53c5840908c9c7d063c7"
-  integrity sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==
-  dependencies:
-    xregexp "4.0.0"
-
 decode-uri-component@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
@@ -3867,17 +3913,17 @@
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
   integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==
 
-deepmerge@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.1.0.tgz#a612626ce4803da410d77554bfd80361599c034d"
-  integrity sha512-/TnecbwXEdycfbsM2++O3eGiatEFHjjNciHEwJclM+T5Kd94qD1AP+2elP/Mq0L5b9VZJao5znR01Mz6eX8Seg==
-
-default-gateway@^2.6.0:
-  version "2.7.2"
-  resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-2.7.2.tgz#b7ef339e5e024b045467af403d50348db4642d0f"
-  integrity sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==
-  dependencies:
-    execa "^0.10.0"
+deepmerge@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.2.0.tgz#58ef463a57c08d376547f8869fdc5bcee957f44e"
+  integrity sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow==
+
+default-gateway@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
+  integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==
+  dependencies:
+    execa "^1.0.0"
     ip-regex "^2.1.0"
 
 default-require-extensions@^1.0.0:
@@ -3944,6 +3990,19 @@
     pify "^3.0.0"
     rimraf "^2.2.8"
 
+del@^4.1.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4"
+  integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==
+  dependencies:
+    "@types/glob" "^7.1.1"
+    globby "^6.1.0"
+    is-path-cwd "^2.0.0"
+    is-path-in-cwd "^2.0.0"
+    p-map "^2.0.0"
+    pify "^4.0.1"
+    rimraf "^2.6.3"
+
 delayed-stream@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -4023,7 +4082,7 @@
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
-dir-glob@^2.0.0, dir-glob@^2.2.1:
+dir-glob@^2.0.0, dir-glob@^2.2.2:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
   integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==
@@ -4062,7 +4121,7 @@
   resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
   integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=
 
-dom-converter@~0.2:
+dom-converter@^0.2:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
   integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==
@@ -4074,29 +4133,24 @@
   resolved "https://registry.yarnpkg.com/dom-event-types/-/dom-event-types-1.0.0.tgz#5830a0a29e1bf837fe50a70cd80a597232813cae"
   integrity sha512-2G2Vwi2zXTHBGqXHsJ4+ak/iP0N8Ar+G8a7LiD2oup5o4sQWytwqqrZu/O6hIMV0KMID2PL69OhpshLO0n7UJQ==
 
-dom-serializer@0, dom-serializer@~0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
-  integrity sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=
-  dependencies:
-    domelementtype "~1.1.1"
-    entities "~1.1.1"
+dom-serializer@0, dom-serializer@~0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
+  integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==
+  dependencies:
+    domelementtype "^1.3.0"
+    entities "^1.1.1"
 
 domain-browser@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
   integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
 
-domelementtype@1, domelementtype@^1.3.0:
+domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
   integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
 
-domelementtype@~1.1.1:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
-  integrity sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=
-
 domexception@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
@@ -4104,13 +4158,6 @@
   dependencies:
     webidl-conversions "^4.0.2"
 
-domhandler@2.1:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594"
-  integrity sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=
-  dependencies:
-    domelementtype "1"
-
 domhandler@^2.3.0:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
@@ -4118,13 +4165,6 @@
   dependencies:
     domelementtype "1"
 
-domutils@1.1:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485"
-  integrity sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=
-  dependencies:
-    domelementtype "1"
-
 domutils@1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
@@ -4148,6 +4188,16 @@
   dependencies:
     is-obj "^1.0.0"
 
+dotenv-expand@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
+  integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==
+
+dotenv@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c"
+  integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==
+
 duplexer@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
@@ -4190,15 +4240,13 @@
     jsbn "~0.1.0"
     safer-buffer "^2.1.0"
 
-editorconfig@^0.15.2:
-  version "0.15.2"
-  resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.2.tgz#047be983abb9ab3c2eefe5199cb2b7c5689f0702"
-  integrity sha512-GWjSI19PVJAM9IZRGOS+YKI8LN+/sjkSjNyvxL5ucqP9/IqtYNXBaQ/6c/hkPNYQHyOHra2KoXZI/JVpuqwmcQ==
-  dependencies:
-    "@types/node" "^10.11.7"
-    "@types/semver" "^5.5.0"
+editorconfig@^0.15.3:
+  version "0.15.3"
+  resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5"
+  integrity sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==
+  dependencies:
     commander "^2.19.0"
-    lru-cache "^4.1.3"
+    lru-cache "^4.1.5"
     semver "^5.6.0"
     sigmund "^1.0.1"
 
@@ -4217,10 +4265,10 @@
   resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
   integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
 
-electron-to-chromium@^1.3.103:
-  version "1.3.113"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9"
-  integrity sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==
+electron-to-chromium@^1.3.127:
+  version "1.3.131"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.131.tgz#205a0b7a276b3f56bc056f19178909243054252a"
+  integrity sha512-NSO4jLeyGLWrT4mzzfYX8vt1MYCoMI5LxSYAjt0H9+LF/14JyiKJSyyjA6AJTxflZlEM5v3QU33F0ohbPMCAPg==
 
 elliptic@^6.0.0:
   version "6.4.1"
@@ -4235,6 +4283,11 @@
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.0"
 
+emoji-regex@^7.0.1:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
+  integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
+
 emojis-list@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
@@ -4319,9 +4372,9 @@
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
 
 escodegen@1.x.x, escodegen@^1.6.1, escodegen@^1.9.1:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589"
-  integrity sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==
+  version "1.11.1"
+  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510"
+  integrity sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==
   dependencies:
     esprima "^3.1.3"
     estraverse "^4.2.0"
@@ -4337,7 +4390,7 @@
   dependencies:
     get-stdin "^6.0.0"
 
-eslint-loader@^2.1.1:
+eslint-loader@^2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.1.2.tgz#453542a1230d6ffac90e4e7cb9cadba9d851be68"
   integrity sha512-rA9XiXEOilLYPOIInvVH5S/hYfyTPyxag6DZhoQOduM+3TkghAEQ3VcFO8VnX4J4qg/UIBzp72aOf/xvYmpmsg==
@@ -4379,9 +4432,9 @@
     estraverse "^4.1.1"
 
 eslint-scope@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172"
-  integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
+  integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==
   dependencies:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
@@ -4488,9 +4541,9 @@
   integrity sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==
 
 eventemitter3@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
-  integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
+  integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
 
 events@^3.0.0:
   version "3.0.0"
@@ -4519,19 +4572,6 @@
   dependencies:
     merge "^1.2.0"
 
-execa@^0.10.0:
-  version "0.10.0"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50"
-  integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==
-  dependencies:
-    cross-spawn "^6.0.0"
-    get-stream "^3.0.0"
-    is-stream "^1.1.0"
-    npm-run-path "^2.0.0"
-    p-finally "^1.0.0"
-    signal-exit "^3.0.0"
-    strip-eof "^1.0.0"
-
 execa@^0.7.0:
   version "0.7.0"
   resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
@@ -4622,7 +4662,7 @@
     jest-message-util "^23.4.0"
     jest-regex-util "^23.3.0"
 
-express@^4.16.2, express@^4.16.3:
+express@^4.16.3, express@^4.16.4:
   version "4.16.4"
   resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e"
   integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==
@@ -4833,6 +4873,11 @@
     loader-utils "^1.0.2"
     schema-utils "^1.0.0"
 
+file-saver@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.2.tgz#06d6e728a9ea2df2cce2f8d9e84dfcdc338ec17a"
+  integrity sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==
+
 file-saver@eligrey/FileSaver.js#1.3.8:
   version "1.3.8"
   resolved "https://codeload.github.com/eligrey/FileSaver.js/tar.gz/e865e37af9f9947ddcced76b549e27dc45c1cb2e"
@@ -4895,9 +4940,9 @@
     unpipe "~1.0.0"
 
 find-babel-config@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.1.0.tgz#acc01043a6749fec34429be6b64f542ebb5d6355"
-  integrity sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U=
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.2.0.tgz#a9b7b317eb5b9860cda9d54740a8c8337a2283a2"
+  integrity sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==
   dependencies:
     json5 "^0.5.1"
     path-exists "^3.0.0"
@@ -4921,12 +4966,12 @@
     pkg-dir "^2.0.0"
 
 find-cache-dir@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d"
-  integrity sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
+  integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
   dependencies:
     commondir "^1.0.1"
-    make-dir "^1.0.0"
+    make-dir "^2.0.0"
     pkg-dir "^3.0.0"
 
 find-up@^1.0.0:
@@ -4980,11 +5025,11 @@
     readable-stream "^2.3.6"
 
 follow-redirects@^1.0.0, follow-redirects@^1.3.0:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.1.tgz#514973c44b5757368bad8bddfe52f81f015c94cb"
-  integrity sha512-t2JCjbzxQpWvbhts3l6SH1DKzSrx8a+SsaVf4h6bG4kOXUuPYS/kg2Lr4gQSb7eemaHqJkOThF1BGyjlUkO1GQ==
-  dependencies:
-    debug "=3.1.0"
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
+  integrity sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==
+  dependencies:
+    debug "^3.2.6"
 
 for-in@^0.1.3:
   version "0.1.8"
@@ -5081,12 +5126,12 @@
   integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
 
 fsevents@^1.2.3, fsevents@^1.2.7:
-  version "1.2.7"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4"
-  integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==
-  dependencies:
-    nan "^2.9.2"
-    node-pre-gyp "^0.10.0"
+  version "1.2.9"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"
+  integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==
+  dependencies:
+    nan "^2.12.1"
+    node-pre-gyp "^0.12.0"
 
 fstream@^1.0.0, fstream@^1.0.2:
   version "1.0.11"
@@ -5161,6 +5206,11 @@
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
   integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
 
+get-caller-file@^2.0.1:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
 get-stdin@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
@@ -5288,9 +5338,9 @@
     which "^1.2.14"
 
 globals@^11.0.1, globals@^11.1.0:
-  version "11.11.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-11.11.0.tgz#dcf93757fa2de5486fbeed7118538adf789e9c2e"
-  integrity sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==
+  version "11.12.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+  integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
 
 globals@^9.18.0:
   version "9.18.0"
@@ -5320,13 +5370,14 @@
     pify "^3.0.0"
     slash "^1.0.0"
 
-globby@^9.0.0:
-  version "9.0.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-9.0.0.tgz#3800df736dc711266df39b4ce33fe0d481f94c23"
-  integrity sha512-q0qiO/p1w/yJ0hk8V9x1UXlgsXUxlGd0AHUOXZVXBO6aznDtpx7M8D1kBrCAItoPm+4l8r6ATXV1JpjY2SBQOw==
-  dependencies:
+globby@^9.2.0:
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
+  integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==
+  dependencies:
+    "@types/glob" "^7.1.1"
     array-union "^1.0.2"
-    dir-glob "^2.2.1"
+    dir-glob "^2.2.2"
     fast-glob "^2.2.6"
     glob "^7.1.3"
     ignore "^4.0.3"
@@ -5370,12 +5421,12 @@
   integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
 
 gzip-size@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.0.0.tgz#a55ecd99222f4c48fd8c01c625ce3b349d0a0e80"
-  integrity sha512-5iI7omclyqrnWw4XbXAmGhPsABkSIDQonv2K0h61lybgofWa6iZyvrI3r2zsJH4P8Nb64fFVzlvfhs0g7BBxAA==
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.0.tgz#2db0396c71f5c902d5cf6b52add5030b93c99bd2"
+  integrity sha512-wfSnvypBDRW94v5W3ckvvz/zFUNdJ81VgOP6tE4bPpRUcc0wGqU+y0eZjJEvKxwubJFix6P84sE8M51YWLT7rQ==
   dependencies:
     duplexer "^0.1.1"
-    pify "^3.0.0"
+    pify "^4.0.1"
 
 handle-thing@^2.0.0:
   version "2.0.0"
@@ -5383,11 +5434,11 @@
   integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
 
 handlebars@^4.0.3:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a"
-  integrity sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==
-  dependencies:
-    async "^2.5.0"
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
+  integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==
+  dependencies:
+    neo-async "^2.6.0"
     optimist "^0.6.1"
     source-map "^0.6.1"
   optionalDependencies:
@@ -5507,10 +5558,10 @@
   resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
   integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
 
-highlight.js@*:
-  version "9.14.2"
-  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.14.2.tgz#efbfb22dc701406e4da406056ef8c2b70ebe5b26"
-  integrity sha512-Nc6YNECYpxyJABGYJAyw7dBAYbXEuIzwzkqoJnwbc1nIpCiN+3ioYf0XrBnLiyyG0JLuJhpPtt2iTSbXiKLoyA==
+highlight.js@*, highlight.js@^9.6.0:
+  version "9.15.6"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.6.tgz#72d4d8d779ec066af9a17cb14360c3def0aa57c4"
+  integrity sha512-zozTAWM1D6sozHo8kqhfYgsac+B+q0PmsjXeyDrYIHHcBN0zTVT66+s2GW1GZv7DbyaROdLXKdabwS/WqPyIdQ==
 
 hmac-drbg@^1.0.0:
   version "1.0.1"
@@ -5522,9 +5573,9 @@
     minimalistic-crypto-utils "^1.0.1"
 
 hoek@6.x.x:
-  version "6.1.2"
-  resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.2.tgz#99e6d070561839de74ee427b61aa476bd6bddfd6"
-  integrity sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==
+  version "6.1.3"
+  resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c"
+  integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==
 
 home-or-tmp@^2.0.0:
   version "2.0.0"
@@ -5535,9 +5586,9 @@
     os-tmpdir "^1.0.1"
 
 homedir-polyfill@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
-  integrity sha1-TCu8inWJmP7r9e1oWA921GdotLw=
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
+  integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
   dependencies:
     parse-passwd "^1.0.0"
 
@@ -5583,7 +5634,7 @@
   dependencies:
     whatwg-encoding "^1.0.1"
 
-html-entities@^1.2.0:
+html-entities@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
   integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=
@@ -5626,27 +5677,17 @@
   dependencies:
     css-line-break "1.0.1"
 
-htmlparser2@^3.9.1:
-  version "3.10.0"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464"
-  integrity sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==
-  dependencies:
-    domelementtype "^1.3.0"
+htmlparser2@^3.3.0, htmlparser2@^3.9.1:
+  version "3.10.1"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
+  integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
+  dependencies:
+    domelementtype "^1.3.1"
     domhandler "^2.3.0"
     domutils "^1.5.1"
     entities "^1.1.1"
     inherits "^2.0.1"
-    readable-stream "^3.0.6"
-
-htmlparser2@~3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe"
-  integrity sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=
-  dependencies:
-    domelementtype "1"
-    domhandler "2.1"
-    domutils "1.1"
-    readable-stream "1.0"
+    readable-stream "^3.1.1"
 
 http-deceiver@^1.2.7:
   version "1.2.7"
@@ -5663,6 +5704,17 @@
     setprototypeof "1.1.0"
     statuses ">= 1.4.0 < 2"
 
+http-errors@1.7.2:
+  version "1.7.2"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
+  integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
+  dependencies:
+    depd "~1.1.2"
+    inherits "2.0.3"
+    setprototypeof "1.1.1"
+    statuses ">= 1.5.0 < 2"
+    toidentifier "1.0.0"
+
 http-parser-js@>=0.4.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8"
@@ -5677,17 +5729,17 @@
     debug "2"
     extend "3"
 
-http-proxy-middleware@~0.18.0:
-  version "0.18.0"
-  resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz#0987e6bb5a5606e5a69168d8f967a87f15dd8aab"
-  integrity sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==
-  dependencies:
-    http-proxy "^1.16.2"
+http-proxy-middleware@^0.19.1:
+  version "0.19.1"
+  resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a"
+  integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==
+  dependencies:
+    http-proxy "^1.17.0"
     is-glob "^4.0.0"
-    lodash "^4.17.5"
-    micromatch "^3.1.9"
-
-http-proxy@^1.16.2:
+    lodash "^4.17.11"
+    micromatch "^3.1.10"
+
+http-proxy@^1.17.0:
   version "1.17.0"
   resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a"
   integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==
@@ -5746,9 +5798,9 @@
     postcss "^6.0.1"
 
 ieee754@^1.1.4, ieee754@^1.1.6:
-  version "1.1.12"
-  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
-  integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==
+  version "1.1.13"
+  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
+  integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
 
 iferr@^0.1.5:
   version "0.1.5"
@@ -5880,13 +5932,13 @@
     strip-ansi "^4.0.0"
     through "^2.3.6"
 
-internal-ip@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-3.0.1.tgz#df5c99876e1d2eb2ea2d74f520e3f669a00ece27"
-  integrity sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==
-  dependencies:
-    default-gateway "^2.6.0"
-    ipaddr.js "^1.5.2"
+internal-ip@^4.2.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
+  integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==
+  dependencies:
+    default-gateway "^4.2.0"
+    ipaddr.js "^1.9.0"
 
 interpret@^1.1.0:
   version "1.2.0"
@@ -5925,12 +5977,7 @@
   resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
   integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
 
-ipaddr.js@1.8.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e"
-  integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4=
-
-ipaddr.js@^1.5.2:
+ipaddr.js@1.9.0, ipaddr.js@^1.9.0:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
   integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
@@ -6123,9 +6170,9 @@
     is-extglob "^2.1.0"
 
 is-glob@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
-  integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
+  integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
   dependencies:
     is-extglob "^2.1.1"
 
@@ -6158,6 +6205,11 @@
   resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
   integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=
 
+is-path-cwd@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.1.0.tgz#2e0c7e463ff5b7a0eb60852d851a6809347a124c"
+  integrity sha512-Sc5j3/YnM8tDeyCsVeKlm/0p95075DyLmDEIkSgQ7mXkrOX+uTCtmQFm0CYzVyJwcCCmO3k8qfJt17SxQwB5Zw==
+
 is-path-in-cwd@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52"
@@ -6165,6 +6217,13 @@
   dependencies:
     is-path-inside "^1.0.0"
 
+is-path-in-cwd@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb"
+  integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==
+  dependencies:
+    is-path-inside "^2.1.0"
+
 is-path-inside@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
@@ -6172,6 +6231,18 @@
   dependencies:
     path-is-inside "^1.0.1"
 
+is-path-inside@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2"
+  integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==
+  dependencies:
+    path-is-inside "^1.0.2"
+
+is-plain-obj@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
+  integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
+
 is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
@@ -6654,10 +6725,10 @@
     pretty-format "^23.6.0"
     semver "^5.5.0"
 
-jest-transform-stub@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/jest-transform-stub/-/jest-transform-stub-1.0.0.tgz#e4e941454f31a8bbc4db96b31f46a08b294372b1"
-  integrity sha512-7eilMk4sxi2Fiy223I+BYTS5wJQEGEBqR3D8dy5A6RWmMTnmjipw2ImGDfXzEUBieebyrnitzkJfpNOJSFklLQ==
+jest-transform-stub@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/jest-transform-stub/-/jest-transform-stub-2.0.0.tgz#19018b0851f7568972147a5d60074b55f0225a7d"
+  integrity sha512-lspHaCRx/mBbnm3h4uMMS3R5aZzMwyNpNIJLXj4cEsV0mIUtS4IjYJLSoyjRCtnxb6RIGJ4NL2quZzfIeNhbkg==
 
 jest-util@^23.4.0:
   version "23.4.0"
@@ -6683,7 +6754,19 @@
     leven "^2.1.0"
     pretty-format "^23.6.0"
 
-jest-watcher@^23.4.0:
+jest-watch-typeahead@0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-0.2.1.tgz#6c40f232996ca6c39977e929e9f79b189e7d87e4"
+  integrity sha512-xdhEtKSj0gmnkDQbPTIHvcMmXNUDzYpHLEJ5TFqlaI+schi2NI96xhWiZk9QoesAS7oBmKwWWsHazTrYl2ORgg==
+  dependencies:
+    ansi-escapes "^3.0.0"
+    chalk "^2.4.1"
+    jest-watcher "^23.1.0"
+    slash "^2.0.0"
+    string-length "^2.0.0"
+    strip-ansi "^5.0.0"
+
+jest-watcher@^23.1.0, jest-watcher@^23.4.0:
   version "23.4.0"
   resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-23.4.0.tgz#d2e28ce74f8dad6c6afc922b92cabef6ed05c91c"
   integrity sha1-0uKM50+NrWxq/JIrksq+9u0FyRw=
@@ -6722,14 +6805,14 @@
   integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==
 
 js-beautify@^1.6.12, js-beautify@^1.6.14:
-  version "1.8.9"
-  resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.9.tgz#08e3c05ead3ecfbd4f512c3895b1cda76c87d523"
-  integrity sha512-MwPmLywK9RSX0SPsUJjN7i+RQY9w/yC17Lbrq9ViEefpLRgqAR2BgrMN2AbifkUuhDV8tRauLhLda/9+bE0YQA==
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.0.tgz#9753a13c858d96828658cd18ae3ca0e5783ea672"
+  integrity sha512-OMwf/tPDpE/BLlYKqZOhqWsd3/z2N3KOlyn1wsCRGFwViE8LOQTcDtathQvHvZc+q+zWmcNAbwKSC+iJoMaH2Q==
   dependencies:
     config-chain "^1.1.12"
-    editorconfig "^0.15.2"
+    editorconfig "^0.15.3"
     glob "^7.1.3"
-    mkdirp "~0.5.0"
+    mkdirp "~0.5.1"
     nopt "~4.0.1"
 
 js-levenshtein@^1.1.3:
@@ -6764,10 +6847,10 @@
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
   integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
 
-js-yaml@^3.12.0, js-yaml@^3.7.0, js-yaml@^3.9.0, js-yaml@^3.9.1:
-  version "3.12.1"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600"
-  integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==
+js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.7.0, js-yaml@^3.9.0, js-yaml@^3.9.1:
+  version "3.13.1"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
+  integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
   dependencies:
     argparse "^1.0.7"
     esprima "^4.0.0"
@@ -6943,7 +7026,7 @@
     is-promise "^2.0.0"
     promise "^7.0.1"
 
-killable@^1.0.0:
+killable@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
   integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==
@@ -7041,9 +7124,9 @@
     strip-bom "^2.0.0"
 
 loader-fs-cache@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz#56e0bf08bd9708b26a765b68509840c8dec9fdbc"
-  integrity sha1-VuC/CL2XCLJqdltoUJhAyN7J/bw=
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.2.tgz#54cedf6b727e1779fd8f01205f05f6e88706f086"
+  integrity sha512-70IzT/0/L+M20jUlEqZhZyArTU6VKLRTYRDAYN26g4jfzpJqjipLL3/hgYpySqI9PwsVRHHFja0LfEmsx9X2Cw==
   dependencies:
     find-cache-dir "^0.1.1"
     mkdirp "0.5.1"
@@ -7163,11 +7246,6 @@
   resolved "https://registry.yarnpkg.com/lodash._stack/-/lodash._stack-4.1.3.tgz#751aa76c1b964b047e76d14fc72a093fcb5e2dd0"
   integrity sha1-dRqnbBuWSwR+dtFPxyoJP8teLdA=
 
-lodash.assign@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
-  integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=
-
 lodash.clone@3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-3.0.3.tgz#84688c73d32b5a90ca25616963f189252a997043"
@@ -7177,11 +7255,6 @@
     lodash._bindcallback "^3.0.0"
     lodash._isiterateecall "^3.0.0"
 
-lodash.clonedeep@^4.3.2:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
-  integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
-
 lodash.create@3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7"
@@ -7257,12 +7330,7 @@
   resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
   integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
 
-lodash.merge@^4.6.0:
-  version "4.6.1"
-  resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
-  integrity sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==
-
-lodash.mergewith@^4.0.0, lodash.mergewith@^4.6.0:
+lodash.mergewith@^4.0.0:
   version "4.6.1"
   resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
   integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==
@@ -7297,7 +7365,7 @@
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@4.x, lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@~4.17.10:
+lodash@4.x, lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@~4.17.10:
   version "4.17.11"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
   integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
@@ -7309,7 +7377,7 @@
   dependencies:
     chalk "^2.0.1"
 
-loglevel@^1.4.1:
+loglevel@^1.6.1:
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa"
   integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=
@@ -7339,7 +7407,7 @@
   resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
   integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
 
-lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache@^4.1.3:
+lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache@^4.1.5:
   version "4.1.5"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
   integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
@@ -7366,6 +7434,14 @@
   dependencies:
     pify "^3.0.0"
 
+make-dir@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
+  integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
+  dependencies:
+    pify "^4.0.1"
+    semver "^5.6.0"
+
 makeerror@1.0.x:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
@@ -7434,15 +7510,15 @@
     mimic-fn "^1.0.0"
 
 mem@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/mem/-/mem-4.1.0.tgz#aeb9be2d21f47e78af29e4ac5978e8afa2ca5b8a"
-  integrity sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
+  integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==
   dependencies:
     map-age-cleaner "^0.1.1"
-    mimic-fn "^1.0.0"
+    mimic-fn "^2.0.0"
     p-is-promise "^2.0.0"
 
-memory-fs@^0.4.0, memory-fs@~0.4.1:
+memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
   integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=
@@ -7519,7 +7595,7 @@
     parse-glob "^3.0.4"
     regex-cache "^0.4.2"
 
-micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8, micromatch@^3.1.9:
+micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8:
   version "3.1.10"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
   integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
@@ -7546,22 +7622,17 @@
     bn.js "^4.0.0"
     brorand "^1.0.1"
 
-"mime-db@>= 1.36.0 < 2":
-  version "1.38.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad"
-  integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==
-
-mime-db@~1.37.0:
-  version "1.37.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8"
-  integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==
-
-mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.19:
-  version "2.1.21"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
-  integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==
-  dependencies:
-    mime-db "~1.37.0"
+mime-db@1.40.0, "mime-db@>= 1.40.0 < 2":
+  version "1.40.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
+  integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
+
+mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
+  version "2.1.24"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
+  integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
+  dependencies:
+    mime-db "1.40.0"
 
 mime@1.4.1:
   version "1.4.1"
@@ -7569,21 +7640,27 @@
   integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
 
 mime@^2.0.3, mime@^2.3.1:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6"
-  integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.2.tgz#ce5229a5e99ffc313abac806b482c10e7ba6ac78"
+  integrity sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==
 
 mimic-fn@^1.0.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
   integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
 
-mini-css-extract-plugin@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0"
-  integrity sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==
+mimic-fn@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+  integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+
+mini-css-extract-plugin@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.6.0.tgz#a3f13372d6fcde912f3ee4cd039665704801e3b9"
+  integrity sha512-79q5P7YGI6rdnVyIAV4NXpBQJFWdkzJxCim3Kog4078fM0piAaFlwocqbejdWtLW1cEzCexPrh6EdyFsPgVdAw==
   dependencies:
     loader-utils "^1.1.0"
+    normalize-url "^2.0.1"
     schema-utils "^1.0.0"
     webpack-sources "^1.1.0"
 
@@ -7783,10 +7860,19 @@
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
   integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
 
-nan@^2.10.0, nan@^2.9.2:
-  version "2.12.1"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552"
-  integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==
+mz@^2.4.0:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
+  integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
+  dependencies:
+    any-promise "^1.0.0"
+    object-assign "^4.0.1"
+    thenify-all "^1.0.0"
+
+nan@^2.12.1, nan@^2.13.2:
+  version "2.13.2"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7"
+  integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==
 
 nanomatch@^1.2.9:
   version "1.2.13"
@@ -7811,18 +7897,18 @@
   integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
 
 needle@^2.2.1:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e"
-  integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==
-  dependencies:
-    debug "^2.1.2"
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.1.tgz#d272f2f4034afb9c4c9ab1379aabc17fc85c9388"
+  integrity sha512-CaLXV3W8Vnbps8ZANqDGz7j4x7Yj1LW4TWF/TQuDfj7Cfx4nAPTvw98qgTevtto1oHDrh3pQkaODbqupXlsWTg==
+  dependencies:
+    debug "^4.1.0"
     iconv-lite "^0.4.4"
     sax "^1.2.4"
 
-negotiator@0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
-  integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=
+negotiator@0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
+  integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
 
 neo-async@^2.5.0, neo-async@^2.6.0:
   version "2.6.0"
@@ -7947,10 +8033,10 @@
     shellwords "^0.1.1"
     which "^1.3.0"
 
-node-pre-gyp@^0.10.0:
-  version "0.10.3"
-  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc"
-  integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==
+node-pre-gyp@^0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149"
+  integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==
   dependencies:
     detect-libc "^1.0.2"
     mkdirp "^0.5.1"
@@ -7963,17 +8049,17 @@
     semver "^5.3.0"
     tar "^4"
 
-node-releases@^1.1.3:
-  version "1.1.7"
-  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.7.tgz#b09a10394d0ed8f7778f72bb861dde68b146303b"
-  integrity sha512-bKdrwaqJUPHqlCzDD7so/R+Nk0jGv9a11ZhLrD9f6i947qGLrGAhU3OxRENa19QQmwzGy/g6zCDEuLGDO8HPvA==
+node-releases@^1.1.17:
+  version "1.1.17"
+  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.17.tgz#71ea4631f0a97d5cd4f65f7d04ecf9072eac711a"
+  integrity sha512-/SCjetyta1m7YXLgtACZGDYJdCSIBAWorDWkGCGZlydP2Ll7J48l7j/JxNYZ+xsgSPbWfdulVS/aY+GdjUsQ7Q==
   dependencies:
     semver "^5.3.0"
 
 node-sass@^4.10.0:
-  version "4.11.0"
-  resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a"
-  integrity sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==
+  version "4.12.0"
+  resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017"
+  integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==
   dependencies:
     async-foreach "^0.1.3"
     chalk "^1.1.1"
@@ -7982,12 +8068,10 @@
     get-stdin "^4.0.1"
     glob "^7.0.3"
     in-publish "^2.0.0"
-    lodash.assign "^4.2.0"
-    lodash.clonedeep "^4.3.2"
-    lodash.mergewith "^4.6.0"
+    lodash "^4.17.11"
     meow "^3.7.0"
     mkdirp "^0.5.1"
-    nan "^2.10.0"
+    nan "^2.13.2"
     node-gyp "^3.8.0"
     npmlog "^4.0.0"
     request "^2.88.0"
@@ -8010,7 +8094,7 @@
     abbrev "1"
     osenv "^0.1.4"
 
-normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
+normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.5.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
   integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
@@ -8042,6 +8126,15 @@
   resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
   integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
 
+normalize-url@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
+  integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==
+  dependencies:
+    prepend-http "^2.0.0"
+    query-string "^5.0.1"
+    sort-keys "^2.0.0"
+
 normalize-url@^3.0.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
@@ -8053,9 +8146,9 @@
   integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
 
 npm-packlist@^1.1.6:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.3.0.tgz#7f01e8e44408341379ca98cfd756e7b29bd2626c"
-  integrity sha512-qPBc6CnxEzpOcc4bjoIBJbYdy0D/LFFPUdxvfwor4/w3vxeE0h6TiOVurCEPpQ6trjN77u/ShyfeJGsbAfB3dA==
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc"
+  integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==
   dependencies:
     ignore-walk "^3.0.1"
     npm-bundled "^1.0.1"
@@ -8100,9 +8193,9 @@
   integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==
 
 nwsapi@^2.0.7:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.0.tgz#781065940aed90d9bb01ca5d0ce0fcf81c32712f"
-  integrity sha512-ZG3bLAvdHmhIjaQ/Db1qvBxsGvFMLIRpQszyqbg31VJ53UP++uZX1/gf3Ut96pdwN9AuDwlMqIYLm0UPCdUeHg==
+  version "2.1.4"
+  resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.4.tgz#e006a878db23636f8e8a67d33ca0e4edf61a842f"
+  integrity sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==
 
 oauth-sign@~0.9.0:
   version "0.9.0"
@@ -8129,9 +8222,9 @@
   integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
 
 object-keys@^1.0.11, object-keys@^1.0.12:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032"
-  integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+  integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
 
 object-visit@^1.0.0:
   version "1.0.1"
@@ -8173,7 +8266,7 @@
   dependencies:
     isobject "^3.0.1"
 
-object.values@^1.0.4:
+object.values@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.0.tgz#bf6810ef5da3e5325790eaaa2be213ea84624da9"
   integrity sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==
@@ -8189,9 +8282,9 @@
   integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
 
 ol@^5.3.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/ol/-/ol-5.3.0.tgz#106b870561fabb9b790b5869b3d93025cb03d389"
-  integrity sha512-UrOJGNI5XdYfE9n43RJdsMq25SjI4nIi5Kf0kxi+q6vEknzeRxM/wgYf8FMs7Ss3URuIbsKmetW9dVMOYB/DkQ==
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/ol/-/ol-5.3.2.tgz#dfc70b315b2dcce3cb4b9b79a2e9eb4ef856dd72"
+  integrity sha512-PfS8Fe1iy4YNJ7P+TvebKME+8gp5NBfQuIldAHfBCkc7agmTezscQrsJWggz5B6Sprm/M/4YBtbyQtw4pIC65w==
   dependencies:
     pbf "3.1.0"
     pixelworks "1.1.0"
@@ -8209,10 +8302,10 @@
   dependencies:
     ee-first "1.1.1"
 
-on-headers@~1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
-  integrity sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=
+on-headers@~1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
+  integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
 
 once@^1.3.0, once@^1.3.1, once@^1.4.0:
   version "1.4.0"
@@ -8233,10 +8326,10 @@
   resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
   integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
 
-opn@^5.1.0, opn@^5.3.0:
-  version "5.4.0"
-  resolved "https://registry.yarnpkg.com/opn/-/opn-5.4.0.tgz#cb545e7aab78562beb11aa3bfabc7042e1761035"
-  integrity sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==
+opn@^5.3.0, opn@^5.5.0:
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
+  integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==
   dependencies:
     is-wsl "^1.1.0"
 
@@ -8260,16 +8353,16 @@
     type-check "~0.3.2"
     wordwrap "~1.0.0"
 
-ora@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/ora/-/ora-3.1.0.tgz#dbedd8c03b5d017fb67083e87ee52f5ec89823ed"
-  integrity sha512-vRBPaNCclUi8pUxRF/G8+5qEQkc6EgzKK1G2ZNJUIGu088Un5qIxFXeDgymvPRM9nmrcUOGzQgS1Vmtz+NtlMw==
+ora@^3.4.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318"
+  integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==
   dependencies:
     chalk "^2.4.2"
     cli-cursor "^2.1.0"
-    cli-spinners "^1.3.1"
+    cli-spinners "^2.0.0"
     log-symbols "^2.2.0"
-    strip-ansi "^5.0.0"
+    strip-ansi "^5.2.0"
     wcwidth "^1.0.1"
 
 original@^1.0.0:
@@ -8305,7 +8398,7 @@
     lcid "^1.0.0"
     mem "^1.1.0"
 
-os-locale@^3.0.0:
+os-locale@^3.0.0, os-locale@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
   integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
@@ -8338,9 +8431,9 @@
   integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
 
 p-is-promise@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.0.0.tgz#7554e3d572109a87e1f3f53f6a7d85d1b194f4c5"
-  integrity sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
+  integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
 
 p-limit@^1.0.0, p-limit@^1.1.0:
   version "1.3.0"
@@ -8350,9 +8443,9 @@
     p-try "^1.0.0"
 
 p-limit@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.1.0.tgz#1d5a0d20fb12707c758a655f6bbc4386b5930d68"
-  integrity sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2"
+  integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==
   dependencies:
     p-try "^2.0.0"
 
@@ -8375,15 +8468,20 @@
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
   integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
 
+p-map@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
+  integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
+
 p-try@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
   integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
 
 p-try@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
-  integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+  integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
 
 pac-proxy-agent@1:
   version "1.1.0"
@@ -8412,9 +8510,9 @@
     thunkify "~2.1.1"
 
 pako@~1.0.5:
-  version "1.0.8"
-  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.8.tgz#6844890aab9c635af868ad5fecc62e8acbba3ea4"
-  integrity sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
+  integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==
 
 parallel-transform@^1.1.0:
   version "1.1.0"
@@ -8433,9 +8531,9 @@
     no-case "^2.2.0"
 
 parse-asn1@^5.0.0:
-  version "5.1.3"
-  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.3.tgz#1600c6cc0727365d68b97f3aa78939e735a75204"
-  integrity sha512-VrPoetlz7B/FqjBLD2f5wBVZvsZVLnRUrxVLfRYhGXCODa/NWE4p3Wp+6+aV3ZPL3KM7/OZmxDIwwijD7yuucg==
+  version "5.1.4"
+  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc"
+  integrity sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==
   dependencies:
     asn1.js "^4.0.0"
     browserify-aes "^1.0.0"
@@ -8474,7 +8572,7 @@
   resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
   integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
 
-parse5@4.0.0:
+parse5@4.0.0, parse5@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
   integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==
@@ -8492,9 +8590,9 @@
     "@types/node" "*"
 
 parseurl@~1.3.2:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
-  integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+  integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
 
 pascalcase@^0.1.1:
   version "0.1.1"
@@ -8654,6 +8752,13 @@
   dependencies:
     find-up "^3.0.0"
 
+pkg-up@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f"
+  integrity sha1-yBmscoBZpGHKscOImivjxJoATX8=
+  dependencies:
+    find-up "^2.1.0"
+
 pluralize@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
@@ -8669,12 +8774,12 @@
   resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.0.11.tgz#35aff58c17491d127a07336d5522ebc9df57c954"
   integrity sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg==
 
-popper.js@^1.12.9:
-  version "1.14.7"
-  resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.7.tgz#e31ec06cfac6a97a53280c3e55e4e0c860e7738e"
-  integrity sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ==
-
-portfinder@^1.0.20, portfinder@^1.0.9:
+popper.js@^1.15.0:
+  version "1.15.0"
+  resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2"
+  integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==
+
+portfinder@^1.0.20:
   version "1.0.20"
   resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.20.tgz#bea68632e54b2e13ab7b0c4775e9b41bf270e44a"
   integrity sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==
@@ -8688,7 +8793,7 @@
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
   integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
 
-postcss-calc@^7.0.0:
+postcss-calc@^7.0.1:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.1.tgz#36d77bab023b0ecbb9789d84dcb23c4941145436"
   integrity sha512-oXqx0m6tb4N3JGdmeMSc/i91KppbYsFZKdH0xMOqK8V1rJlzrKlTdokz8ozUXLVejydRN6u2IddxpcijRj2FqQ==
@@ -8698,10 +8803,10 @@
     postcss-selector-parser "^5.0.0-rc.4"
     postcss-value-parser "^3.3.1"
 
-postcss-colormin@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.2.tgz#93cd1fa11280008696887db1a528048b18e7ed99"
-  integrity sha512-1QJc2coIehnVFsz0otges8kQLsryi4lo19WD+U5xCWvXd0uw/Z+KKYnbiNDCnO9GP+PvErPHCG0jNvWTngk9Rw==
+postcss-colormin@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381"
+  integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==
   dependencies:
     browserslist "^4.0.0"
     color "^3.0.0"
@@ -8717,10 +8822,10 @@
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
-postcss-discard-comments@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.1.tgz#30697735b0c476852a7a11050eb84387a67ef55d"
-  integrity sha512-Ay+rZu1Sz6g8IdzRjUgG2NafSNpp2MSMOQUb+9kkzzzP+kh07fP0yNbhtFejURnyVXSX3FYy2nVNW1QTnNjgBQ==
+postcss-discard-comments@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033"
+  integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==
   dependencies:
     postcss "^7.0.0"
 
@@ -8763,20 +8868,20 @@
     postcss-load-config "^2.0.0"
     schema-utils "^1.0.0"
 
-postcss-merge-longhand@^4.0.10:
-  version "4.0.10"
-  resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.10.tgz#c4d63ab57bdc054ab4067ab075d488c8c2978380"
-  integrity sha512-hME10s6CSjm9nlVIcO1ukR7Jr5RisTaaC1y83jWCivpuBtPohA3pZE7cGTIVSYjXvLnXozHTiVOkG4dnnl756g==
+postcss-merge-longhand@^4.0.11:
+  version "4.0.11"
+  resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24"
+  integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==
   dependencies:
     css-color-names "0.0.4"
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
     stylehacks "^4.0.0"
 
-postcss-merge-rules@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.2.tgz#2be44401bf19856f27f32b8b12c0df5af1b88e74"
-  integrity sha512-UiuXwCCJtQy9tAIxsnurfF0mrNHKc4NnNx6NxqmzNNjXpQwLSukUxELHTRF0Rg1pAmcoKLih8PwvZbiordchag==
+postcss-merge-rules@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650"
+  integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==
   dependencies:
     browserslist "^4.0.0"
     caniuse-api "^3.0.0"
@@ -8793,20 +8898,20 @@
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
-postcss-minify-gradients@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.1.tgz#6da95c6e92a809f956bb76bf0c04494953e1a7dd"
-  integrity sha512-pySEW3E6Ly5mHm18rekbWiAjVi/Wj8KKt2vwSfVFAWdW6wOIekgqxKxLU7vJfb107o3FDNPkaYFCxGAJBFyogA==
+postcss-minify-gradients@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471"
+  integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==
   dependencies:
     cssnano-util-get-arguments "^4.0.0"
     is-color-stop "^1.0.0"
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
-postcss-minify-params@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.1.tgz#5b2e2d0264dd645ef5d68f8fec0d4c38c1cf93d2"
-  integrity sha512-h4W0FEMEzBLxpxIVelRtMheskOKKp52ND6rJv+nBS33G1twu2tCyurYj/YtgU76+UDCvWeNs0hs8HFAWE2OUFg==
+postcss-minify-params@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874"
+  integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==
   dependencies:
     alphanum-sort "^1.0.0"
     browserslist "^4.0.0"
@@ -8815,10 +8920,10 @@
     postcss-value-parser "^3.0.0"
     uniqs "^2.0.0"
 
-postcss-minify-selectors@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.1.tgz#a891c197977cc37abf60b3ea06b84248b1c1e9cd"
-  integrity sha512-8+plQkomve3G+CodLCgbhAKrb5lekAnLYuL1d7Nz+/7RANpBEVdgBkPNwljfSKvZ9xkkZTZITd04KP+zeJTJqg==
+postcss-minify-selectors@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8"
+  integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==
   dependencies:
     alphanum-sort "^1.0.0"
     has "^1.0.0"
@@ -8863,48 +8968,48 @@
   dependencies:
     postcss "^7.0.0"
 
-postcss-normalize-display-values@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.1.tgz#d9a83d47c716e8a980f22f632c8b0458cfb48a4c"
-  integrity sha512-R5mC4vaDdvsrku96yXP7zak+O3Mm9Y8IslUobk7IMP+u/g+lXvcN4jngmHY5zeJnrQvE13dfAg5ViU05ZFDwdg==
+postcss-normalize-display-values@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a"
+  integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==
   dependencies:
     cssnano-util-get-match "^4.0.0"
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
-postcss-normalize-positions@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.1.tgz#ee2d4b67818c961964c6be09d179894b94fd6ba1"
-  integrity sha512-GNoOaLRBM0gvH+ZRb2vKCIujzz4aclli64MBwDuYGU2EY53LwiP7MxOZGE46UGtotrSnmarPPZ69l2S/uxdaWA==
+postcss-normalize-positions@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f"
+  integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==
   dependencies:
     cssnano-util-get-arguments "^4.0.0"
     has "^1.0.0"
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
-postcss-normalize-repeat-style@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.1.tgz#5293f234b94d7669a9f805495d35b82a581c50e5"
-  integrity sha512-fFHPGIjBUyUiswY2rd9rsFcC0t3oRta4wxE1h3lpwfQZwFeFjXFSiDtdJ7APCmHQOnUZnqYBADNRPKPwFAONgA==
+postcss-normalize-repeat-style@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c"
+  integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==
   dependencies:
     cssnano-util-get-arguments "^4.0.0"
     cssnano-util-get-match "^4.0.0"
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
-postcss-normalize-string@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.1.tgz#23c5030c2cc24175f66c914fa5199e2e3c10fef3"
-  integrity sha512-IJoexFTkAvAq5UZVxWXAGE0yLoNN/012v7TQh5nDo6imZJl2Fwgbhy3J2qnIoaDBrtUP0H7JrXlX1jjn2YcvCQ==
+postcss-normalize-string@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c"
+  integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==
   dependencies:
     has "^1.0.0"
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
-postcss-normalize-timing-functions@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.1.tgz#8be83e0b9cb3ff2d1abddee032a49108f05f95d7"
-  integrity sha512-1nOtk7ze36+63ONWD8RCaRDYsnzorrj+Q6fxkQV+mlY5+471Qx9kspqv0O/qQNMeApg8KNrRf496zHwJ3tBZ7w==
+postcss-normalize-timing-functions@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9"
+  integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==
   dependencies:
     cssnano-util-get-match "^4.0.0"
     postcss "^7.0.0"
@@ -8929,37 +9034,37 @@
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
-postcss-normalize-whitespace@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.1.tgz#d14cb639b61238418ac8bc8d3b7bdd65fc86575e"
-  integrity sha512-U8MBODMB2L+nStzOk6VvWWjZgi5kQNShCyjRhMT3s+W9Jw93yIjOnrEkKYD3Ul7ChWbEcjDWmXq0qOL9MIAnAw==
+postcss-normalize-whitespace@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82"
+  integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==
   dependencies:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
-postcss-ordered-values@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.1.tgz#2e3b432ef3e489b18333aeca1f1295eb89be9fc2"
-  integrity sha512-PeJiLgJWPzkVF8JuKSBcylaU+hDJ/TX3zqAMIjlghgn1JBi6QwQaDZoDIlqWRcCAI8SxKrt3FCPSRmOgKRB97Q==
+postcss-ordered-values@^4.1.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee"
+  integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==
   dependencies:
     cssnano-util-get-arguments "^4.0.0"
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
-postcss-reduce-initial@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.2.tgz#bac8e325d67510ee01fa460676dc8ea9e3b40f15"
-  integrity sha512-epUiC39NonKUKG+P3eAOKKZtm5OtAtQJL7Ye0CBN1f+UQTHzqotudp+hki7zxXm7tT0ZAKDMBj1uihpPjP25ug==
+postcss-reduce-initial@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df"
+  integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==
   dependencies:
     browserslist "^4.0.0"
     caniuse-api "^3.0.0"
     has "^1.0.0"
     postcss "^7.0.0"
 
-postcss-reduce-transforms@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.1.tgz#8600d5553bdd3ad640f43bff81eb52f8760d4561"
-  integrity sha512-sZVr3QlGs0pjh6JAIe6DzWvBaqYw05V1t3d9Tp+VnFRT5j+rsqoWsysh/iSD7YNsULjq9IAylCznIwVd5oU/zA==
+postcss-reduce-transforms@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29"
+  integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==
   dependencies:
     cssnano-util-get-match "^4.0.0"
     has "^1.0.0"
@@ -8975,7 +9080,7 @@
     indexes-of "^1.0.1"
     uniq "^1.0.1"
 
-postcss-selector-parser@^5.0.0, postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4:
+postcss-selector-parser@^5.0.0, postcss-selector-parser@^5.0.0-rc.4:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c"
   integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==
@@ -8984,10 +9089,19 @@
     indexes-of "^1.0.1"
     uniq "^1.0.1"
 
-postcss-svgo@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.1.tgz#5628cdb38f015de6b588ce6d0bf0724b492b581d"
-  integrity sha512-YD5uIk5NDRySy0hcI+ZJHwqemv2WiqqzDgtvgMzO8EGSkK5aONyX8HMVFRFJSdO8wUWTuisUFn/d7yRRbBr5Qw==
+postcss-selector-parser@^6.0.0:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
+  integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==
+  dependencies:
+    cssesc "^3.0.0"
+    indexes-of "^1.0.1"
+    uniq "^1.0.1"
+
+postcss-svgo@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258"
+  integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==
   dependencies:
     is-svg "^3.0.0"
     postcss "^7.0.0"
@@ -9018,9 +9132,9 @@
     supports-color "^5.4.0"
 
 postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.5:
-  version "7.0.14"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5"
-  integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==
+  version "7.0.16"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.16.tgz#48f64f1b4b558cb8b52c88987724359acb010da2"
+  integrity sha512-MOo8zNSlIqh22Uaa3drkdIAgUGEL+AD1ESiSdmElLUmE2uVDo1QloiT/IfW9qRw8Gw+Y/w69UVMGwbufMSftxA==
   dependencies:
     chalk "^2.4.2"
     source-map "^0.6.1"
@@ -9031,6 +9145,11 @@
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
   integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
 
+prepend-http@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
+  integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
+
 preserve@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
@@ -9049,9 +9168,9 @@
   integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==
 
 prettier@^1.13.0, prettier@^1.15.2, prettier@^1.15.3:
-  version "1.16.4"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"
-  integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==
+  version "1.17.0"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.0.tgz#53b303676eed22cc14a9f0cec09b477b3026c008"
+  integrity sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==
 
 pretty-error@^2.0.2:
   version "2.1.1"
@@ -9146,12 +9265,12 @@
   integrity sha512-Xdayp8sB/mU+sUV4G7ws8xtYMGdQnxbeIfLjyO9TZZRJdztBGhlmbI5x1qcY4TG5hBkIKGnc28i7nXxaugu88w==
 
 proxy-addr@~2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93"
-  integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
+  integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
   dependencies:
     forwarded "~0.1.2"
-    ipaddr.js "1.8.0"
+    ipaddr.js "1.9.0"
 
 proxy-agent@2.0.0:
   version "2.0.0"
@@ -9340,22 +9459,22 @@
   integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
 
 purgecss-webpack-plugin@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/purgecss-webpack-plugin/-/purgecss-webpack-plugin-1.4.0.tgz#accf6f8f41a7d48fe830da16a4c94d1ab884d6c0"
-  integrity sha512-kCVR8RvmtJ6IwzxMBNFmAucItyvY6db0Ui5DBmQHCe8GvY2ST03a26wFCU8XwfzN8gpKUGZPyuD3OtL+9WOT0w==
-  dependencies:
-    purgecss "^1.1.0"
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/purgecss-webpack-plugin/-/purgecss-webpack-plugin-1.5.0.tgz#18c0fb8815d79364a80d2701b8d62ba6bc2f8cc0"
+  integrity sha512-ZSU6lok2DuDBuR7VCte5V12eke0Tx8xsCKxMbOnMfuJNPccPGv4jflRUm2Wvr2yGB8lFzKNZaTWaSk9g3kCv5A==
+  dependencies:
+    purgecss "^1.3.0"
     webpack-sources "^1.3.0"
 
-purgecss@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-1.1.0.tgz#0d27c18bb4070d246a845ea22697271eabb59ffe"
-  integrity sha512-/XYpiMvbehpeJqxu8k0hzCai9F2RQGjprjpJzRMq9e2qkT8Fk7AW9zLr7bAuqQfxgMIV/+DTNlks3Ckn6J9WEw==
-  dependencies:
-    glob "^7.1.2"
-    postcss "^7.0.0"
-    postcss-selector-parser "^5.0.0-rc.3"
-    yargs "^12.0.1"
+purgecss@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-1.3.0.tgz#fc3c303df9a74a75547545b2c0da28a9ec63da00"
+  integrity sha512-0UMnr8aUsPO7RbzAT72UELRvwMHhadtuunDm7rcgRS6b8pCVO8yglIqikiYFwQk2XP606mk+GpjI1G74Auxgtg==
+  dependencies:
+    glob "^7.1.3"
+    postcss "^7.0.14"
+    postcss-selector-parser "^6.0.0"
+    yargs "^13.2.2"
 
 q@1.4.1:
   version "1.4.1"
@@ -9372,6 +9491,15 @@
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
   integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
 
+query-string@^5.0.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
+  integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==
+  dependencies:
+    decode-uri-component "^0.2.0"
+    object-assign "^4.1.0"
+    strict-uri-encode "^1.0.0"
+
 querystring-es3@^0.2.0:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -9382,10 +9510,10 @@
   resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
   integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
 
-querystringify@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef"
-  integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==
+querystringify@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
+  integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
 
 quickselect@^1.0.1:
   version "1.1.1"
@@ -9402,9 +9530,9 @@
     math-random "^1.0.1"
 
 randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80"
-  integrity sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+  integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
   dependencies:
     safe-buffer "^5.1.0"
 
@@ -9421,7 +9549,17 @@
   resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
   integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
 
-raw-body@2, raw-body@2.3.3:
+raw-body@2:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
+  integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
+  dependencies:
+    bytes "3.1.0"
+    http-errors "1.7.2"
+    iconv-lite "0.4.24"
+    unpipe "1.0.0"
+
+raw-body@2.3.3:
   version "2.3.3"
   resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3"
   integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==
@@ -9474,6 +9612,16 @@
     parse-json "^4.0.0"
     pify "^3.0.0"
 
+read-pkg@^5.0.0:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.1.1.tgz#5cf234dde7a405c90c88a519ab73c467e9cb83f5"
+  integrity sha512-dFcTLQi6BZ+aFUaICg7er+/usEoqFdQxiEBsEMNGoipenihtxxtdrQuBXvyANCEI8VuUIVYFgeHGx9sLLvim4w==
+  dependencies:
+    "@types/normalize-package-data" "^2.4.0"
+    normalize-package-data "^2.5.0"
+    parse-json "^4.0.0"
+    type-fest "^0.4.1"
+
 "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
   version "2.3.6"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
@@ -9487,16 +9635,6 @@
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
-readable-stream@1.0:
-  version "1.0.34"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
-  integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.1"
-    isarray "0.0.1"
-    string_decoder "~0.10.x"
-
 readable-stream@1.1.x:
   version "1.1.14"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
@@ -9507,10 +9645,10 @@
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
-readable-stream@3, readable-stream@^3.0.6:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06"
-  integrity sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==
+readable-stream@3, readable-stream@^3.0.6, readable-stream@^3.1.1:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.3.0.tgz#cb8011aad002eb717bf040291feba8569c986fb9"
+  integrity sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==
   dependencies:
     inherits "^2.0.3"
     string_decoder "^1.1.1"
@@ -9526,9 +9664,9 @@
     readable-stream "^2.0.2"
 
 realpath-native@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.2.tgz#cd51ce089b513b45cf9b1516c82989b51ccc6560"
-  integrity sha512-+S3zTvVt9yTntFrBpm7TQmQ3tzpCrnA1a/y+3cUHAc9ZR6aIjG0WNLR+Rj79QpJktY+VeW/TQtFlQ1bzsehI8g==
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
+  integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==
   dependencies:
     util.promisify "^1.0.0"
 
@@ -9540,10 +9678,10 @@
     indent-string "^2.1.0"
     strip-indent "^1.0.1"
 
-regenerate-unicode-properties@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c"
-  integrity sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw==
+regenerate-unicode-properties@^8.0.2:
+  version "8.0.2"
+  resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.0.2.tgz#7b38faa296252376d363558cfbda90c9ce709662"
+  integrity sha512-SbA/iNrBUf6Pv2zU8Ekv1Qbhv92yxL4hiDa2siuxs4KKn4oOoMDHXjAf7+Nz9qinUQ46B1LcWEi/PhJfPWpZWQ==
   dependencies:
     regenerate "^1.4.0"
 
@@ -9557,15 +9695,15 @@
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
   integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
 
-regenerator-runtime@^0.12.0:
-  version "0.12.1"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
-  integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
-
-regenerator-transform@^0.13.3:
-  version "0.13.3"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb"
-  integrity sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA==
+regenerator-runtime@^0.13.2:
+  version "0.13.2"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
+  integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==
+
+regenerator-transform@^0.13.4:
+  version "0.13.4"
+  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.4.tgz#18f6763cf1382c69c36df76c6ce122cc694284fb"
+  integrity sha512-T0QMBjK3J0MtxjPmdIMXm72Wvj2Abb0Bd4HADdfijwMdoIsyQZ6fWC7kDFhk2YinBBEMZDL7Y7wh0J1sGx3S4A==
   dependencies:
     private "^0.1.6"
 
@@ -9585,13 +9723,9 @@
     safe-regex "^1.1.0"
 
 regexp-tree@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.1.tgz#27b455f9b138ca2e84c090e9aff1ffe2a04d97fa"
-  integrity sha512-HwRjOquc9QOwKTgbxvZTcddS5mlNlwePMQ3NFL8broajMLD5CXDAqas8Y5yxJH5QtZp5iRor3YCILd5pz71Cgw==
-  dependencies:
-    cli-table3 "^0.5.0"
-    colors "^1.1.2"
-    yargs "^12.0.5"
+  version "0.1.6"
+  resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.6.tgz#84900fa12fdf428a2ac25f04300382a7c0148479"
+  integrity sha512-LFrA98Dw/heXqDojz7qKFdygZmFoiVlvE1Zp7Cq2cvF+ZA+03Gmhy0k0PQlsC1jvHPiTUSs+pDHEuSWv6+6D7w==
 
 regexpp@^1.0.1:
   version "1.1.0"
@@ -9607,17 +9741,17 @@
     regjsgen "^0.2.0"
     regjsparser "^0.1.4"
 
-regexpu-core@^4.1.3, regexpu-core@^4.2.0:
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.4.0.tgz#8d43e0d1266883969720345e70c275ee0aec0d32"
-  integrity sha512-eDDWElbwwI3K0Lo6CqbQbA6FwgtCz4kYTarrri1okfkRLZAqstU+B3voZBCjg8Fl6iq0gXrJG6MvRgLthfvgOA==
+regexpu-core@^4.5.4:
+  version "4.5.4"
+  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae"
+  integrity sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==
   dependencies:
     regenerate "^1.4.0"
-    regenerate-unicode-properties "^7.0.0"
+    regenerate-unicode-properties "^8.0.2"
     regjsgen "^0.5.0"
     regjsparser "^0.6.0"
     unicode-match-property-ecmascript "^1.0.4"
-    unicode-match-property-value-ecmascript "^1.0.2"
+    unicode-match-property-value-ecmascript "^1.1.0"
 
 regjsgen@^0.2.0:
   version "0.2.0"
@@ -9654,13 +9788,13 @@
   integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
 
 renderkid@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.2.tgz#12d310f255360c07ad8fde253f6c9e9de372d2aa"
-  integrity sha512-FsygIxevi1jSiPY9h7vZmBFUbAOcbYm9UwyiLNdVsLRs/5We9Ob5NMPbGYUTWiLq5L+ezlVdE0A8bbME5CWTpg==
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149"
+  integrity sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==
   dependencies:
     css-select "^1.1.0"
-    dom-converter "~0.2"
-    htmlparser2 "~3.3.0"
+    dom-converter "^0.2"
+    htmlparser2 "^3.3.0"
     strip-ansi "^3.0.0"
     utila "^0.4.0"
 
@@ -9681,21 +9815,21 @@
   dependencies:
     is-finite "^1.0.0"
 
-request-promise-core@1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6"
-  integrity sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=
-  dependencies:
-    lodash "^4.13.1"
-
-request-promise-native@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.5.tgz#5281770f68e0c9719e5163fd3fab482215f4fda5"
-  integrity sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=
-  dependencies:
-    request-promise-core "1.1.1"
-    stealthy-require "^1.1.0"
-    tough-cookie ">=2.3.3"
+request-promise-core@1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346"
+  integrity sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==
+  dependencies:
+    lodash "^4.17.11"
+
+request-promise-native@^1.0.5, request-promise-native@^1.0.7:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.7.tgz#a49868a624bdea5069f1251d0a836e0d89aa2c59"
+  integrity sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==
+  dependencies:
+    request-promise-core "1.1.2"
+    stealthy-require "^1.1.1"
+    tough-cookie "^2.3.3"
 
 request@^2.55.0, request@^2.87.0, request@^2.88.0:
   version "2.88.0"
@@ -9738,6 +9872,11 @@
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
   integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
 
+require-main-filename@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
+  integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
+
 require-uncached@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
@@ -9751,6 +9890,11 @@
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
   integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
 
+reselect@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
+  integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=
+
 resolve-cwd@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -9793,10 +9937,10 @@
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
   integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
 
-resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.8.1:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
-  integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==
+resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.8.1:
+  version "1.10.1"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18"
+  integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==
   dependencies:
     path-parse "^1.0.6"
 
@@ -9835,7 +9979,7 @@
   dependencies:
     align-text "^0.1.1"
 
-rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@~2.6.2:
+rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2:
   version "2.6.3"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
   integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@@ -9887,9 +10031,9 @@
   integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=
 
 rxjs@^6.3.3:
-  version "6.4.0"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504"
-  integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==
+  version "6.5.1"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.1.tgz#f7a005a9386361921b8524f38f54cbf80e5d08f4"
+  integrity sha512-y0j31WJc83wPu31vS1VlAFW5JGrnGC+j+TtGAa1fRQphy48+fDYiDmX8tjGloToEsMkxnouOg/1IzXGKkJnZMg==
   dependencies:
     tslib "^1.9.0"
 
@@ -9993,7 +10137,7 @@
   resolved "https://registry.yarnpkg.com/selenium-server/-/selenium-server-3.141.59.tgz#cbefdf50aae636ee4c67b819532a8233ce3fd6b0"
   integrity sha512-pL7T1YtAqOEXiBbTx0KdZMkE2U7PYucemd7i0nDLcxcR1APXYZlJfNr5hrvL3mZgwXb7AJEZPINzC6mDU3eP5g==
 
-selfsigned@^1.9.1:
+selfsigned@^1.10.4:
   version "1.10.4"
   resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.4.tgz#cdd7eccfca4ed7635d47a08bf2d5d3074092e2cd"
   integrity sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==
@@ -10001,9 +10145,14 @@
     node-forge "0.7.5"
 
 "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
-  version "5.6.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
-  integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
+  version "5.7.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
+  integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
+
+semver@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65"
+  integrity sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==
 
 semver@~5.0.1:
   version "5.0.3"
@@ -10035,11 +10184,11 @@
     statuses "~1.4.0"
 
 serialize-javascript@^1.4.0:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879"
-  integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==
-
-serve-index@^1.7.2:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65"
+  integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==
+
+serve-index@^1.9.1:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
   integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=
@@ -10097,6 +10246,11 @@
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
   integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
 
+setprototypeof@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
+  integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
+
 sha.js@^2.4.0, sha.js@^2.4.8:
   version "2.4.11"
   resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
@@ -10252,6 +10406,13 @@
     ip "^1.1.4"
     smart-buffer "^1.0.13"
 
+sort-keys@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
+  integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=
+  dependencies:
+    is-plain-obj "^1.0.0"
+
 source-list-map@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@@ -10275,10 +10436,10 @@
   dependencies:
     source-map "^0.5.6"
 
-source-map-support@^0.5.6, source-map-support@~0.5.9:
-  version "0.5.10"
-  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c"
-  integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==
+source-map-support@^0.5.6, source-map-support@~0.5.10:
+  version "0.5.12"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
+  integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==
   dependencies:
     buffer-from "^1.0.0"
     source-map "^0.6.0"
@@ -10332,9 +10493,9 @@
     spdx-license-ids "^3.0.0"
 
 spdx-license-ids@^3.0.0:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e"
-  integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz#75ecd1a88de8c184ef015eafb51b5b48bfd11bb1"
+  integrity sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==
 
 spdy-transport@^3.0.0:
   version "3.0.0"
@@ -10400,7 +10561,7 @@
   dependencies:
     figgy-pudding "^3.5.1"
 
-stable@~0.1.6:
+stable@^0.1.8:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
   integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
@@ -10433,7 +10594,7 @@
     define-property "^0.2.5"
     object-copy "^0.1.0"
 
-"statuses@>= 1.4.0 < 2":
+"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2":
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
   integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
@@ -10450,7 +10611,7 @@
   dependencies:
     readable-stream "^2.0.1"
 
-stealthy-require@^1.1.0:
+stealthy-require@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
   integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
@@ -10487,6 +10648,11 @@
   resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
   integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
 
+strict-uri-encode@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
+  integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
+
 string-length@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
@@ -10512,6 +10678,15 @@
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
 
+string-width@^3.0.0, string-width@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
+  integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
+  dependencies:
+    emoji-regex "^7.0.1"
+    is-fullwidth-code-point "^2.0.0"
+    strip-ansi "^5.1.0"
+
 string.prototype.padend@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0"
@@ -10563,12 +10738,12 @@
   dependencies:
     ansi-regex "^3.0.0"
 
-strip-ansi@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f"
-  integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==
-  dependencies:
-    ansi-regex "^4.0.0"
+strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
+  integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
+  dependencies:
+    ansi-regex "^4.1.0"
 
 strip-bom@3.0.0, strip-bom@^3.0.0:
   version "3.0.0"
@@ -10605,9 +10780,9 @@
   integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
 
 stylehacks@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.1.tgz#3186595d047ab0df813d213e51c8b94e0b9010f2"
-  integrity sha512-TK5zEPeD9NyC1uPIdjikzsgWxdQQN/ry1X3d1iOz1UkYDCmcr928gWD1KHgyC27F50UnE0xCTrBOO1l6KR8M4w==
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
+  integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==
   dependencies:
     browserslist "^4.0.0"
     postcss "^7.0.0"
@@ -10639,7 +10814,7 @@
   dependencies:
     has-flag "^2.0.0"
 
-supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
+supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
   integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
@@ -10659,22 +10834,22 @@
   integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
 
 svgo@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985"
-  integrity sha512-GBkJbnTuFpM4jFbiERHDWhZc/S/kpHToqmZag3aEBjPYK44JAN2QBjvrGIxLOoCyMZjuFQIfTO2eJd8uwLY/9g==
-  dependencies:
-    coa "~2.0.1"
-    colors "~1.1.2"
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.2.2.tgz#0253d34eccf2aed4ad4f283e11ee75198f9d7316"
+  integrity sha512-rAfulcwp2D9jjdGu+0CuqlrAUin6bBWrpoqXWwKDZZZJfXcUXQSxLJOFJCQCSA0x0pP2U0TxSlJu2ROq5Bq6qA==
+  dependencies:
+    chalk "^2.4.1"
+    coa "^2.0.2"
     css-select "^2.0.0"
-    css-select-base-adapter "~0.1.0"
+    css-select-base-adapter "^0.1.1"
     css-tree "1.0.0-alpha.28"
     css-url-regex "^1.1.0"
-    csso "^3.5.0"
-    js-yaml "^3.12.0"
+    csso "^3.5.1"
+    js-yaml "^3.13.1"
     mkdirp "~0.5.1"
-    object.values "^1.0.4"
+    object.values "^1.1.0"
     sax "~1.2.4"
-    stable "~0.1.6"
+    stable "^0.1.8"
     unquote "~1.1.1"
     util.promisify "~1.0.0"
 
@@ -10696,9 +10871,9 @@
     string-width "^2.1.1"
 
 tapable@^1.0.0, tapable@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e"
-  integrity sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
+  integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
 
 tar@^2.0.0:
   version "2.2.1"
@@ -10730,10 +10905,10 @@
     debug "4.1.0"
     is2 "2.0.1"
 
-terser-webpack-plugin@^1.1.0, terser-webpack-plugin@^1.2.1:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.2.tgz#9bff3a891ad614855a7dde0d707f7db5a927e3d9"
-  integrity sha512-1DMkTk286BzmfylAvLXwpJrI7dWa5BnFmscV/2dCr8+c56egFcbaeFAl7+sujAjdmpLam21XRdhA4oifLyiWWg==
+terser-webpack-plugin@^1.1.0, terser-webpack-plugin@^1.2.3:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz#3f98bc902fac3e5d0de730869f50668561262ec8"
+  integrity sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA==
   dependencies:
     cacache "^11.0.2"
     find-cache-dir "^2.0.0"
@@ -10745,13 +10920,13 @@
     worker-farm "^1.5.2"
 
 terser@^3.16.1:
-  version "3.16.1"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-3.16.1.tgz#5b0dd4fa1ffd0b0b43c2493b2c364fd179160493"
-  integrity sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow==
-  dependencies:
-    commander "~2.17.1"
+  version "3.17.0"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2"
+  integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==
+  dependencies:
+    commander "^2.19.0"
     source-map "~0.6.1"
-    source-map-support "~0.5.9"
+    source-map-support "~0.5.10"
 
 test-exclude@^4.2.1:
   version "4.2.3"
@@ -10769,6 +10944,20 @@
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
   integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
 
+thenify-all@^1.0.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
+  integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=
+  dependencies:
+    thenify ">= 3.1.0 < 4"
+
+"thenify@>= 3.1.0 < 4":
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839"
+  integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=
+  dependencies:
+    any-promise "^1.0.0"
+
 thread-loader@^2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/thread-loader/-/thread-loader-2.1.2.tgz#f585dd38e852c7f9cded5d092992108148f5eb30"
@@ -10880,6 +11069,11 @@
     regex-not "^1.0.2"
     safe-regex "^1.1.0"
 
+toidentifier@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
+  integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+
 token-stream@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-0.0.1.tgz#ceeefc717a76c4316f126d0b9dbaa55d7e7df01a"
@@ -10897,16 +11091,7 @@
   resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
   integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk=
 
-tough-cookie@>=2.3.3:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2"
-  integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==
-  dependencies:
-    ip-regex "^2.1.0"
-    psl "^1.1.28"
-    punycode "^2.1.1"
-
-tough-cookie@^2.2.0, tough-cookie@^2.3.4:
+tough-cookie@^2.2.0, tough-cookie@^2.3.3, tough-cookie@^2.3.4:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
   integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
@@ -11005,25 +11190,30 @@
   resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822"
   integrity sha1-C6XsKohWQORw6k6FBZcZANrFiCI=
 
+type-fest@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8"
+  integrity sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==
+
 type-is@~1.6.16:
-  version "1.6.16"
-  resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
-  integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==
+  version "1.6.18"
+  resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+  integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
   dependencies:
     media-typer "0.3.0"
-    mime-types "~2.1.18"
+    mime-types "~2.1.24"
 
 typedarray@^0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-uglify-js@3.4.x, uglify-js@^3.1.4:
-  version "3.4.9"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
-  integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==
-  dependencies:
-    commander "~2.17.1"
+uglify-js@3.4.x:
+  version "3.4.10"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
+  integrity sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==
+  dependencies:
+    commander "~2.19.0"
     source-map "~0.6.1"
 
 uglify-js@^2.6.1:
@@ -11036,6 +11226,14 @@
   optionalDependencies:
     uglify-to-browserify "~1.0.0"
 
+uglify-js@^3.1.4:
+  version "3.5.10"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.10.tgz#652bef39f86d9dbfd6674407ee05a5e2d372cf2d"
+  integrity sha512-/GTF0nosyPLbdJBd+AwYiZ+Hu5z8KXWnO0WCGt1BQ/u9Iamhejykqmz5o1OHJ53+VAk6xVxychonnApDjuqGsw==
+  dependencies:
+    commander "~2.20.0"
+    source-map "~0.6.1"
+
 uglify-to-browserify@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
@@ -11054,15 +11252,15 @@
     unicode-canonical-property-names-ecmascript "^1.0.4"
     unicode-property-aliases-ecmascript "^1.0.4"
 
-unicode-match-property-value-ecmascript@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz#9f1dc76926d6ccf452310564fd834ace059663d4"
-  integrity sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==
+unicode-match-property-value-ecmascript@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277"
+  integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==
 
 unicode-property-aliases-ecmascript@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0"
-  integrity sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57"
+  integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==
 
 union-value@^1.0.0:
   version "1.0.0"
@@ -11121,10 +11319,10 @@
     has-value "^0.3.1"
     isobject "^3.0.0"
 
-upath@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"
-  integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==
+upath@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068"
+  integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==
 
 upper-case@^1.1.1:
   version "1.1.3"
@@ -11153,11 +11351,11 @@
     schema-utils "^1.0.0"
 
 url-parse@^1.4.3:
-  version "1.4.4"
-  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8"
-  integrity sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==
-  dependencies:
-    querystringify "^2.0.0"
+  version "1.4.7"
+  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
+  integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
+  dependencies:
+    querystringify "^2.1.1"
     requires-port "^1.0.0"
 
 url@^0.11.0:
@@ -11223,13 +11421,13 @@
   integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
 
 v-tooltip@^2.0.0-rc.33:
-  version "2.0.0-rc.33"
-  resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.0-rc.33.tgz#78f7d8e9c34265622be65ba9dc78c67f1dc02b73"
-  integrity sha1-ePfY6cNCZWIr5lup3HjGfx3AK3M=
-  dependencies:
-    lodash.merge "^4.6.0"
-    popper.js "^1.12.9"
-    vue-resize "^0.4.3"
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.2.tgz#8610d9eece2cc44fd66c12ef2f12eec6435cab9b"
+  integrity sha512-xQ+qzOFfywkLdjHknRPgMMupQNS8yJtf9Utd5Dxiu/0n4HtrxqsgDtN2MLZ0LKbburtSAQgyypuE/snM8bBZhw==
+  dependencies:
+    lodash "^4.17.11"
+    popper.js "^1.15.0"
+    vue-resize "^0.4.5"
 
 v8-compile-cache@^2.0.2:
   version "2.0.2"
@@ -11305,9 +11503,9 @@
     lodash "^4.17.4"
 
 vue-gettext@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/vue-gettext/-/vue-gettext-2.1.2.tgz#b6b5160bed8ce61b6ead6adf5f4029ff04f57720"
-  integrity sha512-issGZfHvGntflaoLd+46Ru305lWOVEG/0vTI7uB5zvq/4JVGoF7J/+DUrcYswowiCKpJluYqf6inILat2HNeRQ==
+  version "2.1.4"
+  resolved "https://registry.yarnpkg.com/vue-gettext/-/vue-gettext-2.1.4.tgz#f42aa8480ad45c3f63ee67ea2f56891740010e37"
+  integrity sha512-UkJ+tKMp4/cn5RKK7Nm0l5apvpTqeuzlP22/SkNYdEfAzFBasX6qxK1EMTZGTXGWlc6h/KMu9X6W8KjWCnFbtQ==
 
 vue-highlightjs@^1.3.3:
   version "1.3.3"
@@ -11317,14 +11515,14 @@
     highlight.js "*"
 
 vue-hot-reload-api@^2.3.0:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.2.tgz#1fcc1495effe08a790909b46bf7b5c4cfeb6f21b"
-  integrity sha512-NpznMQoe/DzMG7nJjPkJKT7FdEn9xXfnntG7POfTmqnSaza97ylaBf1luZDh4IgV+vgUoR//id5pf8Ru+Ym+0g==
-
-vue-jest@^3.0.2:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.3.tgz#80f664712f2678b1d8bb3af0f2c0bef5efa8de31"
-  integrity sha512-QwFQjkv2vXYPKUkNZkMbV/ZTHyQhRM1JY8nP68dRLQmdvCN+VUEKhlByH/PgPqDr2p/NuhaM3PUjJ9nreR++3w==
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz#2756f46cb3258054c5f4723de8ae7e87302a1ccf"
+  integrity sha512-KmvZVtmM26BQOMK1rwUZsrqxEGeKiYSZGA7SNWE6uExx8UX/cj9hq2MRV/wWC3Cq6AoeDGk57rL9YMFRel/q+g==
+
+vue-jest@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.4.tgz#b6a2b0d874968f26fa775ac901903fece531e08b"
+  integrity sha512-PY9Rwt4OyaVlA+KDJJ0614CbEvNOkffDI9g9moLQC/2DDoo0YrqZm7dHi13Q10uoK5Nt5WCYFdeAheOExPah0w==
   dependencies:
     babel-plugin-transform-es2015-modules-commonjs "^6.26.0"
     chalk "^2.1.0"
@@ -11338,14 +11536,14 @@
     vue-template-es2015-compiler "^1.6.0"
 
 vue-js-toggle-button@^1.3.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/vue-js-toggle-button/-/vue-js-toggle-button-1.3.1.tgz#a129cf7493ad5bcd2a54c905cd5935d1c271e30c"
-  integrity sha512-2KpkUVSVvxFtT6LG+JXNpARo/E9jBNWUPvWKvoYA/kJJJtdJfOPdCLA3GKaIzeR/Cy0Tb72CNEy9qkoN0GhMHg==
-
-vue-loader@^15.6.2:
-  version "15.6.2"
-  resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.6.2.tgz#892741d96260936ff69e892f72ec361ba4d100d2"
-  integrity sha512-T6fONodj861M3PqZ1jlbUFjeezbUnPRY2bd+3eZuDvYADgkN3VFU2H5feqySNg9XBt8rcbyBGmFWTZtrOX+v5w==
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/vue-js-toggle-button/-/vue-js-toggle-button-1.3.2.tgz#d2e538465c321967144d035824cb71eedd82984e"
+  integrity sha512-LS+pvX5lXEhX+Gei5MOAEw7bx99/A+9idFhMtBgz72ApsEHlW69Y7bk+ZKC1rLRUxchL5WlQ+MhJXqXewhkfjg==
+
+vue-loader@^15.7.0:
+  version "15.7.0"
+  resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.0.tgz#27275aa5a3ef4958c5379c006dd1436ad04b25b3"
+  integrity sha512-x+NZ4RIthQOxcFclEcs8sXGEWqnZHodL2J9Vq+hUz+TDZzBaDIh1j3d9M2IUlTjtrHTZy4uMuRdTi8BGws7jLA==
   dependencies:
     "@vue/component-compiler-utils" "^2.5.1"
     hash-sum "^1.0.2"
@@ -11353,15 +11551,15 @@
     vue-hot-reload-api "^2.3.0"
     vue-style-loader "^4.1.0"
 
-vue-resize@^0.4.3:
+vue-resize@^0.4.5:
   version "0.4.5"
   resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-0.4.5.tgz#4777a23042e3c05620d9cbda01c0b3cc5e32dcea"
   integrity sha512-bhP7MlgJQ8TIkZJXAfDf78uJO+mEI3CaLABLjv0WNzr4CcGRGPIAItyWYnP6LsPA4Oq0WE+suidNs6dgpO4RHg==
 
 vue-router@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be"
-  integrity sha512-opKtsxjp9eOcFWdp6xLQPLmRGgfM932Tl56U9chYTnoWqKxQ8M20N7AkdEbM5beUh6wICoFGYugAX9vQjyJLFg==
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.6.tgz#2e4f0f9cbb0b96d0205ab2690cfe588935136ac3"
+  integrity sha512-Ox0ciFLswtSGRTHYhGvx2L44sVbTPNS+uD2kRISuo8B39Y79rOo0Kw0hzupTmiVtftQYCZl87mwldhh2L9Aquw==
 
 vue-snotify@^3.2.1:
   version "3.2.1"
@@ -11377,22 +11575,22 @@
     loader-utils "^1.0.2"
 
 vue-template-compiler@^2.5.16, vue-template-compiler@^2.5.17:
-  version "2.6.6"
-  resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.6.tgz#a807acbf3d51971d3721d75ecb1b927b517c1a02"
-  integrity sha512-OakxDGyrmMQViCjkakQFbDZlG0NibiOzpLauOfyCUVRQc9yPmTqpiz9nF0VeA+dFkXegetw0E5x65BFhhLXO0A==
+  version "2.6.10"
+  resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
+  integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==
   dependencies:
     de-indent "^1.0.2"
     he "^1.1.0"
 
-vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.8.2:
-  version "1.8.2"
-  resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.8.2.tgz#dd73e80ba58bb65dd7a8aa2aeef6089cf6116f2a"
-  integrity sha512-cliV19VHLJqFUYbz/XeWXe5CO6guzwd0yrrqqp0bmjlMP3ZZULY7fu8RTC4+3lmHwo6ESVDHFDsvjB15hcR5IA==
+vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
+  integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
 
 vue@^2.5.16:
-  version "2.6.6"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.6.tgz#dde41e483c11c46a7bf523909f4f2f816ab60d25"
-  integrity sha512-Y2DdOZD8sxApS+iUlwv1v8U1qN41kq6Kw45lM6nVZKhygeWA49q7VCCXkjXqeDBXgurrKWkYQ9cJeEJwAq0b9Q==
+  version "2.6.10"
+  resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
+  integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
 
 vuex@^3.0.1:
   version "3.1.0"
@@ -11454,12 +11652,13 @@
   resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
   integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
 
-webpack-bundle-analyzer@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.3.tgz#dbc7fff8f52058b6714a20fddf309d0790e3e0a0"
-  integrity sha512-naLWiRfmtH4UJgtUktRTLw6FdoZJ2RvCR9ePbwM9aRMsS/KjFerkPZG9epEvXRAw5d5oPdrs9+3p+afNjxW8Xw==
-  dependencies:
-    acorn "^5.7.3"
+webpack-bundle-analyzer@^3.0.3, webpack-bundle-analyzer@^3.3.0:
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.3.2.tgz#3da733a900f515914e729fcebcd4c40dde71fc6f"
+  integrity sha512-7qvJLPKB4rRWZGjVp5U1KEjwutbDHSKboAl0IfafnrdXMrgC0tOtZbQD6Rw0u4cmpgRN4O02Fc0t8eAT+FgGzA==
+  dependencies:
+    acorn "^6.0.7"
+    acorn-walk "^6.1.1"
     bfj "^6.1.1"
     chalk "^2.4.1"
     commander "^2.18.0"
@@ -11481,9 +11680,9 @@
     javascript-stringify "^1.6.0"
 
 webpack-cli@^3.1.2:
-  version "3.2.3"
-  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.2.3.tgz#13653549adfd8ccd920ad7be1ef868bacc22e346"
-  integrity sha512-Ik3SjV6uJtWIAN5jp5ZuBMWEAaP5E4V78XJ2nI+paFPh8v4HPSwo/myN0r29Xc/6ZKnd2IdrAlpSgNOu2CDQ6Q==
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.2.tgz#aed2437b0db0a7faa2ad28484e166a5360014a91"
+  integrity sha512-FLkobnaJJ+03j5eplxlI0TUxhGCOdfewspIGuvDVtpOlrAuKMFC57K42Ukxqs1tn8947/PM6tP95gQc0DCzRYA==
   dependencies:
     chalk "^2.4.1"
     cross-spawn "^6.0.5"
@@ -11495,53 +11694,53 @@
     loader-utils "^1.1.0"
     supports-color "^5.5.0"
     v8-compile-cache "^2.0.2"
-    yargs "^12.0.4"
-
-webpack-dev-middleware@3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz#1132fecc9026fd90f0ecedac5cbff75d1fb45890"
-  integrity sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==
-  dependencies:
-    memory-fs "~0.4.1"
+    yargs "^12.0.5"
+
+webpack-dev-middleware@^3.6.2:
+  version "3.6.2"
+  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.6.2.tgz#f37a27ad7c09cd7dc67cd97655413abaa1f55942"
+  integrity sha512-A47I5SX60IkHrMmZUlB0ZKSWi29TZTcPz7cha1Z75yYOsgWh/1AcPmQEbC8ZIbU3A1ytSv1PMU0PyPz2Lmz2jg==
+  dependencies:
+    memory-fs "^0.4.1"
     mime "^2.3.1"
     range-parser "^1.0.3"
     webpack-log "^2.0.0"
 
-webpack-dev-server@^3.1.14:
-  version "3.1.14"
-  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz#60fb229b997fc5a0a1fc6237421030180959d469"
-  integrity sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==
+webpack-dev-server@^3.3.1:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.3.1.tgz#7046e49ded5c1255a82c5d942bcdda552b72a62d"
+  integrity sha512-jY09LikOyGZrxVTXK0mgIq9y2IhCoJ05848dKZqX1gAGLU1YDqgpOT71+W53JH/wI4v6ky4hm+KvSyW14JEs5A==
   dependencies:
     ansi-html "0.0.7"
     bonjour "^3.5.0"
-    chokidar "^2.0.0"
-    compression "^1.5.2"
-    connect-history-api-fallback "^1.3.0"
-    debug "^3.1.0"
-    del "^3.0.0"
-    express "^4.16.2"
-    html-entities "^1.2.0"
-    http-proxy-middleware "~0.18.0"
+    chokidar "^2.1.5"
+    compression "^1.7.4"
+    connect-history-api-fallback "^1.6.0"
+    debug "^4.1.1"
+    del "^4.1.0"
+    express "^4.16.4"
+    html-entities "^1.2.1"
+    http-proxy-middleware "^0.19.1"
     import-local "^2.0.0"
-    internal-ip "^3.0.1"
+    internal-ip "^4.2.0"
     ip "^1.1.5"
-    killable "^1.0.0"
-    loglevel "^1.4.1"
-    opn "^5.1.0"
-    portfinder "^1.0.9"
+    killable "^1.0.1"
+    loglevel "^1.6.1"
+    opn "^5.5.0"
+    portfinder "^1.0.20"
     schema-utils "^1.0.0"
-    selfsigned "^1.9.1"
-    semver "^5.6.0"
-    serve-index "^1.7.2"
+    selfsigned "^1.10.4"
+    semver "^6.0.0"
+    serve-index "^1.9.1"
     sockjs "0.3.19"
     sockjs-client "1.3.0"
     spdy "^4.0.0"
-    strip-ansi "^3.0.0"
-    supports-color "^5.1.0"
+    strip-ansi "^3.0.1"
+    supports-color "^6.1.0"
     url "^0.11.0"
-    webpack-dev-middleware "3.4.0"
+    webpack-dev-middleware "^3.6.2"
     webpack-log "^2.0.0"
-    yargs "12.0.2"
+    yargs "12.0.5"
 
 webpack-log@^2.0.0:
   version "2.0.0"
@@ -11700,9 +11899,9 @@
   integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
 
 worker-farm@^1.5.2:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0"
-  integrity sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
+  integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==
   dependencies:
     errno "~0.1.7"
 
@@ -11714,6 +11913,15 @@
     string-width "^1.0.1"
     strip-ansi "^3.0.1"
 
+wrap-ansi@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
+  integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
+  dependencies:
+    ansi-styles "^3.2.0"
+    string-width "^3.0.0"
+    strip-ansi "^5.0.0"
+
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -11743,9 +11951,9 @@
     async-limiter "~1.0.0"
 
 ws@^6.0.0:
-  version "6.1.3"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.3.tgz#d2d2e5f0e3c700ef2de89080ebc0ac6e1bf3a72d"
-  integrity sha512-tbSxiT+qJI223AP4iLfQbkbxkwdFcneYinM2+x46Gx2wgvbaOMO36czfdfVUBRTHvzAMRhDd98sA5d/BuWbQdg==
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
+  integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
   dependencies:
     async-limiter "~1.0.0"
 
@@ -11769,11 +11977,6 @@
   resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
   integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=
 
-xregexp@4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020"
-  integrity sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==
-
 xtend@^4.0.0, xtend@~4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
@@ -11799,13 +12002,6 @@
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
   integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
 
-yargs-parser@^10.1.0:
-  version "10.1.0"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8"
-  integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==
-  dependencies:
-    camelcase "^4.1.0"
-
 yargs-parser@^11.1.1:
   version "11.1.1"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
@@ -11814,6 +12010,14 @@
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
+yargs-parser@^13.0.0:
+  version "13.1.0"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.0.tgz#7016b6dd03e28e1418a510e258be4bff5a31138f"
+  integrity sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
 yargs-parser@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
@@ -11828,13 +12032,13 @@
   dependencies:
     camelcase "^4.1.0"
 
-yargs@12.0.2:
-  version "12.0.2"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc"
-  integrity sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==
+yargs@12.0.5, yargs@^12.0.1, yargs@^12.0.5:
+  version "12.0.5"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
+  integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
   dependencies:
     cliui "^4.0.0"
-    decamelize "^2.0.0"
+    decamelize "^1.2.0"
     find-up "^3.0.0"
     get-caller-file "^1.0.1"
     os-locale "^3.0.0"
@@ -11844,7 +12048,7 @@
     string-width "^2.0.0"
     which-module "^2.0.0"
     y18n "^3.2.1 || ^4.0.0"
-    yargs-parser "^10.1.0"
+    yargs-parser "^11.1.1"
 
 yargs@^11.0.0:
   version "11.1.0"
@@ -11864,23 +12068,22 @@
     y18n "^3.2.1"
     yargs-parser "^9.0.2"
 
-yargs@^12.0.1, yargs@^12.0.4, yargs@^12.0.5:
-  version "12.0.5"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
-  integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
+yargs@^13.0.0, yargs@^13.2.2:
+  version "13.2.2"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.2.tgz#0c101f580ae95cea7f39d927e7770e3fdc97f993"
+  integrity sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==
   dependencies:
     cliui "^4.0.0"
-    decamelize "^1.2.0"
     find-up "^3.0.0"
-    get-caller-file "^1.0.1"
-    os-locale "^3.0.0"
+    get-caller-file "^2.0.1"
+    os-locale "^3.1.0"
     require-directory "^2.1.1"
-    require-main-filename "^1.0.1"
+    require-main-filename "^2.0.0"
     set-blocking "^2.0.0"
-    string-width "^2.0.0"
+    string-width "^3.0.0"
     which-module "^2.0.0"
-    y18n "^3.2.1 || ^4.0.0"
-    yargs-parser "^11.1.1"
+    y18n "^4.0.0"
+    yargs-parser "^13.0.0"
 
 yargs@^7.0.0:
   version "7.1.0"
--- a/cmd/gemma/main.go	Wed May 29 10:58:45 2019 +0200
+++ b/cmd/gemma/main.go	Mon Jun 03 10:19:18 2019 +0200
@@ -31,6 +31,8 @@
 	"gemma.intevation.de/gemma/pkg/config"
 	"gemma.intevation.de/gemma/pkg/controllers"
 	"gemma.intevation.de/gemma/pkg/geoserver"
+	"gemma.intevation.de/gemma/pkg/imports"
+	"gemma.intevation.de/gemma/pkg/scheduler"
 )
 
 func prepareSessionStore() {
@@ -56,6 +58,10 @@
 	// Do GeoServer setup in background.
 	geoserver.Reconfigure(geoserver.PrepareGeoServer)
 
+	// Log what it is rgistered to the import queue and scheduler.
+	imports.LogImportKindNames()
+	scheduler.LogActionNames()
+
 	m := mux.NewRouter()
 	controllers.BindRoutes(m)
 
@@ -107,6 +113,7 @@
 
 func main() {
 	config.RootCmd.Run = start
+	log.SetFlags(log.LstdFlags | log.Lshortfile)
 	if err := config.RootCmd.Execute(); err != nil {
 		log.Fatalln(err)
 	}
--- a/docker/Dockerfile.geoserv	Wed May 29 10:58:45 2019 +0200
+++ b/docker/Dockerfile.geoserv	Mon Jun 03 10:19:18 2019 +0200
@@ -15,7 +15,7 @@
 
 ENV GS_URL https://downloads.sourceforge.net/project/geoserver/GeoServer
 
-ENV GS_VERSION 2.15.0
+ENV GS_VERSION 2.15.1
 ENV GS_DATADIR /opt/geoserver/data
 
 ENV CATALINA_OPTS="-DGEOSERVER_DATA_DIR=$GS_DATADIR"
--- a/example_conf.toml	Wed May 29 10:58:45 2019 +0200
+++ b/example_conf.toml	Mon Jun 03 10:19:18 2019 +0200
@@ -72,3 +72,4 @@
 # -----------------------
 # Schema for "Testclient imports"
 # schema-dirs = "$PATH_TO_SCHEMATA"
+# published-config ="$PATH/pub-config.json"
--- a/pkg/common/nashsutcliffe.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/common/nashsutcliffe.go	Mon Jun 03 10:19:18 2019 +0200
@@ -14,68 +14,58 @@
 package common
 
 import (
-	"sort"
+	"fmt"
 	"time"
 )
 
-type NSMeasurement struct {
-	When      time.Time
-	Predicted float64
-	Observed  float64
+type TimedValue struct {
+	When  time.Time
+	Value float64
+}
+
+type TimedValues []TimedValue
+
+func epsEquals(a, b time.Time) bool {
+	d := a.Sub(b)
+	return -10*time.Millisecond < d && d < 10*time.Millisecond
 }
 
-func NashSutcliffeSort(measurements []NSMeasurement) {
-	sort.Slice(measurements, func(i, j int) bool {
-		return measurements[i].When.Before(measurements[j].When)
-	})
+func (mvs TimedValues) Find(when time.Time) (float64, bool) {
+	for i := range mvs {
+		if epsEquals(when, mvs[i].When) {
+			return mvs[i].Value, true
+		}
+	}
+	return 0, false
 }
 
-func NashSutcliffe(measurements []NSMeasurement, from, to time.Time) (float64, int) {
+func NashSutcliffe(predicted, observed []float64) float64 {
 
-	if len(measurements) == 0 {
-		return 0, 0
+	if len(predicted) != len(observed) {
+		panic(fmt.Sprintf(
+			"NashSutcliffe: predicted and observed len differ: %d != %d",
+			len(predicted),
+			len(observed)))
 	}
 
-	if to.Before(from) {
-		from, to = to, from
-	}
-
-	begin := sort.Search(len(measurements), func(i int) bool {
-		return !measurements[i].When.Before(from)
-	})
-	if begin >= len(measurements) {
-		return 0, 0
-	}
-
-	end := sort.Search(len(measurements), func(i int) bool {
-		return measurements[i].When.After(to)
-	})
-	if end >= len(measurements) {
-		end = len(measurements) - 1
-	}
-	if end <= begin {
-		return 0, 0
-	}
-	sample := measurements[begin:end]
-
-	if len(sample) == 0 {
-		return 0, 0
+	if len(observed) == 0 {
+		return 0
 	}
 
 	var mo float64
-	for i := range sample {
-		mo += sample[i].Observed
+	for _, v := range observed {
+		mo += v
 	}
 
-	mo /= float64(len(sample))
+	mo /= float64(len(observed))
 
 	var num, denom float64
-	for i := range sample {
-		d1 := sample[i].Predicted - sample[i].Observed
+	for i, o := range observed {
+		d1 := predicted[i] - o
 		num += d1 * d1
-		d2 := sample[i].Observed - mo
+		d2 := o - mo
 		denom += d2 * d2
 	}
 
-	return 1 - num/denom, len(sample)
+	return 1 - num/denom
 }
--- a/pkg/common/time.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/common/time.go	Mon Jun 03 10:19:18 2019 +0200
@@ -4,16 +4,53 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
 // License-Filename: LICENSES/AGPL-3.0.txt
 //
-// Copyright (C) 2018 by via donau
+// Copyright (C) 2018, 2019 by via donau
 //   – Österreichische Wasserstraßen-Gesellschaft mbH
 // Software engineering by Intevation GmbH
 //
 // Author(s):
 //  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+//  * Bernhard E. Reiter <bernhard.reiter@intevation.de>
 
 package common
 
+import (
+	"math"
+	"time"
+)
+
 const (
-	TimeFormat = "2006-01-02T15:04:05"
+	// time.RFC3339 equals "simplified ISO format as defined by ECMA-262"
+	//   https://tc39.github.io/ecma262/#sec-date-time-string-format
+	// and "SHOULD be used in new protocols on the Internet." (RFC section 5.6)
+	TimeFormat = time.RFC3339
 	DateFormat = "2006-01-02"
 )
+
+var utc0 = time.Unix(0, 0)
+
+func InterpolateTime(t1 time.Time, m1 float64, t2 time.Time, m2 float64) func(float64) time.Time {
+
+	// f(m1) = t1
+	// f(m2) = t2
+	// t1 = m1*a + b <=> b = t1 - m1*a
+	// t2 = m2*a + b
+
+	// t1 - t2 = a*(m1 - m2)
+	// a = (t1-t2)/(m1 - m2) for m1 != m2
+
+	if m1 == m2 {
+		t := t1.Add(t2.Sub(t1) / 2)
+		return func(float64) time.Time { return t }
+	}
+
+	a := t1.Sub(t2).Seconds() / (m1 - m2)
+	b := t1.Sub(utc0).Seconds() - m1*a
+
+	return func(m float64) time.Time {
+		x := m*a + b
+		secs := math.Ceil(x)
+		nsecs := math.Ceil((x - secs) * (999999999 + 1))
+		return time.Unix(int64(secs), int64(nsecs))
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/common/time_test.go	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,61 @@
+// 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, 2019 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 (
+	"testing"
+	"time"
+)
+
+func TestInterpolateTimeByValue(t *testing.T) {
+
+	t1 := time.Now().UTC()
+	t2 := t1.Add(time.Hour).UTC()
+
+	f := InterpolateTime(t1, 10, t2, 20)
+
+	v1 := f(10)
+	v2 := f(20)
+	v3 := f(15)
+
+	t3 := t1.Add(time.Hour / 2)
+
+	d1 := v1.Sub(t1)
+	d2 := v2.Sub(t2)
+	d3 := v3.Sub(t3)
+
+	if d1 < 0 {
+		d1 = -d1
+	}
+
+	if d1 > 100*time.Microsecond {
+		t.Errorf("difference too big t1: %v\n", d1)
+	}
+
+	if d2 < 0 {
+		d2 = -d2
+	}
+
+	if d2 > 100*time.Microsecond {
+		t.Errorf("difference too big t2: %v\n", d2)
+	}
+
+	if d3 < 0 {
+		d3 = -d3
+	}
+
+	if d3 > 100*time.Microsecond {
+		t.Errorf("difference too big t3: %v\n", d3)
+	}
+}
--- a/pkg/config/config.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/config/config.go	Mon Jun 03 10:19:18 2019 +0200
@@ -4,7 +4,7 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
 // License-Filename: LICENSES/AGPL-3.0.txt
 //
-// Copyright (C) 2018 by via donau
+// Copyright (C) 2018, 2019 by via donau
 //   – Österreichische Wasserstraßen-Gesellschaft mbH
 // Software engineering by Intevation GmbH
 //
@@ -22,6 +22,7 @@
 	"time"
 
 	homedir "github.com/mitchellh/go-homedir"
+
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 
@@ -106,6 +107,13 @@
 // If left empty the system default for temporary files is used.
 func TmpDir() string { return viper.GetString("tmp-dir") }
 
+// PublishedConfig is a name of a JSON file where extra configuration is stored
+// to be served to to the web client.
+func PublishedConfig() string { return viper.GetString("published-config") }
+
+// SOAPTimeout is the timeout till a SOAP request is canceled.
+func SOAPTimeout() time.Duration { return viper.GetDuration("soap-timeout") }
+
 var (
 	proxyKeyOnce       sync.Once
 	proxyKey           []byte
@@ -140,7 +148,6 @@
 }
 
 // ProxyPrefix is the prefix used in generated URLs by the proxy.
-// It defauls to http://${WebHost}:${WebPort}".
 // You may need to set this if you run gemma behind a proxy
 // on a specific domain.
 func ProxyPrefix() string {
@@ -156,7 +163,6 @@
 }
 
 // ExternalURL is the URL to find this server from the outside.
-// It defauls to http://${WebHost}:${WebPort}".
 func ExternalURL() string {
 	fetchExternal := func() {
 		if externalURL == "" {
@@ -183,7 +189,7 @@
 	return sessionTimeout
 }
 
-// The root directories where to find schema files.
+// SchemaDirs are the root directories where to find schema files.
 func SchemaDirs() string { return viper.GetString("schema-dirs") }
 
 // RootCmd is cobra command to be bound th the cobra/viper infrastructure.
@@ -227,6 +233,10 @@
 		fl.Bool(name, value, usage)
 		vbind(name)
 	}
+	d := func(name string, value time.Duration, usage string) {
+		fl.Duration(name, value, usage)
+		vbind(name)
+	}
 
 	strP("db-host", "H", "localhost", "host of the database")
 	uiP("db-port", "P", 5432, "port of the database")
@@ -236,7 +246,7 @@
 	strP("db-ssl", "S", "prefer", "SSL mode of the database")
 
 	strP("sessions", "s", "", "path to the sessions file")
-	str("session-timeout", "3h", "duration until sessions expire")
+	d("session-timeout", 3*time.Hour, "duration until sessions expire")
 
 	strP("web", "w", "./web", "path to the web files")
 	strP("host", "o", "localhost", "host of the web app")
@@ -260,15 +270,19 @@
 	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}'")
+		"Defaults to 'http://${host}:${port}'")
 
 	str("external-url", "", "URL to find the server from the outside.\n"+
-		"Defaults to 'http://${web-host}:${web-port}'")
+		"Defaults to 'http://${host}:${port}'")
 
 	str("tmp-dir", "", "Temp directory of gemma server.\n"+
 		"Defaults to system temp directory.")
 
 	str("schema-dirs", ".", "Directories to find XSD schema files in (recursive).")
+
+	str("published-config", "", "path to a config file served to client.")
+
+	d("soap-timeout", time.Minute, "Timeout till a SOAP request is canceled.")
 }
 
 var (
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/controllers/bottlenecks.go	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,731 @@
+// 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) 2019 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 (
+	"context"
+	"database/sql"
+	"encoding/csv"
+	"fmt"
+	"log"
+	"net/http"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/gorilla/mux"
+
+	"gemma.intevation.de/gemma/pkg/common"
+	"gemma.intevation.de/gemma/pkg/middleware"
+)
+
+const (
+	selectLimitingSQL = `
+SELECT limiting from waterway.bottlenecks WHERE objnam = $1`
+
+	selectAvailableDepthSQL = `
+WITH data AS (
+  SELECT
+    efa.measure_date,
+    efa.available_depth_value,
+    efa.available_width_value,
+    efa.water_level_value
+  FROM waterway.effective_fairway_availability efa
+  JOIN waterway.fairway_availability fa
+    ON efa.fairway_availability_id = fa.id
+  JOIN waterway.bottlenecks bn
+    ON fa.bottleneck_id = bn.id
+  WHERE
+    bn.objnam = $1 AND
+    efa.level_of_service = $2 AND
+    efa.measure_type = 'Measured' AND
+    (efa.available_depth_value IS NOT NULL OR
+     efa.available_width_value IS NOT NULL) AND
+    efa.water_level_value IS NOT NULL
+),
+before AS (
+  SELECT * FROM data WHERE measure_date < $3
+  ORDER BY measure_date DESC LIMIT 1
+),
+inside AS (
+  SELECT * FROM data WHERE measure_date BETWEEN $3 AND $4
+),
+after AS (
+  SELECT * FROM data WHERE measure_date > $4
+  ORDER BY measure_date LIMIT 1
+)
+SELECT * FROM before
+UNION ALL
+SELECT * FROM inside
+UNION ALL
+SELECT * FROM after
+ORDER BY measure_date
+`
+
+	selectGaugeLevelsSQL = `
+SELECT
+  grwl.depth_reference,
+  grwl.value
+FROM waterway.gauges_reference_water_levels grwl
+  JOIN waterway.bottlenecks bns
+    ON grwl.location = bns.gauge_location
+      AND grwl.validity = bns.gauge_validity
+WHERE bns.objnam = $1 AND (
+  grwl.depth_reference like 'HDC%' OR
+  grwl.depth_reference like 'LDC%' OR
+  grwl.depth_reference like 'MW%'
+)
+`
+	selectGaugeLDCSQL = `
+SELECT
+  grwl.value
+FROM waterway.gauges_reference_water_levels grwl
+  JOIN waterway.bottlenecks bns
+    ON grwl.location = bns.gauge_location
+      AND grwl.validity = bns.gauge_validity
+WHERE bns.objnam = $1 AND grwl.depth_reference like 'LDC%'
+`
+)
+
+type (
+	availMeasurement struct {
+		when  time.Time
+		depth int16
+		width int16
+		value int16
+	}
+
+	availMeasurements []availMeasurement
+)
+
+// afdRefs are the typical available fairway depth reference values.
+var afdRefs = []float64{
+	230,
+	250,
+}
+
+func (measurement *availMeasurement) getDepth() float64 {
+	return float64(measurement.depth)
+}
+
+func (measurement *availMeasurement) getValue() float64 {
+	return float64(measurement.value)
+}
+
+func (measurement *availMeasurement) getWidth() float64 {
+	return float64(measurement.width)
+}
+
+func limitingFactor(limiting string) func(*availMeasurement) float64 {
+	switch limiting {
+	case "depth":
+		return (*availMeasurement).getDepth
+	case "width":
+		return (*availMeasurement).getWidth
+	default:
+		log.Printf("warn: unknown limitation '%s'. default to 'depth'\n", limiting)
+		return (*availMeasurement).getDepth
+	}
+}
+
+func (measurements availMeasurements) classify(
+	from, to time.Time,
+	breaks []float64,
+	access func(*availMeasurement) float64,
+) []time.Duration {
+
+	if len(breaks) == 0 {
+		return []time.Duration{}
+	}
+
+	result := make([]time.Duration, len(breaks)+1)
+	classes := make([]float64, len(breaks)+2)
+	values := make([]time.Time, len(classes))
+
+	// Add sentinels
+	classes[0] = breaks[0] - 9999
+	classes[len(classes)-1] = breaks[len(breaks)-1] + 9999
+	for i := range breaks {
+		classes[i+1] = breaks[i]
+	}
+
+	idx := sort.Search(len(measurements), func(i int) bool {
+		// All values before from can be ignored.
+		return !measurements[i].when.Before(from)
+	})
+
+	if idx >= len(measurements) {
+		return result
+	}
+
+	// Be safe for interpolation.
+	if idx > 0 {
+		idx--
+	}
+
+	measurements = measurements[idx:]
+
+	for i := 0; i < len(measurements)-1; i++ {
+		p1 := &measurements[i]
+		p2 := &measurements[i+1]
+
+		if p1.when.After(to) {
+			return result
+		}
+
+		if p2.when.Before(from) {
+			continue
+		}
+
+		lo, hi := maxTime(p1.when, from), minTime(p2.when, to)
+
+		m1, m2 := access(p1), access(p2)
+		if m1 == m2 { // The whole interval is in only one class.
+			for j := 0; j < len(classes)-1; j++ {
+				if classes[j] <= m1 && m1 <= classes[j+1] {
+					result[j] += hi.Sub(lo)
+					break
+				}
+			}
+			continue
+		}
+
+		f := common.InterpolateTime(
+			p1.when, m1,
+			p2.when, m2,
+		)
+
+		for j, c := range classes {
+			values[j] = f(c)
+		}
+
+		for j := 0; j < len(values)-1; j++ {
+			start, end := orderTime(values[j], values[j+1])
+
+			if start.After(hi) || end.Before(lo) {
+				continue
+			}
+
+			start, end = maxTime(start, lo), minTime(end, hi)
+			result[j] += end.Sub(start)
+		}
+	}
+
+	return result
+}
+
+func orderTime(a, b time.Time) (time.Time, time.Time) {
+	if a.Before(b) {
+		return a, b
+	}
+	return b, a
+}
+
+func minTime(a, b time.Time) time.Time {
+	if a.Before(b) {
+		return a
+	}
+	return b
+}
+
+func maxTime(a, b time.Time) time.Time {
+	if a.After(b) {
+		return a
+	}
+	return b
+}
+
+func durationsToPercentage(duration time.Duration, classes []time.Duration) []float64 {
+	percents := make([]float64, len(classes))
+	total := 100 / duration.Seconds()
+	for i, v := range classes {
+		percents[i] = v.Seconds() * total
+	}
+	return percents
+}
+
+func parseFormTime(
+	rw http.ResponseWriter,
+	req *http.Request,
+	field string,
+	def time.Time,
+) (time.Time, bool) {
+	f := req.FormValue(field)
+	if f == "" {
+		return def.UTC(), true
+	}
+	v, err := time.Parse(common.TimeFormat, f)
+	if err != nil {
+		http.Error(
+			rw, fmt.Sprintf("Invalid format for '%s': %v.", field, err),
+			http.StatusBadRequest,
+		)
+		return time.Time{}, false
+	}
+	return v.UTC(), true
+}
+
+func parseFormInt(
+	rw http.ResponseWriter,
+	req *http.Request,
+	field string,
+	def int,
+) (int, bool) {
+	f := req.FormValue(field)
+	if f == "" {
+		return def, true
+	}
+	v, err := strconv.Atoi(f)
+	if err != nil {
+		http.Error(
+			rw, fmt.Sprintf("Invalid format for '%s': %v.", field, err),
+			http.StatusBadRequest,
+		)
+		return 0, false
+	}
+	return v, true
+}
+
+func intervalMode(mode string) int {
+	switch strings.ToLower(mode) {
+	case "monthly":
+		return 0
+	case "quarterly":
+		return 1
+	case "yearly":
+		return 2
+	default:
+		return 0
+	}
+}
+
+func loadDepthValues(
+	ctx context.Context,
+	conn *sql.Conn,
+	bottleneck string,
+	los int,
+	from, to time.Time,
+) (availMeasurements, error) {
+
+	rows, err := conn.QueryContext(
+		ctx, selectAvailableDepthSQL, bottleneck, los, from, to)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	var ms availMeasurements
+
+	for rows.Next() {
+		var m availMeasurement
+		if err := rows.Scan(
+			&m.when,
+			&m.depth,
+			&m.width,
+			&m.value,
+		); err != nil {
+			return nil, err
+		}
+		m.when = m.when.UTC()
+		ms = append(ms, m)
+	}
+
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+
+	return ms, nil
+}
+
+func loadLDCReferenceValue(
+	ctx context.Context,
+	conn *sql.Conn,
+	bottleneck string,
+) ([]float64, error) {
+	var value float64
+	err := conn.QueryRowContext(ctx, selectGaugeLDCSQL, bottleneck).Scan(&value)
+	switch {
+	case err == sql.ErrNoRows:
+		return nil, nil
+	case err != nil:
+		return nil, err
+	}
+	return []float64{value}, nil
+}
+
+func breaksToReferenceValue(breaks string) []float64 {
+	parts := strings.Split(breaks, ",")
+	var values []float64
+
+	for _, part := range parts {
+		part = strings.TrimSpace(part)
+		if v, err := strconv.ParseFloat(part, 64); err == nil {
+			values = append(values, v)
+		}
+	}
+
+	sort.Float64s(values)
+
+	// dedup
+	for i := 1; i < len(values); {
+		if values[i-1] == values[i] {
+			copy(values[i:], values[i+1:])
+			values = values[:len(values)-1]
+		} else {
+			i++
+		}
+	}
+	return values
+}
+
+func bottleneckAvailabilty(rw http.ResponseWriter, req *http.Request) {
+
+	mode := intervalMode(req.FormValue("mode"))
+	bn := mux.Vars(req)["objnam"]
+
+	if bn == "" {
+		http.Error(
+			rw,
+			"Missing objnam of bottleneck",
+			http.StatusBadRequest,
+		)
+		return
+	}
+
+	from, ok := parseFormTime(rw, req, "from", time.Now().AddDate(-1, 0, 0))
+	if !ok {
+		return
+	}
+
+	to, ok := parseFormTime(rw, req, "to", from.AddDate(1, 0, 0))
+	if !ok {
+		return
+	}
+
+	if to.Before(from) {
+		to, from = from, to
+	}
+
+	los, ok := parseFormInt(rw, req, "los", 1)
+	if !ok {
+		return
+	}
+
+	conn := middleware.GetDBConn(req)
+	ctx := req.Context()
+
+	ldcRefs, err := loadLDCReferenceValue(ctx, conn, bn)
+	if err != nil {
+		http.Error(
+			rw,
+			fmt.Sprintf("Internal server error: %v", err),
+			http.StatusInternalServerError,
+		)
+		return
+	}
+
+	if len(ldcRefs) == 0 {
+		http.Error(
+			rw,
+			"No gauge reference values found for bottleneck",
+			http.StatusNotFound,
+		)
+		return
+	}
+
+	var breaks []float64
+	if b := req.FormValue("breaks"); b != "" {
+		breaks = breaksToReferenceValue(b)
+	} else {
+		breaks = afdRefs
+	}
+
+	log.Printf("info: time interval: (%v - %v)\n", from, to)
+
+	var ms availMeasurements
+	if ms, err = loadDepthValues(ctx, conn, bn, los, from, to); err != nil {
+		return
+	}
+
+	if len(ms) == 0 {
+		http.Error(
+			rw,
+			"No available fairway depth values found",
+			http.StatusNotFound,
+		)
+		return
+	}
+
+	rw.Header().Add("Content-Type", "text/csv")
+
+	out := csv.NewWriter(rw)
+
+	record := make([]string, 1+2+len(breaks)+1)
+	record[0] = "#time"
+	record[1] = fmt.Sprintf("# < LDC (%.1f) [h]", ldcRefs[0])
+	record[2] = fmt.Sprintf("# >= LDC (%.1f) [h]", ldcRefs[0])
+	for i, v := range breaks {
+		if i == 0 {
+			record[3] = fmt.Sprintf("#d < %.1f [%%]", v)
+		}
+		record[i+4] = fmt.Sprintf("#d >= %.1f [%%]", v)
+	}
+
+	if err := out.Write(record); err != nil {
+		// Too late for HTTP status message.
+		log.Printf("error: %v\n", err)
+		return
+	}
+
+	interval := intervals[mode](from, to)
+
+	for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() {
+		lnwl := ms.classify(
+			pfrom, pto,
+			ldcRefs,
+			(*availMeasurement).getValue,
+		)
+
+		afd := ms.classify(
+			pfrom, pto,
+			breaks,
+			(*availMeasurement).getDepth,
+		)
+
+		duration := pto.Sub(pfrom)
+		lnwlPercents := durationsToPercentage(duration, lnwl)
+		afdPercents := durationsToPercentage(duration, afd)
+
+		record[0] = label
+		for i, v := range lnwlPercents {
+			record[1+i] = fmt.Sprintf("%.3f", v)
+		}
+		for i, v := range afdPercents {
+			record[3+i] = fmt.Sprintf("%.3f", v)
+		}
+
+		if err := out.Write(record); err != nil {
+			// Too late for HTTP status message.
+			log.Printf("error: %v\n", err)
+			return
+		}
+	}
+
+	out.Flush()
+	if err := out.Error(); err != nil {
+		// Too late for HTTP status message.
+		log.Printf("error: %v\n", err)
+	}
+	return
+}
+
+func bottleneckAvailableFairwayDepth(rw http.ResponseWriter, req *http.Request) {
+
+	mode := intervalMode(req.FormValue("mode"))
+
+	bn := mux.Vars(req)["objnam"]
+	if bn == "" {
+		http.Error(
+			rw, "Missing objnam of bottleneck",
+			http.StatusBadRequest)
+		return
+	}
+
+	from, ok := parseFormTime(rw, req, "from", time.Now().AddDate(-1, 0, 0))
+	if !ok {
+		return
+	}
+
+	to, ok := parseFormTime(rw, req, "to", from.AddDate(1, 0, 0))
+	if !ok {
+		return
+	}
+
+	if to.Before(from) {
+		to, from = from, to
+	}
+
+	los, ok := parseFormInt(rw, req, "los", 1)
+	if !ok {
+		return
+	}
+
+	conn := middleware.GetDBConn(req)
+	ctx := req.Context()
+
+	var limiting string
+	err := conn.QueryRowContext(ctx, selectLimitingSQL, bn).Scan(&limiting)
+	switch {
+	case err == sql.ErrNoRows:
+		http.Error(
+			rw, fmt.Sprintf("Unknown limitation for %s.", bn),
+			http.StatusNotFound)
+		return
+	case err != nil:
+		http.Error(
+			rw, fmt.Sprintf("DB error: %v.", err),
+			http.StatusInternalServerError)
+		return
+	}
+
+	access := limitingFactor(limiting)
+
+	log.Printf("info: time interval: (%v - %v)\n", from, to)
+
+	// load the measurements
+	ms, err := loadDepthValues(ctx, conn, bn, los, from, to)
+	if err != nil {
+		http.Error(
+			rw, fmt.Sprintf("Loading measurements failed: %v.", err),
+			http.StatusInternalServerError)
+		return
+	}
+
+	ldcRefs, err := loadLDCReferenceValue(ctx, conn, bn)
+	if err != nil {
+		http.Error(
+			rw, fmt.Sprintf("Loading LDC failed: %v.", err),
+			http.StatusInternalServerError)
+		return
+	}
+	if len(ldcRefs) == 0 {
+		http.Error(rw, "No LDC found", http.StatusNotFound)
+		return
+	}
+
+	var breaks []float64
+	if b := req.FormValue("breaks"); b != "" {
+		breaks = breaksToReferenceValue(b)
+	} else {
+		breaks = afdRefs
+	}
+
+	rw.Header().Add("Content-Type", "text/csv")
+
+	out := csv.NewWriter(rw)
+
+	// label, ldc, classes
+	record := make([]string, 1+2+len(breaks)+1)
+	record[0] = "#time"
+	record[1] = fmt.Sprintf("# < LDC (%.1f) [h]", ldcRefs[0])
+	record[2] = fmt.Sprintf("# >= LDC (%.1f) [h]", ldcRefs[0])
+	for i, v := range breaks {
+		if i == 0 {
+			record[3] = fmt.Sprintf("# < %.1f [h]", v)
+		}
+		record[i+4] = fmt.Sprintf("# >= %.1f [h]", v)
+	}
+
+	if err := out.Write(record); err != nil {
+		// Too late for HTTP status message.
+		log.Printf("error: %v\n", err)
+		return
+	}
+
+	//log.Println(len(ms))
+	//for i := range ms {
+	//	log.Println(ms[i].when, ms[i].depth)
+	//}
+
+	log.Printf("info: measurements: %d\n", len(ms))
+	if len(ms) > 1 {
+		log.Printf("info: first: %v\n", ms[0].when)
+		log.Printf("info: last: %v\n", ms[len(ms)-1].when)
+		log.Printf("info: interval: %.2f [h]\n", ms[len(ms)-1].when.Sub(ms[0].when).Hours())
+	}
+
+	interval := intervals[mode](from, to)
+
+	for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() {
+
+		ldc := ms.classify(
+			pfrom, pto,
+			ldcRefs,
+			access,
+		)
+
+		ranges := ms.classify(
+			pfrom, pto,
+			breaks,
+			access,
+		)
+
+		record[0] = label
+		for i, v := range ldc {
+			record[i+1] = fmt.Sprintf("%.3f", v.Hours())
+		}
+
+		for i, d := range ranges {
+			record[3+i] = fmt.Sprintf("%.3f", d.Hours())
+		}
+
+		if err := out.Write(record); err != nil {
+			// Too late for HTTP status message.
+			log.Printf("error: %v\n", err)
+			return
+		}
+	}
+
+	out.Flush()
+	if err := out.Error(); err != nil {
+		// Too late for HTTP status message.
+		log.Printf("error: %v\n", err)
+	}
+}
+
+var intervals = []func(time.Time, time.Time) func() (time.Time, time.Time, string){
+	monthly,
+	quarterly,
+	yearly,
+}
+
+func monthly(from, to time.Time) func() (time.Time, time.Time, string) {
+	pfrom := from
+	return func() (time.Time, time.Time, string) {
+		if pfrom.After(to) {
+			return time.Time{}, time.Time{}, ""
+		}
+		f := pfrom
+		pfrom = pfrom.AddDate(0, 1, 0)
+		label := fmt.Sprintf("%02d-%d", f.Month(), f.Year())
+		return f, f.AddDate(0, 1, 0).Add(-time.Nanosecond), label
+	}
+}
+
+func quarterly(from, to time.Time) func() (time.Time, time.Time, string) {
+	pfrom := from
+	return func() (time.Time, time.Time, string) {
+		if pfrom.After(to) {
+			return time.Time{}, time.Time{}, ""
+		}
+		f := pfrom
+		pfrom = pfrom.AddDate(0, 3, 0)
+		label := fmt.Sprintf("Q%d-%d", (int(f.Month())-1)/3+1, f.Year())
+		return f, f.AddDate(0, 3, 0).Add(-time.Nanosecond), label
+	}
+}
+
+func yearly(from, to time.Time) func() (time.Time, time.Time, string) {
+	pfrom := from
+	return func() (time.Time, time.Time, string) {
+		if pfrom.After(to) {
+			return time.Time{}, time.Time{}, ""
+		}
+		f := pfrom
+		pfrom = pfrom.AddDate(1, 0, 0)
+		label := fmt.Sprintf("%d", f.Year())
+		return f, f.AddDate(1, 0, 0).Add(-time.Nanosecond), label
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/controllers/common.go	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,126 @@
+// 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) 2019 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 (
+	"fmt"
+	"log"
+	"strconv"
+	"strings"
+
+	"github.com/jackc/pgx/pgtype"
+)
+
+type (
+	filterNode interface {
+		serialize(*strings.Builder, *[]interface{})
+	}
+
+	filterTerm struct {
+		format string
+		args   []interface{}
+	}
+
+	filterNot struct {
+		filterNode
+	}
+
+	filterAnd []filterNode
+	filterOr  []filterNode
+)
+
+func (ft *filterTerm) serialize(stmt *strings.Builder, args *[]interface{}) {
+	indices := make([]interface{}, len(ft.args))
+	for i := range indices {
+		indices[i] = len(*args) + i + 1
+	}
+	fmt.Fprintf(stmt, ft.format, indices...)
+	*args = append(*args, (*ft).args...)
+}
+
+func buildFilterTerm(format string, args ...interface{}) *filterTerm {
+	return &filterTerm{format: format, args: args}
+}
+
+func (fa filterAnd) serialize(stmt *strings.Builder, args *[]interface{}) {
+	for i, node := range fa {
+		if i > 0 {
+			stmt.WriteString(" AND ")
+		}
+		stmt.WriteByte('(')
+		node.serialize(stmt, args)
+		stmt.WriteByte(')')
+	}
+}
+
+func (fo filterOr) serialize(stmt *strings.Builder, args *[]interface{}) {
+	for i, node := range fo {
+		if i > 0 {
+			stmt.WriteString(" OR ")
+		}
+		stmt.WriteByte('(')
+		node.serialize(stmt, args)
+		stmt.WriteByte(')')
+	}
+}
+
+func (fn *filterNot) serialize(stmt *strings.Builder, args *[]interface{}) {
+	stmt.WriteString("NOT (")
+	fn.filterNode.serialize(stmt, args)
+	stmt.WriteByte(')')
+}
+
+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
+	for _, part := range parts {
+		if part = strings.ToLower(strings.TrimSpace(part)); len(part) == 0 {
+			continue
+		}
+		for _, a := range allowed {
+			if part == a {
+				accepted = append(accepted, part)
+				break
+			}
+		}
+	}
+	if len(accepted) == 0 {
+		return nil
+	}
+	var ta pgtype.TextArray
+	if err := ta.Set(accepted); err != nil {
+		log.Printf("warn: %v\n", err)
+		return nil
+	}
+	return &ta
+}
--- a/pkg/controllers/cross.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/cross.go	Mon Jun 03 10:19:18 2019 +0200
@@ -21,7 +21,6 @@
 	"net/http"
 	"time"
 
-	"gemma.intevation.de/gemma/pkg/common"
 	"gemma.intevation.de/gemma/pkg/models"
 	"gemma.intevation.de/gemma/pkg/octree"
 )
@@ -167,13 +166,6 @@
 				Type:        "MultiLineString",
 				Coordinates: joined,
 			},
-			Properties: map[string]interface{}{
-				"waterlevel": map[string]interface{}{
-					// TODO: Fetch values from database.
-					"value": float64(50),
-					"when":  start.Format(common.TimeFormat),
-				},
-			},
 		},
 	}
 
--- a/pkg/controllers/gauges.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/gauges.go	Mon Jun 03 10:19:18 2019 +0200
@@ -14,16 +14,20 @@
 package controllers
 
 import (
+	"context"
 	"database/sql"
 	"encoding/csv"
 	"fmt"
 	"log"
+	"math"
 	"net/http"
 	"sort"
 	"strconv"
+	"strings"
 	"time"
 
 	"github.com/gorilla/mux"
+	"github.com/jackc/pgx/pgtype"
 	"gonum.org/v1/gonum/stat"
 
 	"gemma.intevation.de/gemma/pkg/common"
@@ -34,26 +38,39 @@
 const (
 	selectPredictedObserveredSQL = `
 SELECT
-  a.measure_date AS measure_date,
-  a.water_level  AS predicted,
-  b.water_level  AS observed
-FROM waterway.gauge_measurements a JOIN waterway.gauge_measurements b
-  ON a.fk_gauge_id  = b.fk_gauge_id AND
-     a.measure_date = b.measure_date AND
-     a.predicted AND NOT b.predicted
+  measure_date,
+  date_issue,
+  predicted,
+  water_level
+FROM (
+  SELECT
+    location,
+    measure_date,
+    date_issue,
+    false AS predicted,
+    water_level
+  FROM waterway.gauge_measurements
+  UNION ALL
+  SELECT
+    location,
+    measure_date,
+    date_issue,
+    true AS predicted,
+    water_level
+  FROM waterway.gauge_predictions
+) AS gmp
 WHERE
-  a.fk_gauge_id = (
+  location = (
     $1::char(2),
     $2::char(3),
     $3::char(5),
     $4::char(5),
     $5::int
   ) AND
-  a.measure_date BETWEEN
-    $6::timestamp AND $6::timestamp - '72hours'::interval
-ORDER BY a.measure_date
+  measure_date BETWEEN
+    $6::timestamp - '72hours'::interval AND $6::timestamp
+ORDER BY measure_date, date_issue
 `
-
 	selectWaterlevelsSQL = `
 SELECT
   measure_date,
@@ -61,26 +78,84 @@
   value_min,
   value_max,
   predicted
+FROM (
+  SELECT
+    location,
+    measure_date,
+    date_issue,
+    water_level,
+    NULL AS value_min,
+    NULL AS value_max,
+    false AS predicted
+  FROM waterway.gauge_measurements
+  UNION ALL
+  SELECT
+    location,
+    measure_date,
+    date_issue,
+    water_level,
+    lower(conf_interval) AS value_min,
+    upper(conf_interval) AS value_max,
+    true AS predicted
+  FROM waterway.gauge_predictions
+) AS gmp
+WHERE
+`
+
+	selectAllWaterlevelsMeasuredRangeSQL = `
+SELECT
+  min(measure_date),
+  max(measure_date)
 FROM waterway.gauge_measurements
 WHERE
+  location = (
+    $1::char(2),
+    $2::char(3),
+    $3::char(5),
+    $4::char(5),
+    $5::int
+   )::isrs
+   AND staging_done
 `
-	selectWaterlevelsMeasuredSQL = `
+
+	selectAllWaterlevelsMeasuredSQL = `
+SELECT
+    extract(day from measure_date)::varchar || ':' ||
+    extract(month from measure_date)::varchar AS day_month,
+  percentile_disc(0.25) within group (order by water_level) AS q25,
+  percentile_disc(0.5)  within group (order by water_level) AS median,
+  percentile_disc(0.75) within group (order by water_level) AS q75,
+  avg(water_level) AS mean,
+  min(water_level) AS min,
+  max(water_level) AS max
+FROM waterway.gauge_measurements
+WHERE
+  location = (
+    $1::char(2),
+    $2::char(3),
+    $3::char(5),
+    $4::char(5),
+    $5::int
+   )::isrs
+   AND staging_done
+GROUP BY extract(day from measure_date)::varchar || ':' ||
+         extract(month from measure_date)::varchar;
+`
+	selectYearWaterlevelsMeasuredSQL = `
 SELECT
   measure_date,
   water_level
 FROM waterway.gauge_measurements
 WHERE
-  NOT predicted
-  AND fk_gauge_id = (
+  location = (
     $1::char(2),
     $2::char(3),
     $3::char(5),
     $4::char(5),
     $5::int
-  ) 
-  AND measure_date BETWEEN
-    $6::timestamp with time zone AND
-    $7::timestamp with time zone
+   )::isrs
+  AND staging_done
+  AND measure_date BETWEEN $6 AND $7
 ORDER BY measure_date
 `
 )
@@ -103,7 +178,100 @@
 	return "f"
 }
 
-func averageWaterlevels(rw http.ResponseWriter, req *http.Request) {
+func yearWaterlevels(rw http.ResponseWriter, req *http.Request) {
+
+	gauge := mux.Vars(req)["gauge"]
+
+	isrs, err := models.IsrsFromString(gauge)
+	if err != nil {
+		http.Error(
+			rw, fmt.Sprintf("error: Invalid ISRS code: %v", err),
+			http.StatusBadRequest)
+		return
+	}
+
+	year, _ := strconv.Atoi(mux.Vars(req)["year"])
+
+	conn := middleware.GetDBConn(req)
+
+	ctx := req.Context()
+
+	begin := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
+	end := time.Date(year+1, time.January, 1, 0, 0, 0, 0, time.UTC).Add(-time.Microsecond)
+
+	log.Printf("info: begin %s\n", begin)
+	log.Printf("info: end   %s\n", end)
+
+	rows, err := conn.QueryContext(
+		ctx,
+		selectYearWaterlevelsMeasuredSQL,
+		isrs.CountryCode,
+		isrs.LoCode,
+		isrs.FairwaySection,
+		isrs.Orc,
+		isrs.Hectometre,
+		begin,
+		end,
+	)
+	if err != nil {
+		http.Error(
+			rw, fmt.Sprintf("error: %v", err),
+			http.StatusInternalServerError)
+		return
+	}
+	defer rows.Close()
+
+	var values []float64
+
+	lastDay, lastMonth := -1, -1
+
+	write := func() error {
+		var err error
+		if len(values) > 0 {
+			mean := stat.Mean(values, nil)
+			_, err = fmt.Fprintf(
+				rw, "%02d-%02d,%s\n", lastDay, lastMonth,
+				float64format(mean))
+			values = values[:0]
+		}
+		return err
+	}
+
+	for rows.Next() {
+		var when time.Time
+		var value float64
+		if err := rows.Scan(&when, &value); err != nil {
+			log.Printf("error: %v", err)
+			// Too late for an HTTP error code.
+			return
+		}
+		when = when.UTC()
+		day, month := when.Day(), int(when.Month())
+		if day != lastDay || month != lastMonth {
+			if err := write(); err != nil {
+				log.Printf("error: %v", err)
+				// Too late for an HTTP error code.
+				return
+			}
+			lastDay, lastMonth = day, month
+		}
+		values = append(values, value)
+	}
+
+	if err := rows.Err(); err != nil {
+		log.Printf("error: %v", err)
+		// Too late for an HTTP error code.
+		return
+	}
+
+	if err := write(); err != nil {
+		log.Printf("error: %v", err)
+		// Too late for an HTTP error code.
+	}
+}
+
+func longtermWaterlevels(rw http.ResponseWriter, req *http.Request) {
+
 	gauge := mux.Vars(req)["gauge"]
 
 	isrs, err := models.IsrsFromString(gauge)
@@ -114,52 +282,41 @@
 		return
 	}
 
-	var from, to time.Time
-
-	if t := req.FormValue("to"); t != "" {
-		var err error
-		if to, err = time.ParseInLocation(common.DateFormat, t, time.UTC); err != nil {
-			http.Error(
-				rw, fmt.Sprintf("error: bad from date: %v", err),
-				http.StatusBadRequest)
-			return
-		}
-	} else {
-		y, m, d := time.Now().Date()
-		to = time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
-	}
-
-	if f := req.FormValue("from"); f != "" {
-		var err error
-		if from, err = time.ParseInLocation(common.DateFormat, f, time.UTC); err != nil {
-			http.Error(
-				rw, fmt.Sprintf("error: bad from date: %v", err),
-				http.StatusBadRequest)
-			return
-		}
-	} else {
-		from = to.AddDate(-1, 0, 0)
-	}
-
-	to = to.AddDate(0, 0, 1).Add(-time.Nanosecond)
-
-	if to.Before(from) {
-		from, to = to, from
-	}
-
 	conn := middleware.GetDBConn(req)
 
 	ctx := req.Context()
 
-	rows, err := conn.QueryContext(
+	var begin, end pgtype.Timestamp
+
+	err = conn.QueryRowContext(
 		ctx,
-		selectWaterlevelsMeasuredSQL,
+		selectAllWaterlevelsMeasuredRangeSQL,
 		isrs.CountryCode,
 		isrs.LoCode,
 		isrs.FairwaySection,
 		isrs.Orc,
 		isrs.Hectometre,
-		from, to,
+	).Scan(&begin, &end)
+
+	switch {
+	case err == sql.ErrNoRows || begin.Status != pgtype.Present || end.Status != pgtype.Present:
+		http.NotFound(rw, req)
+		return
+	case err != nil:
+		http.Error(
+			rw, fmt.Sprintf("error: %v", err),
+			http.StatusInternalServerError)
+		return
+	}
+
+	rows, err := conn.QueryContext(
+		ctx,
+		selectAllWaterlevelsMeasuredSQL,
+		isrs.CountryCode,
+		isrs.LoCode,
+		isrs.FairwaySection,
+		isrs.Orc,
+		isrs.Hectometre,
 	)
 	if err != nil {
 		http.Error(
@@ -169,14 +326,84 @@
 	}
 	defer rows.Close()
 
+	type result struct {
+		day    int
+		month  int
+		q25    float64
+		median float64
+		q75    float64
+		mean   float64
+		min    float64
+		max    float64
+	}
+
+	results := make([]result, 0, 366)
+
+	start := time.Now()
+
+	for rows.Next() {
+		var r result
+		var dayMonth string
+		if err := rows.Scan(
+			&dayMonth,
+			&r.q25,
+			&r.median,
+			&r.q75,
+			&r.mean,
+			&r.min,
+			&r.max,
+		); err != nil {
+			http.Error(
+				rw, fmt.Sprintf("error: %v", err),
+				http.StatusInternalServerError)
+		}
+		parts := strings.SplitN(dayMonth, ":", 2)
+		r.day, _ = strconv.Atoi(parts[0])
+		r.month, _ = strconv.Atoi(parts[1])
+		results = append(results, r)
+	}
+
+	if err := rows.Err(); err != nil {
+		http.Error(
+			rw, fmt.Sprintf("error: %v", err),
+			http.StatusInternalServerError)
+		return
+	}
+
+	log.Printf("info: loading entries took %s\n", time.Since(start))
+
+	log.Printf("info: days found: %d\n", len(results))
+
+	sort.Slice(results, func(i, j int) bool {
+		if d := results[i].month - results[j].month; d != 0 {
+			return d < 0
+		}
+		return results[i].day < results[j].day
+	})
+
 	rw.Header().Add("Content-Type", "text/csv")
 
 	out := csv.NewWriter(rw)
 
-	var last time.Time
-	var values []float64
+	record := []string{
+		fmt.Sprintf("#interval: %d-%d",
+			begin.Time.UTC().Year(),
+			end.Time.UTC().Year()),
+		"",
+		"",
+		"",
+		"",
+		"",
+		"",
+	}
 
-	record := []string{
+	if err := out.Write(record); err != nil {
+		log.Printf("error: %v\n", err)
+		// Too late for an HTTP error code.
+		return
+	}
+
+	record = []string{
 		"#date",
 		"#min",
 		"#max",
@@ -192,62 +419,20 @@
 		return
 	}
 
-	write := func() error {
-		if len(values) > 0 {
-			sort.Float64s(values)
-			// date
-			record[0] = last.Format(common.DateFormat)
-			// min
-			record[1] = float64format(values[0])
-			// max
-			record[2] = float64format(values[len(values)-1])
-			// mean
-			record[3] = float64format(stat.Mean(values, nil))
-			// median
-			record[4] = float64format(values[len(values)/2])
-			// Q25
-			record[5] = float64format(
-				stat.Quantile(0.25, stat.Empirical, values, nil))
-			// Q75
-			record[6] = float64format(
-				stat.Quantile(0.75, stat.Empirical, values, nil))
-
-			err := out.Write(record)
-			values = values[:0]
-			return err
-		}
-		return nil
-	}
-
-	for rows.Next() {
-		var (
-			date  time.Time
-			value float64
-		)
-		if err := rows.Scan(&date, &value); err != nil {
+	for i := range results {
+		r := &results[i]
+		record[0] = fmt.Sprintf("%02d-%02d", r.day, r.month)
+		record[1] = float64format(r.min)
+		record[2] = float64format(r.max)
+		record[3] = float64format(r.mean)
+		record[4] = float64format(r.median)
+		record[5] = float64format(r.q25)
+		record[6] = float64format(r.q75)
+		if err := out.Write(record); err != nil {
 			log.Printf("error: %v\n", err)
 			// Too late for an HTTP error code.
 			return
 		}
-		oy, om, od := last.Date()
-		ny, nm, nd := date.Date()
-		if oy != ny || om != nm || od != nd {
-			if err := write(); err != nil {
-				log.Printf("error: %v\n", err)
-				// Too late for an HTTP error code.
-				return
-			}
-			last = date
-		} else {
-			values = append(values, value)
-		}
-	}
-	write()
-
-	if err := rows.Err(); err != nil {
-		log.Printf("error: %v", err)
-		// Too late for an HTTP error code.
-		return
 	}
 
 	out.Flush()
@@ -256,7 +441,137 @@
 		// Too late for an HTTP error code.
 		return
 	}
+}
 
+func parseISRS(code string) (*models.Isrs, error) {
+	isrs, err := models.IsrsFromString(code)
+	if err != nil {
+		return nil, JSONError{
+			Code:    http.StatusBadRequest,
+			Message: fmt.Sprintf("error: Invalid ISRS code: %v", err),
+		}
+	}
+	return isrs, nil
+}
+
+type observedPredictedValues struct {
+	when      time.Time
+	observed  float64
+	predicted common.TimedValues
+}
+
+func loadNashSutcliffeData(
+	ctx context.Context,
+	conn *sql.Conn,
+	gauge *models.Isrs,
+	when time.Time,
+) ([]observedPredictedValues, error) {
+
+	var rows *sql.Rows
+	var err error
+	if rows, err = conn.QueryContext(
+		ctx,
+		selectPredictedObserveredSQL,
+		gauge.CountryCode,
+		gauge.LoCode,
+		gauge.FairwaySection,
+		gauge.Orc,
+		gauge.Hectometre,
+		when,
+	); err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	acceptedDeltas := []time.Duration{
+		-time.Hour * 24,
+		-time.Hour * 48,
+		-time.Hour * 72,
+	}
+
+	isAccepted := func(observed, predicted time.Time) bool {
+		for _, delta := range acceptedDeltas {
+			t := observed.Add(delta)
+			d := predicted.Sub(t)
+			if -10*time.Millisecond < d && d < 10*time.Millisecond {
+				return true
+			}
+		}
+		return false
+	}
+
+	var (
+		hasCurrent bool
+		current    observedPredictedValues
+		values     []observedPredictedValues
+	)
+
+	for rows.Next() {
+		var (
+			measureDate time.Time
+			issueDate   time.Time
+			predicted   bool
+			value       float64
+		)
+		if err := rows.Scan(
+			&measureDate,
+			&issueDate,
+			&predicted,
+			&value,
+		); err != nil {
+			return nil, err
+		}
+		measureDate = measureDate.UTC()
+		issueDate = issueDate.UTC()
+
+		if hasCurrent {
+			if !current.when.Equal(measureDate) {
+				if !math.IsNaN(current.observed) && len(current.predicted) > 0 {
+					values = append(values, current)
+				}
+				current = observedPredictedValues{
+					observed: math.NaN(),
+					when:     measureDate,
+				}
+			}
+		} else {
+			hasCurrent = true
+			current = observedPredictedValues{
+				observed: math.NaN(),
+				when:     measureDate,
+			}
+		}
+
+		if predicted {
+			if isAccepted(measureDate, issueDate) {
+				current.predicted = append(
+					current.predicted,
+					common.TimedValue{When: issueDate, Value: value},
+				)
+			}
+		} else {
+			current.observed = value
+		}
+	}
+
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+
+	if hasCurrent && !math.IsNaN(current.observed) && len(current.predicted) > 0 {
+		values = append(values, current)
+	}
+
+	// for i := range values {
+	// 	log.Printf("%v %f %d\n", values[i].when, values[i].observed, len(values[i].predicted))
+	// 	if len(values[i].predicted) > 0 {
+	// 		for j := range values[i].predicted {
+	// 			log.Printf("\t%v %f\n", values[i].predicted[j].When, values[i].predicted[j].Value)
+	// 		}
+	// 	}
+	// }
+
+	return values, nil
 }
 
 func nashSutcliffe(
@@ -267,11 +582,7 @@
 	gauge := mux.Vars(req)["gauge"]
 
 	var isrs *models.Isrs
-	if isrs, err = models.IsrsFromString(gauge); err != nil {
-		err = JSONError{
-			Code:    http.StatusBadRequest,
-			Message: fmt.Sprintf("error: Invalid ISRS code: %v", err),
-		}
+	if isrs, err = parseISRS(gauge); err != nil {
 		return
 	}
 
@@ -287,40 +598,17 @@
 	} else {
 		when = time.Now()
 	}
+	when = when.UTC()
 
 	ctx := req.Context()
 
-	var rows *sql.Rows
-	if rows, err = conn.QueryContext(
-		ctx,
-		selectPredictedObserveredSQL,
-		isrs.CountryCode,
-		isrs.LoCode,
-		isrs.FairwaySection,
-		isrs.Orc,
-		isrs.Hectometre,
-		when,
-	); err != nil {
+	var values []observedPredictedValues
+
+	if values, err = loadNashSutcliffeData(ctx, conn, isrs, when); err != nil {
 		return
 	}
-	defer rows.Close()
 
-	var measurements []common.NSMeasurement
-
-	for rows.Next() {
-		var m common.NSMeasurement
-		if err = rows.Scan(
-			&m.When,
-			&m.Predicted,
-			&m.Observed,
-		); err != nil {
-			return
-		}
-		measurements = append(measurements, m)
-	}
-	if err = rows.Err(); err != nil {
-		return
-	}
+	log.Printf("info: found %d value(s) for Nash Sutcliffe.\n", len(values))
 
 	type coeff struct {
 		Value   float64 `json:"value"`
@@ -333,19 +621,31 @@
 		Coeffs []coeff           `json:"coeffs"`
 	}
 
+	var predicted, observed []float64
+
 	cs := make([]coeff, 3)
 	for i := range cs {
 		cs[i].Hours = (i + 1) * 24
-		cs[i].Value, cs[i].Samples = common.NashSutcliffe(
-			measurements,
-			when,
-			when.Add(time.Duration(-cs[i].Hours)*time.Hour),
-		)
+		delta := -time.Duration(cs[i].Hours) * time.Hour
+
+		for j := range values {
+			when := values[j].when.Add(delta)
+			if p, ok := values[j].predicted.Find(when); ok {
+				predicted = append(predicted, p)
+				observed = append(observed, values[j].observed)
+			}
+		}
+
+		cs[i].Value = common.NashSutcliffe(predicted, observed)
+		cs[i].Samples = len(predicted)
+
+		predicted = predicted[:0]
+		observed = observed[:0]
 	}
 
 	jr = JSONResult{
 		Result: &coeffs{
-			When:   models.ImportTime{when},
+			When:   models.ImportTime{Time: when},
 			Coeffs: cs,
 		},
 	}
@@ -363,17 +663,30 @@
 		return
 	}
 
-	var fb filterBuilder
-	fb.stmt.WriteString(selectWaterlevelsSQL)
-
-	fb.cond(
-		" fk_gauge_id = ($%d::char(2), $%d::char(3), $%d::char(5), $%d::char(5), $%d::int) ",
-		isrs.CountryCode,
-		isrs.LoCode,
-		isrs.FairwaySection,
-		isrs.Orc,
-		isrs.Hectometre,
-	)
+	filters := filterAnd{
+		buildFilterTerm(
+			"location = ($%d::char(2), $%d::char(3), $%d::char(5), $%d::char(5), $%d::int)",
+			isrs.CountryCode,
+			isrs.LoCode,
+			isrs.FairwaySection,
+			isrs.Orc,
+			isrs.Hectometre,
+		),
+		&filterOr{
+			&filterNot{&filterTerm{format: "predicted"}},
+			buildFilterTerm(
+				`date_issue = (
+                 SELECT max(date_issue)
+                 FROM waterway.gauge_measurements gm
+                 WHERE location = ($%d::char(2), $%d::char(3), $%d::char(5), $%d::char(5), $%d::int))`,
+				isrs.CountryCode,
+				isrs.LoCode,
+				isrs.FairwaySection,
+				isrs.Orc,
+				isrs.Hectometre,
+			),
+		},
+	}
 
 	if from := req.FormValue("from"); from != "" {
 		fromTime, err := time.Parse(models.ImportTimeFormat, from)
@@ -383,7 +696,7 @@
 				http.StatusBadRequest)
 			return
 		}
-		fb.cond("measure_date >= $%d", fromTime)
+		filters = append(filters, buildFilterTerm("measure_date >= $%d", fromTime))
 	}
 
 	if to := req.FormValue("to"); to != "" {
@@ -394,14 +707,20 @@
 				http.StatusBadRequest)
 			return
 		}
-		fb.cond("measure_date <= $%d", toTime)
+		filters = append(filters, buildFilterTerm("measure_date <= $%d", toTime))
 	}
 
+	var stmt strings.Builder
+	var args []interface{}
+
+	stmt.WriteString(selectWaterlevelsSQL)
+	filters.serialize(&stmt, &args)
+
 	conn := middleware.GetDBConn(req)
 
 	ctx := req.Context()
 
-	rows, err := conn.QueryContext(ctx, fb.stmt.String(), fb.args...)
+	rows, err := conn.QueryContext(ctx, stmt.String(), args...)
 	if err != nil {
 		http.Error(
 			rw, fmt.Sprintf("error: %v", err),
--- a/pkg/controllers/importconfig.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/importconfig.go	Mon Jun 03 10:19:18 2019 +0200
@@ -98,10 +98,17 @@
 	_, oldCron := pc.Attributes.Get("cron")
 
 	session, _ := auth.GetSession(req)
+	// When a password is stored it doesn't get retransmitted to the client
+	// in order to prevent password leakage
+	// When the user changes the import configuration without a new password
+	// the old password should be conserved
+	oldPasswd, ok := pc.Attributes["password"]
 	pc.User = session.User
 	pc.Attributes = common.Attributes{}
-	pc.Attributes.Marshal(config)
-
+	if ok == true {
+		pc.Attributes["password"] = oldPasswd
+	}
+	pc.Attributes.Marshal(config) // Marshal only overwrites keys present in config
 	cron, newCron := pc.Attributes.Get("cron")
 
 	var tx *sql.Tx
@@ -177,9 +184,19 @@
 		return
 	}
 
-	what := ctor()
+	// Remove `password` from the attributes to be delivered to the client.
+	// Even a priviledged user shall not be able to see the password.
+	// (See config.ListAllPersistentConfigurationsContext() for the other
+	//  place where this is done.)
+	filteredAttributes := make(common.Attributes)
+	for key, value := range cfg.Attributes {
+		if key != "password" {
+			filteredAttributes[key] = value
+		}
+	}
 
-	if err = cfg.Attributes.Unmarshal(what); err != nil {
+	what := ctor()
+	if err = filteredAttributes.Unmarshal(what); err != nil {
 		return
 	}
 
--- a/pkg/controllers/importqueue.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/importqueue.go	Mon Jun 03 10:19:18 2019 +0200
@@ -25,7 +25,6 @@
 	"time"
 
 	"github.com/gorilla/mux"
-	"github.com/jackc/pgx/pgtype"
 
 	"gemma.intevation.de/gemma/pkg/auth"
 	"gemma.intevation.de/gemma/pkg/imports"
@@ -65,8 +64,8 @@
 SELECT enqueued FROM import.imports
 WHERE
 `
-	selectImportSummaySQL = `
-SELECT summary FROM import.imports WHERE id = $1`
+	selectImportSummarySQL = `
+SELECT summary, enqueued FROM import.imports WHERE id = $1`
 
 	selectHasNoRunningImportSQL = `
 SELECT true FROM import.imports
@@ -88,98 +87,22 @@
 DELETE FROM import.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
+type filledStmt struct {
+	stmt strings.Builder
+	args []interface{}
 }
 
-func toTextArray(txt string, allowed []string) *pgtype.TextArray {
-	parts := strings.Split(txt, ",")
-	var accepted []string
-	for _, part := range parts {
-		if part = strings.ToLower(strings.TrimSpace(part)); len(part) == 0 {
-			continue
-		}
-		for _, a := range allowed {
-			if part == a {
-				accepted = append(accepted, part)
-				break
-			}
-		}
-	}
-	if len(accepted) == 0 {
-		return nil
-	}
-	var ta pgtype.TextArray
-	if err := ta.Set(accepted); err != nil {
-		log.Printf("warn: %v\n", err)
-		return nil
-	}
-	return &ta
-}
+func buildFilters(req *http.Request) (*filledStmt, *filledStmt, *filledStmt, error) {
 
-type filterBuilder struct {
-	stmt    strings.Builder
-	args    []interface{}
-	hasCond bool
-}
-
-func (fb *filterBuilder) arg(format string, v ...interface{}) {
-	indices := make([]interface{}, len(v))
-	for i := range indices {
-		indices[i] = len(fb.args) + i + 1
-	}
-	fmt.Fprintf(&fb.stmt, format, indices...)
-	fb.args = append(fb.args, v...)
-}
-
-func (fb *filterBuilder) cond(format string, v ...interface{}) {
-	if fb.hasCond {
-		fb.stmt.WriteString(" AND ")
-	} else {
-		fb.hasCond = true
-	}
-	fb.arg(format, v...)
-}
-
-func buildFilters(req *http.Request) (l, b, a *filterBuilder, err error) {
-
-	l = new(filterBuilder)
-	a = new(filterBuilder)
-	b = new(filterBuilder)
+	var l, a, b filterAnd
 
 	var noBefore, noAfter bool
 
-	var counting bool
-
-	switch count := strings.ToLower(req.FormValue("count")); count {
-	case "1", "t", "true":
-		counting = true
-		l.stmt.WriteString(selectImportsCountSQL)
-	default:
-		l.stmt.WriteString(selectImportsSQL)
-	}
-	a.stmt.WriteString(selectAfterSQL)
-	b.stmt.WriteString(selectBeforeSQL)
-
-	cond := func(format string, v ...interface{}) {
-		l.cond(format, v...)
-		a.cond(format, v...)
-		b.cond(format, v...)
+	cond := func(format string, args ...interface{}) {
+		term := &filterTerm{format: format, args: args}
+		l = append(l, term)
+		a = append(l, term)
+		b = append(b, term)
 	}
 
 	if query := req.FormValue("query"); query != "" {
@@ -205,23 +128,23 @@
 	}
 
 	if from := req.FormValue("from"); from != "" {
-		var fromTime time.Time
-		if fromTime, err = time.Parse(models.ImportTimeFormat, from); err != nil {
-			return
+		fromTime, err := time.Parse(models.ImportTimeFormat, from)
+		if err != nil {
+			return nil, nil, nil, err
 		}
-		l.cond(" enqueued >= $%d ", fromTime)
-		b.cond(" enqueued < $%d", fromTime)
+		l = append(l, buildFilterTerm("enqueued >= $%d", fromTime))
+		b = append(b, buildFilterTerm("enqueued < $%d", fromTime))
 	} else {
 		noBefore = true
 	}
 
 	if to := req.FormValue("to"); to != "" {
-		var toTime time.Time
-		if toTime, err = time.Parse(models.ImportTimeFormat, to); err != nil {
-			return
+		toTime, err := time.Parse(models.ImportTimeFormat, to)
+		if err != nil {
+			return nil, nil, nil, err
 		}
-		l.cond(" enqueued <= $%d ", toTime)
-		a.cond(" enqueued > $%d", toTime)
+		l = append(l, buildFilterTerm("enqueued <= $%d", toTime))
+		a = append(a, buildFilterTerm("enqueued > $%d", toTime))
 	} else {
 		noAfter = true
 	}
@@ -231,32 +154,58 @@
 		cond(" id IN (SELECT id FROM warned) ")
 	}
 
-	if !l.hasCond {
-		l.stmt.WriteString(" TRUE ")
+	fl := &filledStmt{}
+	fa := &filledStmt{}
+	fb := &filledStmt{}
+
+	fa.stmt.WriteString(selectAfterSQL)
+	fb.stmt.WriteString(selectBeforeSQL)
+
+	var counting bool
+
+	switch count := strings.ToLower(req.FormValue("count")); count {
+	case "1", "t", "true":
+		counting = true
+		fl.stmt.WriteString(selectImportsCountSQL)
+	default:
+		fl.stmt.WriteString(selectImportsSQL)
 	}
-	if !b.hasCond {
-		b.stmt.WriteString(" TRUE ")
+
+	if len(l) == 0 {
+		fl.stmt.WriteString(" TRUE ")
+	} else {
+		l.serialize(&fl.stmt, &fl.args)
 	}
-	if !a.hasCond {
-		a.stmt.WriteString(" TRUE ")
+
+	if len(b) == 0 {
+		fb.stmt.WriteString(" TRUE ")
+	} else {
+		b.serialize(&fb.stmt, &fb.args)
+	}
+
+	if len(a) == 0 {
+		fa.stmt.WriteString(" TRUE ")
+	} else {
+		a.serialize(&fa.stmt, &fa.args)
 	}
 
 	if !counting {
-		l.stmt.WriteString(" ORDER BY enqueued DESC ")
-		a.stmt.WriteString(" ORDER BY enqueued LIMIT 1")
-		b.stmt.WriteString(" ORDER BY enqueued DESC LIMIT 1")
+		fl.stmt.WriteString(" ORDER BY enqueued DESC ")
+		fa.stmt.WriteString(" ORDER BY enqueued LIMIT 1")
+		fb.stmt.WriteString(" ORDER BY enqueued DESC LIMIT 1")
 	}
 
 	if noBefore {
-		b = nil
+		fb = nil
 	}
 	if noAfter {
-		a = nil
+		fa = nil
 	}
-	return
+
+	return fl, fb, fa, nil
 }
 
-func neighbored(ctx context.Context, conn *sql.Conn, fb *filterBuilder) *models.ImportTime {
+func neighbored(ctx context.Context, conn *sql.Conn, fb *filledStmt) *models.ImportTime {
 
 	var when time.Time
 	err := conn.QueryRowContext(ctx, fb.stmt.String(), fb.args...).Scan(&when)
@@ -267,7 +216,7 @@
 		log.Printf("warn: %v\n", err)
 		return nil
 	}
-	return &models.ImportTime{when}
+	return &models.ImportTime{Time: when}
 }
 
 func listImports(
@@ -276,7 +225,7 @@
 	conn *sql.Conn,
 ) (jr JSONResult, err error) {
 
-	var list, before, after *filterBuilder
+	var list, before, after *filledStmt
 
 	if list, before, after, err = buildFilters(req); err != nil {
 		return
@@ -330,7 +279,7 @@
 		if signer.Valid {
 			it.Signer = signer.String
 		}
-		it.Enqueued = models.ImportTime{enqueued}
+		it.Enqueued = models.ImportTime{Time: enqueued}
 		imports = append(imports, &it)
 	}
 
@@ -374,7 +323,11 @@
 
 	// Check if he have such a import job first.
 	var summary sql.NullString
-	err = conn.QueryRowContext(ctx, selectImportSummaySQL, id).Scan(&summary)
+	var enqueued time.Time
+	err = conn.QueryRowContext(ctx, selectImportSummarySQL, id).Scan(
+		&summary,
+		&enqueued,
+	)
 	switch {
 	case err == sql.ErrNoRows:
 		err = JSONError{
@@ -418,11 +371,13 @@
 
 	jr = JSONResult{
 		Result: struct {
-			Summary interface{}              `json:"summary,omitempty"`
-			Entries []*models.ImportLogEntry `json:"entries"`
+			Enqueued models.ImportTime        `json:"enqueued"`
+			Summary  interface{}              `json:"summary,omitempty"`
+			Entries  []*models.ImportLogEntry `json:"entries"`
 		}{
-			Summary: sum,
-			Entries: entries,
+			Enqueued: models.ImportTime{Time: enqueued},
+			Summary:  sum,
+			Entries:  entries,
 		},
 	}
 	return
@@ -573,12 +528,18 @@
 	err = tx.QueryRowContext(ctx, isPendingSQL, id).Scan(&pending, &kind)
 	switch {
 	case err == sql.ErrNoRows:
-		err = fmt.Errorf("cannot find import #%d", id)
+		err = JSONError{
+			Code:    http.StatusNotFound,
+			Message: fmt.Sprintf("cannot find import #%d", id),
+		}
 		return
 	case err != nil:
 		return
 	case !pending:
-		err = fmt.Errorf("import %d is not pending", id)
+		err = JSONError{
+			Code:    http.StatusConflict,
+			Message: fmt.Sprintf("import #%d is not pending", id),
+		}
 		return
 	}
 
--- a/pkg/controllers/printtemplates.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/printtemplates.go	Mon Jun 03 10:19:18 2019 +0200
@@ -18,6 +18,7 @@
 	"database/sql"
 	"encoding/json"
 	"net/http"
+	"strings"
 	"time"
 
 	"github.com/gorilla/mux"
@@ -32,62 +33,93 @@
 	listPrintTemplatesSQL = `
 SELECT
   template_name,
+  template_type::varchar,
   date_info,
   country
-FROM
-  users.templates
-ORDER BY date_info DESC`
+FROM users.templates
+WHERE
+`
 
 	hasPrintTemplateSQL = `
-SELECT true FROM users.templates WHERE template_name = $1`
+SELECT true FROM users.templates
+WHERE template_name = $1 AND template_type = $2::template_types`
 
 	deletePrintTemplateSQL = `
-DELETE FROM users.templates WHERE template_name = $1`
+DELETE FROM users.templates
+WHERE template_name = $1 AND template_type = $2::template_types`
 
 	selectPrintTemplateSQL = `
-SELECT template_data FROM users.templates WHERE template_name = $1`
+SELECT template_data FROM users.templates
+WHERE template_name = $1 AND template_type = $2::template_types`
 
 	insertPrintTemplateSQL = `
-INSERT INTO users.templates (template_name, template_data, country)
+INSERT INTO users.templates (template_name, template_type, template_data, country)
 SELECT
   $1,
-  $2,
+  $2::template_types,
+  $3,
   CASE WHEN pg_has_role('sys_admin', 'MEMBER') THEN NULL
        ELSE users.current_user_country()
   END`
 
 	updatePrintTemplateSQL = `
-UPDATE user.templates template_data = $2 WHERE template_name = $1`
+UPDATE user.templates template_data = $2
+WHERE template_name = $1 AND template_type = $2::template_types`
 )
 
+var templateTypes = []string{"map", "diagram", "report"}
+
 func listPrintTemplates(
 	_ interface{},
 	req *http.Request,
 	conn *sql.Conn,
 ) (jr JSONResult, err error) {
 
+	ts := mux.Vars(req)["type"]
+	if ts == "" {
+		if ts = req.FormValue("types"); ts == "" {
+			ts = strings.Join(templateTypes, ",")
+		}
+	}
+
+	types := toTextArray(ts, templateTypes)
+	filter := buildFilterTerm("template_type = ANY($%d) ", types)
+
+	var stmt strings.Builder
+	var args []interface{}
+
+	stmt.WriteString(listPrintTemplatesSQL)
+	filter.serialize(&stmt, &args)
+	stmt.WriteString(" ORDER BY date_info DESC")
+
+	var rows *sql.Rows
+	if rows, err = conn.QueryContext(req.Context(), stmt.String(), args...); err != nil {
+		return
+	}
+	defer rows.Close()
+
 	type template struct {
 		Name    string      `json:"name"`
+		Type    string      `json:"type"`
 		Time    models.Time `json:"time"`
 		Country *string     `json:"country,omitempty"`
 	}
 
-	var rows *sql.Rows
-	if rows, err = conn.QueryContext(req.Context(), listPrintTemplatesSQL); err != nil {
-		return
-	}
-	defer rows.Close()
-
 	templates := []*template{}
 
 	for rows.Next() {
 		var tmpl template
 		var w time.Time
 		var country sql.NullString
-		if err = rows.Scan(&tmpl.Name, &w, &country); err != nil {
+		if err = rows.Scan(
+			&tmpl.Name,
+			&tmpl.Type,
+			&w,
+			&country,
+		); err != nil {
 			return
 		}
-		tmpl.Time = models.Time{w}
+		tmpl.Time = models.Time{Time: w}
 		if country.Valid {
 			tmpl.Country = &country.String
 		}
@@ -104,11 +136,12 @@
 	conn *sql.Conn,
 ) (jr JSONResult, err error) {
 
-	ctx := req.Context()
-	name := mux.Vars(req)["name"]
+	vars := mux.Vars(req)
+	name, typ := vars["name"], vars["type"]
 
+	ctx := req.Context()
 	var data pgtype.Bytea
-	err = conn.QueryRowContext(ctx, selectPrintTemplateSQL, name).Scan(&data)
+	err = conn.QueryRowContext(ctx, selectPrintTemplateSQL, name, typ).Scan(&data)
 
 	switch {
 	case err == sql.ErrNoRows:
@@ -136,8 +169,9 @@
 	conn *sql.Conn,
 ) (jr JSONResult, err error) {
 
-	ctx := req.Context()
-	name := mux.Vars(req)["name"]
+	vars := mux.Vars(req)
+	name, typ := vars["name"], vars["type"]
+
 	in := input.(*json.RawMessage)
 
 	if name == "" {
@@ -154,6 +188,8 @@
 		}
 		return
 	}
+
+	ctx := req.Context()
 	var tx *sql.Tx
 	if tx, err = conn.BeginTx(ctx, nil); err != nil {
 		return
@@ -161,7 +197,7 @@
 	defer tx.Rollback()
 
 	var dummy bool
-	err = tx.QueryRowContext(ctx, hasPrintTemplateSQL, name).Scan(&dummy)
+	err = tx.QueryRowContext(ctx, hasPrintTemplateSQL, name, typ).Scan(&dummy)
 
 	switch {
 	case err == sql.ErrNoRows:
@@ -177,7 +213,7 @@
 	}
 	data := pgtype.Bytea{Bytes: *in, Status: pgtype.Present}
 
-	if _, err = tx.ExecContext(ctx, insertPrintTemplateSQL, name, &data); err != nil {
+	if _, err = tx.ExecContext(ctx, insertPrintTemplateSQL, name, typ, &data); err != nil {
 		return
 	}
 
@@ -199,9 +235,10 @@
 	conn *sql.Conn,
 ) (jr JSONResult, err error) {
 
+	vars := mux.Vars(req)
+	name, typ := vars["name"], vars["type"]
+
 	ctx := req.Context()
-	name := mux.Vars(req)["name"]
-
 	var tx *sql.Tx
 	if tx, err = conn.BeginTx(ctx, nil); err != nil {
 		return
@@ -209,7 +246,7 @@
 	defer tx.Rollback()
 
 	var dummy bool
-	err = tx.QueryRowContext(ctx, hasPrintTemplateSQL, name).Scan(&dummy)
+	err = tx.QueryRowContext(ctx, hasPrintTemplateSQL, name, typ).Scan(&dummy)
 
 	switch {
 	case err == sql.ErrNoRows:
@@ -228,7 +265,7 @@
 		return
 	}
 
-	if _, err = tx.ExecContext(ctx, deletePrintTemplateSQL, name); err != nil {
+	if _, err = tx.ExecContext(ctx, deletePrintTemplateSQL, name, typ); err != nil {
 		return
 	}
 
@@ -251,8 +288,9 @@
 	conn *sql.Conn,
 ) (jr JSONResult, err error) {
 
-	ctx := req.Context()
-	name := mux.Vars(req)["name"]
+	vars := mux.Vars(req)
+	name, typ := vars["name"], vars["type"]
+
 	in := input.(*json.RawMessage)
 
 	if name == "" {
@@ -269,6 +307,8 @@
 		}
 		return
 	}
+
+	ctx := req.Context()
 	var tx *sql.Tx
 	if tx, err = conn.BeginTx(ctx, nil); err != nil {
 		return
@@ -276,7 +316,7 @@
 	defer tx.Rollback()
 
 	var dummy bool
-	err = tx.QueryRowContext(ctx, hasPrintTemplateSQL, name).Scan(&dummy)
+	err = tx.QueryRowContext(ctx, hasPrintTemplateSQL, name, typ).Scan(&dummy)
 
 	switch {
 	case err == sql.ErrNoRows:
@@ -296,7 +336,7 @@
 	}
 	data := pgtype.Bytea{Bytes: *in, Status: pgtype.Present}
 
-	if _, err = tx.ExecContext(ctx, updatePrintTemplateSQL, name, &data); err != nil {
+	if _, err = tx.ExecContext(ctx, updatePrintTemplateSQL, name, typ, &data); err != nil {
 		return
 	}
 
--- a/pkg/controllers/proxy.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/proxy.go	Mon Jun 03 10:19:18 2019 +0200
@@ -27,7 +27,6 @@
 	"net/url"
 	"regexp"
 	"strings"
-	"time"
 
 	"github.com/gorilla/mux"
 	"golang.org/x/net/html/charset"
@@ -118,7 +117,7 @@
 ) {
 	switch enc := h.Get("Content-Encoding"); {
 	case strings.Contains(enc, "gzip"):
-		log.Println("info: gzip compression")
+		//log.Println("info: gzip compression")
 		return func(r io.Reader) (io.ReadCloser, error) {
 				return gzip.NewReader(r)
 			},
@@ -126,7 +125,7 @@
 				return gzip.NewWriter(w), nil
 			}
 	case strings.Contains(enc, "deflate"):
-		log.Println("info: deflate compression")
+		//log.Println("info: deflate compression")
 		return func(r io.Reader) (io.ReadCloser, error) {
 				return flate.NewReader(r), nil
 			},
@@ -134,7 +133,7 @@
 				return flate.NewWriter(w, flate.DefaultCompression)
 			}
 	default:
-		log.Println("info: no content compression")
+		//log.Println("info: no content compression")
 		return func(r io.Reader) (io.ReadCloser, error) {
 				if r2, ok := r.(io.ReadCloser); ok {
 					return r2, nil
@@ -177,13 +176,13 @@
 		}
 
 		go func(force io.ReadCloser) {
-			start := time.Now()
+			//start := time.Now()
 			defer func() {
 				//r.Close()
 				w.Close()
 				pw.Close()
 				force.Close()
-				log.Printf("info: rewrite took %s\n", time.Since(start))
+				//log.Printf("info: rewrite took %s\n", time.Since(start))
 			}()
 			if err := rewrite(suffix, w, r); err != nil {
 				log.Printf("error: rewrite failed: %v\n", err)
--- a/pkg/controllers/routes.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/routes.go	Mon Jun 03 10:19:18 2019 +0200
@@ -74,6 +74,11 @@
 	})).Methods(http.MethodGet)
 
 	// System Settings
+	api.Handle("/system/config", any(&JSONHandler{
+		Handle: getSystemConfig,
+		NoConn: true,
+	})).Methods(http.MethodGet)
+
 	api.Handle("/system/style/{feature}/{attr}", any(&JSONHandler{
 		Handle: getFeatureStyle,
 	})).Methods(http.MethodGet)
@@ -96,25 +101,31 @@
 	}).Methods(http.MethodGet)
 
 	// Print templates
-	api.Handle("/templates/print", any(&JSONHandler{
+	api.Handle("/templates", any(&JSONHandler{
 		Handle: listPrintTemplates,
 	})).Methods(http.MethodGet)
 
-	api.Handle("/templates/print/{name}", any(&JSONHandler{
+	tTypes := "{type:" + strings.Join(templateTypes, "|") + "}"
+
+	api.Handle("/templates/"+tTypes, any(&JSONHandler{
+		Handle: listPrintTemplates,
+	})).Methods(http.MethodGet)
+
+	api.Handle("/templates/"+tTypes+"/{name}", any(&JSONHandler{
 		Handle: fetchPrintTemplate,
 	})).Methods(http.MethodGet)
 
-	api.Handle("/templates/print/{name}", waterwayAdmin(&JSONHandler{
+	api.Handle("/templates/"+tTypes+"/{name}", waterwayAdmin(&JSONHandler{
 		Input:  func(*http.Request) interface{} { return &json.RawMessage{} },
 		Handle: createPrintTemplate,
 		Limit:  maxPrintTemplateSize,
 	})).Methods(http.MethodPost)
 
-	api.Handle("/templates/print/{name}", waterwayAdmin(&JSONHandler{
+	api.Handle("/templates/"+tTypes+"/{name}", waterwayAdmin(&JSONHandler{
 		Handle: deletePrintTemplate,
 	})).Methods(http.MethodDelete)
 
-	api.Handle("/templates/print/{name}", waterwayAdmin(&JSONHandler{
+	api.Handle("/templates/"+tTypes+"/{name}", waterwayAdmin(&JSONHandler{
 		Input:  func(*http.Request) interface{} { return &json.RawMessage{} },
 		Handle: updatePrintTemplate,
 		Limit:  maxPrintTemplateSize,
@@ -229,6 +240,7 @@
 	kinds := strings.Join([]string{
 		"bn", "gm", "fa", "wx", "wa",
 		"wg", "dmv", "fd", "dma",
+		"sec",
 	}, "|")
 
 	api.Handle("/imports/{kind:"+kinds+"}", waterwayAdmin(&JSONHandler{
@@ -299,11 +311,26 @@
 
 	// Handler to serve data to the client.
 
+	api.Handle("/data/{kind:stretch|section}/availability/{name}", any(
+		middleware.DBConn(http.HandlerFunc(stretchAvailabilty)))).Methods(http.MethodGet)
+
+	api.Handle("/data/{kind:stretch|section}/fairway-depth/{name}", any(
+		middleware.DBConn(http.HandlerFunc(stretchAvailableFairwayDepth)))).Methods(http.MethodGet)
+
+	api.Handle("/data/bottleneck/fairway-depth/{objnam}", any(
+		middleware.DBConn(http.HandlerFunc(bottleneckAvailableFairwayDepth)))).Methods(http.MethodGet)
+
+	api.Handle("/data/bottleneck/availability/{objnam}", any(
+		middleware.DBConn(http.HandlerFunc(bottleneckAvailabilty)))).Methods(http.MethodGet)
+
 	api.Handle("/data/waterlevels/{gauge}", any(
 		middleware.DBConn(http.HandlerFunc(waterlevels)))).Methods(http.MethodGet)
 
-	api.Handle("/data/average-waterlevels/{gauge}", any(
-		middleware.DBConn(http.HandlerFunc(averageWaterlevels)))).Methods(http.MethodGet)
+	api.Handle("/data/longterm-waterlevels/{gauge}", any(
+		middleware.DBConn(http.HandlerFunc(longtermWaterlevels)))).Methods(http.MethodGet)
+
+	api.Handle("/data/year-waterlevels/{gauge}/{year:[0-9]+}", any(
+		middleware.DBConn(http.HandlerFunc(yearWaterlevels)))).Methods(http.MethodGet)
 
 	api.Handle("/data/nash-sutcliffe/{gauge}", any(&JSONHandler{
 		Handle: nashSutcliffe,
--- a/pkg/controllers/srimports.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/srimports.go	Mon Jun 03 10:19:18 2019 +0200
@@ -126,7 +126,7 @@
 
 	session, _ := auth.GetSession(req)
 
-	sendEmail := req.FormValue("email") != ""
+	sendEmail := req.FormValue("send-email") != ""
 
 	jobID, err := imports.AddJob(
 		imports.SRJobKind,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/controllers/stretches.go	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,654 @@
+// 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) 2099 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 (
+	"context"
+	"database/sql"
+	"encoding/csv"
+	"fmt"
+	"log"
+	"net/http"
+	"runtime"
+	"strings"
+	"sync"
+	"time"
+
+	"gemma.intevation.de/gemma/pkg/middleware"
+	"github.com/gorilla/mux"
+)
+
+const (
+	selectSectionBottlenecks = `
+SELECT
+  distinct(b.objnam),
+  b.limiting
+FROM waterway.sections s, waterway.bottlenecks b
+WHERE ST_Intersects(b.area, s.area) AND s.name = $1`
+
+	selectStretchBottlenecks = `
+SELECT
+  distinct(b.objnam),
+  b.limiting
+FROM waterway.stretches s, waterway.bottlenecks b
+WHERE ST_Intersects(b.area, s.area) AND s.name = $1`
+)
+
+type (
+	stretchBottleneck struct {
+		name     string
+		limiting string
+	}
+
+	stretchBottlenecks []stretchBottleneck
+
+	fullStretchBottleneck struct {
+		*stretchBottleneck
+		measurements availMeasurements
+		ldc          []float64
+		breaks       []float64
+		access       func(*availMeasurement) float64
+	}
+)
+
+func (bns stretchBottlenecks) contains(limiting string) bool {
+	for i := range bns {
+		if bns[i].limiting == limiting {
+			return true
+		}
+	}
+	return false
+}
+
+func loadFullStretchBottleneck(
+	ctx context.Context,
+	conn *sql.Conn,
+	bn *stretchBottleneck,
+	los int,
+	from, to time.Time,
+	depthbreaks, widthbreaks []float64,
+) (*fullStretchBottleneck, error) {
+	measurements, err := loadDepthValues(ctx, conn, bn.name, los, from, to)
+	if err != nil {
+		return nil, err
+	}
+	ldc, err := loadLDCReferenceValue(ctx, conn, bn.name)
+	if err != nil {
+		return nil, err
+	}
+
+	var access func(*availMeasurement) float64
+	var breaks []float64
+
+	switch bn.limiting {
+	case "width":
+		access = (*availMeasurement).getWidth
+		breaks = widthbreaks
+	case "depth":
+		access = (*availMeasurement).getDepth
+		breaks = depthbreaks
+	default:
+		log.Printf(
+			"warn: unknown limitation '%s'. default to 'depth'.\n",
+			bn.limiting)
+		access = (*availMeasurement).getDepth
+		breaks = depthbreaks
+	}
+
+	return &fullStretchBottleneck{
+		stretchBottleneck: bn,
+		measurements:      measurements,
+		ldc:               ldc,
+		breaks:            breaks,
+		access:            access,
+	}, nil
+}
+
+func loadStretchBottlenecks(
+	ctx context.Context,
+	conn *sql.Conn,
+	stretch bool,
+	name string,
+) (stretchBottlenecks, error) {
+	var sql string
+	if stretch {
+		sql = selectStretchBottlenecks
+	} else {
+		sql = selectSectionBottlenecks
+	}
+
+	rows, err := conn.QueryContext(ctx, sql, name)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	var bns stretchBottlenecks
+
+	for rows.Next() {
+		var bn stretchBottleneck
+		if err := rows.Scan(
+			&bn.name,
+			&bn.limiting,
+		); err != nil {
+			return nil, err
+		}
+		bns = append(bns, bn)
+	}
+
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+
+	return bns, nil
+}
+
+func stretchAvailableFairwayDepth(rw http.ResponseWriter, req *http.Request) {
+
+	vars := mux.Vars(req)
+	stretch := vars["kind"] == "stretch"
+	name := vars["name"]
+	mode := intervalMode(req.FormValue("mode"))
+
+	depthbreaks, widthbreaks := afdRefs, afdRefs
+
+	from, ok := parseFormTime(rw, req, "from", time.Now().AddDate(-1, 0, 0))
+	if !ok {
+		return
+	}
+
+	to, ok := parseFormTime(rw, req, "to", from.AddDate(1, 0, 0))
+	if !ok {
+		return
+	}
+
+	if to.Before(from) {
+		to, from = from, to
+	}
+
+	los, ok := parseFormInt(rw, req, "los", 1)
+	if !ok {
+		return
+	}
+
+	conn := middleware.GetDBConn(req)
+	ctx := req.Context()
+
+	bns, err := loadStretchBottlenecks(ctx, conn, stretch, name)
+	if err != nil {
+		http.Error(
+			rw, fmt.Sprintf("DB error: %v.", err),
+			http.StatusInternalServerError)
+		return
+	}
+
+	if len(bns) == 0 {
+		http.Error(rw, "No bottlenecks found.", http.StatusNotFound)
+		return
+	}
+
+	if b := req.FormValue("depthbreaks"); b != "" {
+		depthbreaks = breaksToReferenceValue(b)
+	}
+
+	if b := req.FormValue("widthbreaks"); b != "" {
+		widthbreaks = breaksToReferenceValue(b)
+	}
+
+	useDepth, useWidth := bns.contains("depth"), bns.contains("width")
+
+	if useDepth && useWidth && len(widthbreaks) != len(depthbreaks) {
+		http.Error(
+			rw,
+			fmt.Sprintf("class breaks lengths differ: %d != %d",
+				len(widthbreaks), len(depthbreaks)),
+			http.StatusBadRequest,
+		)
+		return
+	}
+
+	log.Printf("info: time interval: (%v - %v)\n", from, to)
+
+	var loaded []*fullStretchBottleneck
+	var errors []error
+
+	for i := range bns {
+		l, err := loadFullStretchBottleneck(
+			ctx,
+			conn,
+			&bns[i],
+			los,
+			from, to,
+			depthbreaks, widthbreaks,
+		)
+		if err != nil {
+			log.Printf("error: %v\n", err)
+			errors = append(errors, err)
+			continue
+		}
+		loaded = append(loaded, l)
+	}
+
+	if len(loaded) == 0 {
+		http.Error(
+			rw,
+			fmt.Sprintf("No bottleneck loaded: %v", joinErrors(errors)),
+			http.StatusInternalServerError,
+		)
+		return
+	}
+
+	n := runtime.NumCPU() / 2
+	if n == 0 {
+		n = 1
+	}
+
+	type result struct {
+		label  string
+		from   time.Time
+		to     time.Time
+		ldc    []time.Duration
+		breaks []time.Duration
+	}
+
+	jobCh := make(chan *result)
+
+	var wg sync.WaitGroup
+
+	for i := 0; i < n; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			for res := range jobCh {
+
+				var ldc, breaks []time.Duration
+
+				for _, bn := range loaded {
+					l := bn.measurements.classify(
+						res.from, res.to,
+						bn.ldc,
+						bn.access,
+					)
+					b := bn.measurements.classify(
+						res.from, res.to,
+						bn.breaks,
+						bn.access,
+					)
+
+					if ldc == nil {
+						ldc, breaks = l, b
+					} else {
+						for i, v := range l {
+							ldc[i] += v
+						}
+						for i, v := range b {
+							breaks[i] += v
+						}
+					}
+				}
+
+				res.ldc = ldc
+				res.breaks = breaks
+			}
+		}()
+	}
+
+	var results []*result
+
+	interval := intervals[mode](from, to)
+
+	var breaks []float64
+
+	if useDepth {
+		breaks = depthbreaks
+	} else {
+		breaks = widthbreaks
+	}
+
+	for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() {
+
+		res := &result{
+			label: label,
+			from:  pfrom,
+			to:    pto,
+		}
+		results = append(results, res)
+		jobCh <- res
+	}
+
+	close(jobCh)
+	wg.Wait()
+
+	rw.Header().Add("Content-Type", "text/csv")
+
+	out := csv.NewWriter(rw)
+
+	// label, lnwl, classes
+	record := make([]string, 1+2+len(breaks)+1)
+	record[0] = "# time"
+	record[1] = "# < LDC [h]"
+	record[2] = "# >= LDC [h]"
+	for i, v := range breaks {
+		if useDepth && useWidth {
+			if i == 0 {
+				record[3] = "# < break_1 [h]"
+			}
+			record[i+4] = fmt.Sprintf("# >= break_%d", i+1)
+		} else {
+			if i == 0 {
+				record[3] = fmt.Sprintf("# < %.1f [h]", v)
+			}
+			record[i+4] = fmt.Sprintf("# >= %.1f [h]", v)
+		}
+	}
+
+	if err := out.Write(record); err != nil {
+		// Too late for HTTP status message.
+		log.Printf("error: %v\n", err)
+		return
+	}
+
+	// Normalize to look like as we have only one bottleneck.
+	scale := 1 / float64(len(loaded))
+
+	empty := fmt.Sprintf("%.3f", 0.0)
+	for i := range record[1:] {
+		record[i+1] = empty
+	}
+
+	for _, r := range results {
+		record[0] = r.label
+		for i, v := range r.ldc {
+			record[1+i] = fmt.Sprintf("%.3f", v.Hours()*scale)
+		}
+
+		for i, d := range r.breaks {
+			record[3+i] = fmt.Sprintf("%.3f", d.Hours()*scale)
+		}
+
+		if err := out.Write(record); err != nil {
+			// Too late for HTTP status message.
+			log.Printf("error: %v\n", err)
+			return
+		}
+	}
+
+	out.Flush()
+	if err := out.Error(); err != nil {
+		// Too late for HTTP status message.
+		log.Printf("error: %v\n", err)
+	}
+}
+
+func joinErrors(errors []error) string {
+	var b strings.Builder
+	for _, err := range errors {
+		if b.Len() > 0 {
+			b.WriteString(", ")
+		}
+		b.WriteString(err.Error())
+	}
+	return b.String()
+}
+
+func stretchAvailabilty(rw http.ResponseWriter, req *http.Request) {
+
+	vars := mux.Vars(req)
+	stretch := vars["kind"] == "stretch"
+	name := vars["name"]
+	mode := intervalMode(req.FormValue("mode"))
+
+	if name == "" {
+		http.Error(
+			rw,
+			fmt.Sprintf("Missing %s name", vars["kind"]),
+			http.StatusBadRequest,
+		)
+		return
+	}
+
+	from, ok := parseFormTime(rw, req, "from", time.Now().AddDate(-1, 0, 0))
+	if !ok {
+		return
+	}
+
+	to, ok := parseFormTime(rw, req, "to", from.AddDate(1, 0, 0))
+	if !ok {
+		return
+	}
+
+	if to.Before(from) {
+		to, from = from, to
+	}
+
+	los, ok := parseFormInt(rw, req, "los", 1)
+	if !ok {
+		return
+	}
+
+	depthbreaks, widthbreaks := afdRefs, afdRefs
+
+	if b := req.FormValue("depthbreaks"); b != "" {
+		depthbreaks = breaksToReferenceValue(b)
+	}
+
+	if b := req.FormValue("widthbreaks"); b != "" {
+		widthbreaks = breaksToReferenceValue(b)
+	}
+
+	conn := middleware.GetDBConn(req)
+	ctx := req.Context()
+
+	bns, err := loadStretchBottlenecks(ctx, conn, stretch, name)
+	if err != nil {
+		http.Error(
+			rw, fmt.Sprintf("DB error: %v.", err),
+			http.StatusInternalServerError)
+		return
+	}
+
+	if len(bns) == 0 {
+		http.Error(
+			rw,
+			"No bottlenecks found.",
+			http.StatusNotFound,
+		)
+		return
+	}
+
+	useDepth, useWidth := bns.contains("depth"), bns.contains("width")
+
+	if useDepth && useWidth && len(widthbreaks) != len(depthbreaks) {
+		http.Error(
+			rw,
+			fmt.Sprintf("class breaks lengths differ: %d != %d",
+				len(widthbreaks), len(depthbreaks)),
+			http.StatusBadRequest,
+		)
+		return
+	}
+
+	log.Printf("info: time interval: (%v - %v)\n", from, to)
+
+	var loaded []*fullStretchBottleneck
+	var errors []error
+
+	for i := range bns {
+		l, err := loadFullStretchBottleneck(
+			ctx,
+			conn,
+			&bns[i],
+			los,
+			from, to,
+			depthbreaks, widthbreaks,
+		)
+		if err != nil {
+			log.Printf("error: %v\n", err)
+			errors = append(errors, err)
+			continue
+		}
+		loaded = append(loaded, l)
+	}
+
+	if len(loaded) == 0 {
+		http.Error(
+			rw,
+			fmt.Sprintf("No bottleneck loaded: %v", joinErrors(errors)),
+			http.StatusInternalServerError,
+		)
+		return
+	}
+
+	n := runtime.NumCPU() / 2
+	if n == 0 {
+		n = 1
+	}
+
+	type result struct {
+		label  string
+		from   time.Time
+		to     time.Time
+		ldc    []float64
+		breaks []float64
+	}
+
+	jobCh := make(chan *result)
+
+	var wg sync.WaitGroup
+
+	for i := 0; i < n; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			for res := range jobCh {
+				var ldc, breaks []time.Duration
+
+				for _, bn := range loaded {
+					l := bn.measurements.classify(
+						from, to,
+						bn.ldc,
+						(*availMeasurement).getValue,
+					)
+
+					b := bn.measurements.classify(
+						from, to,
+						bn.breaks,
+						bn.access,
+					)
+
+					if ldc == nil {
+						ldc, breaks = l, b
+					} else {
+						for i, v := range l {
+							ldc[i] += v
+						}
+						for i, v := range b {
+							breaks[i] += v
+						}
+					}
+				}
+
+				duration := res.to.Sub(res.from) * time.Duration(len(loaded))
+
+				res.ldc = durationsToPercentage(duration, ldc)
+				res.breaks = durationsToPercentage(duration, breaks)
+			}
+		}()
+	}
+
+	var results []*result
+
+	interval := intervals[mode](from, to)
+
+	var breaks []float64
+
+	if useDepth {
+		breaks = depthbreaks
+	} else {
+		breaks = widthbreaks
+	}
+
+	for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() {
+
+		res := &result{
+			label: label,
+			from:  pfrom,
+			to:    pto,
+		}
+		results = append(results, res)
+
+		jobCh <- res
+	}
+
+	close(jobCh)
+	wg.Wait()
+
+	rw.Header().Add("Content-Type", "text/csv")
+
+	out := csv.NewWriter(rw)
+
+	// label, lnwl, classes
+	record := make([]string, 1+2+len(breaks)+1)
+	record[0] = "# time"
+	record[1] = "# < LDC [%%]"
+	record[2] = "# >= LDC [%%]"
+	for i, v := range breaks {
+		if useDepth && useWidth {
+			if i == 0 {
+				record[3] = "# < break_1 [%%]"
+			}
+			record[i+4] = fmt.Sprintf("# >= break_%d [%%]", i+1)
+		} else {
+			if i == 0 {
+				record[3] = fmt.Sprintf("# < %.3f [%%]", v)
+			}
+			record[i+4] = fmt.Sprintf("# >= %.3f [%%]", v)
+		}
+	}
+
+	if err := out.Write(record); err != nil {
+		// Too late for HTTP status message.
+		log.Printf("error: %v\n", err)
+		return
+	}
+
+	empty := fmt.Sprintf("%.3f", 0.0)
+	for i := range record[1:] {
+		record[i+1] = empty
+	}
+
+	for _, res := range results {
+		record[0] = res.label
+
+		for i, v := range res.ldc {
+			record[1+i] = fmt.Sprintf("%.3f", v)
+		}
+
+		for i, v := range res.breaks {
+			record[3+i] = fmt.Sprintf("%.3f", v)
+		}
+
+		if err := out.Write(record); err != nil {
+			// Too late for HTTP status message.
+			log.Printf("error: %v\n", err)
+			return
+		}
+	}
+
+	out.Flush()
+	if err := out.Error(); err != nil {
+		// Too late for HTTP status message.
+		log.Printf("error: %v\n", err)
+	}
+}
--- a/pkg/controllers/surveys.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/surveys.go	Mon Jun 03 10:19:18 2019 +0200
@@ -29,19 +29,15 @@
   s.bottleneck_id,
   s.date_info::text,
   s.depth_reference,
-  bg.objname AS gauge_objname,
+  g.objname AS gauge_objname,
   r.value AS waterlevel_value
-FROM
-  (
-	( SELECT * FROM waterway.bottlenecks AS b, waterway.gauges AS g
-		WHERE b.fk_g_fid = g.location
-	) AS bg
-	JOIN waterway.sounding_results AS s
-	ON bg.id = s.bottleneck_id
-  )
-LEFT JOIN waterway.gauges_reference_water_levels AS r
-ON s.depth_reference = r.depth_reference AND bg.location = r.gauge_id
-WHERE bg.objnam=$1`
+FROM waterway.bottlenecks AS b
+  JOIN waterway.gauges AS g
+    ON b.gauge_location = g.location AND b.gauge_validity = g.validity
+  JOIN waterway.sounding_results AS s ON b.id = s.bottleneck_id
+  LEFT JOIN waterway.gauges_reference_water_levels AS r
+    USING (depth_reference, location, validity)
+WHERE b.objnam = $1`
 )
 
 func listSurveys(
--- a/pkg/controllers/system.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/system.go	Mon Jun 03 10:19:18 2019 +0200
@@ -14,12 +14,14 @@
 package controllers
 
 import (
+	"bytes"
 	"database/sql"
 	"fmt"
 	"io/ioutil"
 	"net/http"
 	"strings"
 
+	"gemma.intevation.de/gemma/pkg/config"
 	"gemma.intevation.de/gemma/pkg/models"
 	"github.com/gorilla/mux"
 )
@@ -78,6 +80,26 @@
 	return
 }
 
+func getSystemConfig(
+	_ interface{}, req *http.Request,
+	_ *sql.Conn,
+) (jr JSONResult, err error) {
+
+	cfg := config.PublishedConfig()
+	if cfg == "" {
+		jr = JSONResult{Result: strings.NewReader("{}")}
+		return
+	}
+
+	var data []byte
+	if data, err = ioutil.ReadFile(cfg); err != nil {
+		return
+	}
+
+	jr = JSONResult{Result: bytes.NewReader(data)}
+	return
+}
+
 // Map/Feature style end points
 
 func getFeatureStyle(
--- a/pkg/controllers/uploadedimports.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/uploadedimports.go	Mon Jun 03 10:19:18 2019 +0200
@@ -178,7 +178,7 @@
 
 		session, _ := auth.GetSession(req)
 
-		sendEmail := req.FormValue("email") != ""
+		sendEmail := req.FormValue("send-email") != ""
 
 		jobID, err := imports.AddJob(
 			kind,
--- a/pkg/controllers/user.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/controllers/user.go	Mon Jun 03 10:19:18 2019 +0200
@@ -403,10 +403,10 @@
 
 	var bodyTmpl *template.Template
 	if userData.Role == "sys_admin" {
-		subject = "Sysadmin Notification TEST"
+		subject = "Gemma: Sysadmin Notification TEST"
 		bodyTmpl = testSysadminNotifyMailTmpl
 	} else if userData.Role == "waterway_admin" {
-		subject = "Waterway Admin Notification TEST"
+		subject = "Gemma: Waterway Admin Notification TEST"
 		bodyTmpl = testWWAdminNotifyMailTmpl
 	} else {
 		err = JSONError{
--- a/pkg/geoserver/boot.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/geoserver/boot.go	Mon Jun 03 10:19:18 2019 +0200
@@ -24,6 +24,8 @@
 	"net/url"
 	"strings"
 
+	"golang.org/x/net/html/charset"
+
 	"gemma.intevation.de/gemma/pkg/config"
 	"gemma.intevation.de/gemma/pkg/models"
 )
@@ -172,6 +174,7 @@
 					{"Primary key metadata table", primaryKeyMetadataTbl},
 					{"Session startup SQL", startupSQL},
 					{"Session close-up SQL", closeupSQL},
+					{"validate connections", true},
 					{"Estimated extends", false},
 				},
 			},
@@ -261,11 +264,20 @@
 		hasFeature = func(string) bool { return false }
 	}
 
+	var already []string
+
+	defer func() {
+		if len(already) > 0 {
+			log.Printf("info: already having featuretypes: %s\n",
+				strings.Join(already, ", "))
+		}
+	}()
+
 	for i := range tables {
 		table := tables[i].Name
 
 		if hasFeature(table) {
-			log.Printf("info: featuretype %s already exists.\n", table)
+			already = append(already, table)
 			continue
 		}
 
@@ -495,6 +507,8 @@
 // isSymbologyEncoding tries to figure out if its plain SLD or SE.
 func isSymbologyEncoding(data string) bool {
 	decoder := xml.NewDecoder(strings.NewReader(data))
+	decoder.CharsetReader = charset.NewReaderLabel
+
 	for {
 		tok, err := decoder.Token()
 		switch {
@@ -524,10 +538,19 @@
 			models.IntWMS,
 			models.IntWithStyle))
 
+	var already []string
+
+	defer func() {
+		if len(already) > 0 {
+			log.Printf("info: already having styles: %s\n",
+				strings.Join(already, ", "))
+		}
+	}()
+
 	for i := range entries {
 		entry := &entries[i]
 		if stls.hasStyle(entry.Name) {
-			log.Printf("warn: already has style for %s\n", entry.Name)
+			already = append(already, entry.Name)
 			continue
 		}
 		if err := updateStyle(entry, true); err != nil {
--- a/pkg/imports/agm.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/agm.go	Mon Jun 03 10:19:18 2019 +0200
@@ -20,6 +20,7 @@
 	"database/sql"
 	"encoding/csv"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"math"
@@ -55,10 +56,10 @@
 
 func (agmJobCreator) Create() Job { return new(ApprovedGaugeMeasurements) }
 
-func (agmJobCreator) Depends() []string {
-	return []string{
-		"gauges",
-		"gauge_measurements",
+func (agmJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"gauge_measurements"},
+		{"gauges"},
 	}
 }
 
@@ -75,8 +76,8 @@
   SELECT o.id AS id
   FROM waterway.gauge_measurements o
   JOIN waterway.gauge_measurements n
-    ON n.fk_gauge_id = o.fk_gauge_id AND n.measure_date = o.measure_date
-    WHERE n.id     IN (SELECT key FROM staged)
+    USING (location, measure_date)
+    WHERE n.id IN (SELECT key FROM staged)
 	  AND o.id NOT IN (SELECT key FROM staged)
 )
 DELETE FROM waterway.gauge_measurements WHERE id IN (SELECT id from to_delete)`
@@ -119,38 +120,23 @@
 }
 
 type agmLine struct {
-	CountryCode        string   `json:"country-code"`
-	Sender             string   `json:"sender"`
-	LanguageCode       string   `json:"language-code"`
-	DateIssue          timetz   `json:"date-issue"`
-	ReferenceCode      string   `json:"reference-code"`
-	WaterLevel         float64  `json:"water-level"`
-	Predicted          bool     `json:"predicted"`
-	ValueMin           *float64 `json:"value-min"`
-	ValueMax           *float64 `json:"value-max"`
-	DateInfo           timetz   `json:"date-info"`
-	SourceOrganization string   `json:"source-organization"`
+	CountryCode        string  `json:"country-code"`
+	Sender             string  `json:"sender"`
+	LanguageCode       string  `json:"language-code"`
+	DateIssue          timetz  `json:"date-issue"`
+	ReferenceCode      string  `json:"reference-code"`
+	WaterLevel         float64 `json:"water-level"`
+	DateInfo           timetz  `json:"date-info"`
+	SourceOrganization string  `json:"source-organization"`
 }
 
 func (a *agmLine) hasDiff(b *agmLine) bool {
 	const eps = 0.00001
-	fdiff := func(x, y *float64) bool {
-		if x == nil && y == nil {
-			return false
-		}
-		if (x == nil && y != nil) || (x != nil && y == nil) {
-			return true
-		}
-		return math.Abs(*x-*y) > eps
-	}
 	return a.CountryCode != b.CountryCode ||
 		a.Sender != b.Sender ||
 		a.LanguageCode != b.LanguageCode ||
 		a.ReferenceCode != b.ReferenceCode ||
 		math.Abs(a.WaterLevel-b.WaterLevel) > eps ||
-		a.Predicted != b.Predicted ||
-		fdiff(a.ValueMin, b.ValueMin) ||
-		fdiff(a.ValueMax, b.ValueMax) ||
 		a.SourceOrganization != b.SourceOrganization
 }
 
@@ -170,19 +156,20 @@
   date_issue,
   reference_code,
   water_level,
-  predicted,
-  value_min,
-  value_max,
   date_info,
   source_organization
 FROM waterway.gauge_measurements
 WHERE
-  fk_gauge_id = ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int) AND
-  measure_date = $6 AND staging_done`
+  location
+    = ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int)
+  AND measure_date = $6
+  AND staging_done
+`
 
 	agmInsertSQL = `
 INSERT INTO waterway.gauge_measurements (
-  fk_gauge_id,
+  location,
+  validity,
   measure_date,
   country_code,
   sender,
@@ -190,15 +177,17 @@
   date_issue,
   reference_code,
   water_level,
-  predicted,
-  value_min,
-  value_max,
   date_info,
   source_organization,
-  is_waterlevel,
   staging_done
-) VALUES(
+) VALUES (
   ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int),
+  COALESCE(
+    (SELECT validity FROM waterway.gauges
+       WHERE location
+            = ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int)
+         AND validity @> CAST($6 AS timestamp with time zone)),
+    tstzrange(NULL, NULL)),
   $6,
   $7,
   $8,
@@ -208,10 +197,6 @@
   $12,
   $13,
   $14,
-  $15,
-  $16,
-  $17,
-  true,
   false
 )
 RETURNING id`
@@ -223,6 +208,8 @@
 `
 )
 
+var errContinue = errors.New("continue")
+
 // Do executes the actual approved gauge measurements import.
 func (agm *ApprovedGaugeMeasurements) Do(
 	ctx context.Context,
@@ -290,28 +277,25 @@
 		return nil, fmt.Errorf("Missing columns: %s", strings.Join(missing, ", "))
 	}
 
-	tx, err := conn.BeginTx(ctx, nil)
-	if err != nil {
-		return nil, err
-	}
-	defer tx.Rollback()
-
-	gaugeCheckStmt, err := tx.PrepareContext(ctx, agmGaugeCheckSQL)
+	gaugeCheckStmt, err := conn.PrepareContext(ctx, agmGaugeCheckSQL)
 	if err != nil {
 		return nil, err
 	}
 	defer gaugeCheckStmt.Close()
-	selectStmt, err := tx.PrepareContext(ctx, agmSelectSQL)
+
+	selectStmt, err := conn.PrepareContext(ctx, agmSelectSQL)
 	if err != nil {
 		return nil, err
 	}
 	defer selectStmt.Close()
-	insertStmt, err := tx.PrepareContext(ctx, agmInsertSQL)
+
+	insertStmt, err := conn.PrepareContext(ctx, agmInsertSQL)
 	if err != nil {
 		return nil, err
 	}
 	defer insertStmt.Close()
-	trackStmt, err := tx.PrepareContext(ctx, trackImportSQL)
+
+	trackStmt, err := conn.PrepareContext(ctx, trackImportSQL)
 	if err != nil {
 		return nil, err
 	}
@@ -321,12 +305,24 @@
 
 	checkedGauges := map[models.Isrs]bool{}
 
+	warnLimiter := misc.WarningLimiter{Log: feedback.Warn, MaxWarnings: 100}
+	warn := warnLimiter.Warn
+	defer warnLimiter.Close()
+
 lines:
-	for line := 1; ; line++ {
+	for line, ignored := 1, 0; ; line++ {
 
 		row, err := r.Read()
 		switch {
 		case err == io.EOF || len(row) == 0:
+			feedback.Info("Read %d entries in CSV file", line-1)
+			if ignored > 0 {
+				feedback.Info("%d entries ignored", ignored)
+			}
+			if ignored == line-1 {
+				return nil, UnchangedError("No entries imported")
+			}
+			feedback.Info("Imported %d entries with changes", len(entries))
 			break lines
 		case err != nil:
 			return nil, fmt.Errorf("CSV parsing failed: %v", err)
@@ -340,7 +336,8 @@
 
 		if exists, found := checkedGauges[*gid]; found {
 			if !exists {
-				feedback.Warn("Ignoring data for unknown gauge %s", gid.String())
+				// Just ignore the line since we have already warned
+				ignored++
 				continue lines
 			}
 		} else { // not found in gauge cache
@@ -356,7 +353,8 @@
 			}
 			checkedGauges[*gid] = exists
 			if !exists {
-				feedback.Warn("Ignoring data for unknown gauge %s", gid.String())
+				warn("Ignoring data for unknown gauge %s", gid.String())
+				ignored++
 				continue lines
 			}
 		}
@@ -374,9 +372,6 @@
 			oldDateIssue          time.Time
 			oldReferenceCode      string
 			oldValue              float64
-			oldPredicted          bool
-			oldValueMin           sql.NullFloat64
-			oldValueMax           sql.NullFloat64
 			oldDateInfo           time.Time
 			oldSourceOrganization string
 		)
@@ -397,9 +392,6 @@
 			&oldDateIssue,
 			&oldReferenceCode,
 			&oldValue,
-			&oldPredicted,
-			&oldValueMin,
-			&oldValueMax,
 			&oldDateInfo,
 			&oldSourceOrganization,
 		)
@@ -425,48 +417,55 @@
 		}
 		newValue := value
 
-		newPredicted := false
-
-		newValueMin := sql.NullFloat64{
-			Float64: 0,
-			Valid:   true,
-		}
-		newValueMax := sql.NullFloat64{
-			Float64: 0,
-			Valid:   true,
-		}
-
 		newDateInfo := newDateIssue
 
 		newSourceOrganization := newSender
 
-		var newID int64
+		switch err := func() error {
+			tx, err := conn.BeginTx(ctx, nil)
+			if err != nil {
+				return err
+			}
+			defer tx.Rollback()
+
+			var newID int64
 
-		if err := insertStmt.QueryRowContext(
-			ctx,
-			gid.CountryCode,
-			gid.LoCode,
-			gid.FairwaySection,
-			gid.Orc,
-			gid.Hectometre,
-			md,
-			newCountryCode,
-			newSender,
-			newLanguageCode,
-			newDateIssue,
-			newReferenceCode,
-			newValue,
-			newPredicted,
-			newValueMin,
-			newValueMax,
-			newDateInfo,
-			newSourceOrganization,
-		).Scan(&newID); err != nil {
-			return nil, err
-		}
-		if _, err := trackStmt.ExecContext(
-			ctx, importID, "waterway.gauge_measurements", newID,
-		); err != nil {
+			if err := tx.StmtContext(ctx, insertStmt).QueryRowContext(
+				ctx,
+				gid.CountryCode,
+				gid.LoCode,
+				gid.FairwaySection,
+				gid.Orc,
+				gid.Hectometre,
+				md,
+				newCountryCode,
+				newSender,
+				newLanguageCode,
+				newDateIssue,
+				newReferenceCode,
+				newValue,
+				newDateInfo,
+				newSourceOrganization,
+			).Scan(&newID); err != nil {
+				warn(handleError(err).Error())
+				ignored++
+				return errContinue
+			}
+
+			if _, err := tx.StmtContext(ctx, trackStmt).ExecContext(
+				ctx, importID, "waterway.gauge_measurements", newID,
+			); err != nil {
+				return err
+			}
+
+			if err = tx.Commit(); err != nil {
+				err = fmt.Errorf("Commit failed: %v", err)
+			}
+			return err
+		}(); {
+		case err == errContinue:
+			continue lines
+		case err != nil:
 			return nil, err
 		}
 
@@ -477,9 +476,6 @@
 			newDateIssue,
 			newReferenceCode,
 			newValue,
-			newPredicted,
-			newValueMin,
-			newValueMax,
 			newDateInfo,
 			newSourceOrganization,
 		)
@@ -499,9 +495,6 @@
 				oldDateIssue,
 				oldReferenceCode,
 				oldValue,
-				oldPredicted,
-				oldValueMin,
-				oldValueMax,
 				oldDateInfo,
 				oldSourceOrganization,
 			)
@@ -514,10 +507,6 @@
 		entries = append(entries, ase)
 	}
 
-	if err := tx.Commit(); err != nil {
-		return nil, fmt.Errorf("Commit failed: %v", err)
-	}
-
 	feedback.Info("Importing approved gauge measurements took %s",
 		time.Since(start))
 
@@ -531,19 +520,9 @@
 	dateIssue time.Time,
 	referenceCode string,
 	waterLevel float64,
-	predicted bool,
-	valueMin sql.NullFloat64,
-	valueMax sql.NullFloat64,
 	dateInfo time.Time,
 	sourceOrganization string,
 ) *agmLine {
-	nilFloat := func(v sql.NullFloat64) *float64 {
-		var p *float64
-		if v.Valid {
-			p = &v.Float64
-		}
-		return p
-	}
 	return &agmLine{
 		CountryCode:        countryCode,
 		Sender:             sender,
@@ -551,9 +530,6 @@
 		DateIssue:          timetz{dateIssue},
 		ReferenceCode:      referenceCode,
 		WaterLevel:         waterLevel,
-		Predicted:          predicted,
-		ValueMin:           nilFloat(valueMin),
-		ValueMax:           nilFloat(valueMax),
 		DateInfo:           timetz{dateInfo},
 		SourceOrganization: sourceOrganization,
 	}
--- a/pkg/imports/bn.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/bn.go	Mon Jun 03 10:19:18 2019 +0200
@@ -20,6 +20,7 @@
 	"errors"
 	"regexp"
 	"strconv"
+	"strings"
 	"time"
 
 	"gemma.intevation.de/gemma/pkg/soap/ifbn"
@@ -45,9 +46,15 @@
 SELECT true FROM waterway.bottlenecks WHERE bottleneck_id = $1`
 
 	insertBottleneckSQL = `
+WITH
+bounds (b) AS (VALUES (isrs_fromText($5)), (isrs_fromText($6))),
+r AS (SELECT isrsrange(
+    (SELECT b FROM bounds ORDER BY b USING <~ FETCH FIRST ROW ONLY),
+    (SELECT b FROM bounds ORDER BY b USING >~ FETCH FIRST ROW ONLY)) AS r)
 INSERT INTO waterway.bottlenecks (
   bottleneck_id,
-  fk_g_fid,
+  gauge_location,
+  gauge_validity,
   objnam,
   nobjnm,
   stretch,
@@ -59,16 +66,18 @@
   limiting,
   date_info,
   source_organization
-) VALUES(
+) VALUES (
   $1,
   isrs_fromText($2),
+  COALESCE(
+    (SELECT validity FROM waterway.gauges
+       WHERE location = isrs_fromText($2) AND NOT erased),
+    tstzrange(NULL, NULL)),
   $3,
   $4,
-  isrsrange(least(isrs_fromText($5), isrs_fromText($6)),
-            greatest(isrs_fromText($5), isrs_fromText($6))),
+  (SELECT r FROM r),
   ISRSrange_area(
-    ISRSrange_axis(isrsrange(least(isrs_fromText($5), isrs_fromText($6)),
-                             greatest(isrs_fromText($5), isrs_fromText($6))),
+    ISRSrange_axis((SELECT r FROM r),
                    $14),
     (SELECT ST_Collect(CAST(area AS geometry))
         FROM waterway.waterway_area)),
@@ -81,6 +90,15 @@
   $13
 )
 RETURNING id`
+
+	insertBottleneckMaterialSQL = `
+INSERT INTO waterway.bottlenecks_riverbed_materials (
+   bottleneck_id,
+   riverbed
+) VALUES (
+   $1,
+   $2
+)`
 )
 
 type bnJobCreator struct{}
@@ -95,10 +113,10 @@
 
 func (bnJobCreator) Create() Job { return new(Bottleneck) }
 
-func (bnJobCreator) Depends() []string {
-	return []string{
-		"gauges",
-		"bottlenecks",
+func (bnJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"bottlenecks", "bottlenecks_riverbed_materials"},
+		{"gauges", "distance_marks_virtual", "waterway_axis", "waterway_area"},
 	}
 }
 
@@ -134,14 +152,6 @@
 	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,
@@ -161,7 +171,8 @@
 		}
 
 		if resp.Export_bn_by_isrsResult == nil {
-			return nil, errors.New("no Bottlenecks found")
+			return nil, errors.New(
+				"The requested service returned no bottlenecks")
 		}
 
 		return resp.Export_bn_by_isrsResult.BottleNeckType, nil
@@ -187,7 +198,7 @@
 
 	feedback.Info("Found %d bottlenecks for import", len(bns))
 
-	var hasStmt, insertStmt, trackStmt *sql.Stmt
+	var hasStmt, insertStmt, insertMaterialStmt, trackStmt *sql.Stmt
 
 	for _, x := range []struct {
 		sql  string
@@ -195,6 +206,7 @@
 	}{
 		{hasBottleneckSQL, &hasStmt},
 		{insertBottleneckSQL, &insertStmt},
+		{insertBottleneckMaterialSQL, &insertMaterialStmt},
 		{trackImportSQL, &trackStmt},
 	} {
 		var err error
@@ -211,7 +223,7 @@
 	for _, bn := range bns {
 		if err := storeBottleneck(
 			ctx, importID, conn, feedback, bn, &nids, tolerance,
-			hasStmt, insertStmt, trackStmt); err != nil {
+			hasStmt, insertStmt, insertMaterialStmt, trackStmt); err != nil {
 			return nil, err
 		}
 	}
@@ -237,7 +249,7 @@
 	bn *ifbn.BottleNeckType,
 	nids *[]string,
 	tolerance float64,
-	hasStmt, insertStmt, trackStmt *sql.Stmt,
+	hasStmt, insertStmt, insertMaterialStmt, trackStmt *sql.Stmt,
 ) error {
 
 	tx, err := conn.BeginTx(ctx, nil)
@@ -247,7 +259,8 @@
 	defer tx.Rollback()
 
 	var found bool
-	err = tx.Stmt(hasStmt).QueryRowContext(ctx, bn.Bottleneck_id).Scan(&found)
+	err = tx.StmtContext(ctx, hasStmt).QueryRowContext(ctx,
+		bn.Bottleneck_id).Scan(&found)
 	switch {
 	case err == sql.ErrNoRows:
 		// This is good.
@@ -261,6 +274,19 @@
 
 	rb, lb := splitRBLB(bn.Rb_lb)
 
+	var revisitingTime *int
+	if bn.Revisiting_time != nil &&
+		len(strings.TrimSpace(*bn.Revisiting_time)) > 0 {
+		i, err := strconv.Atoi(*bn.Revisiting_time)
+		if err != nil {
+			feedback.Warn(
+				"Cannot convert given revisiting time '%s' to number of months",
+				*bn.Revisiting_time)
+		} else {
+			revisitingTime = &i
+		}
+	}
+
 	var limiting, country string
 
 	if bn.Limiting_factor != nil {
@@ -273,7 +299,7 @@
 
 	var nid int64
 
-	err = tx.Stmt(insertStmt).QueryRowContext(
+	err = tx.StmtContext(ctx, insertStmt).QueryRowContext(
 		ctx,
 		bn.Bottleneck_id,
 		bn.Fk_g_fid,
@@ -283,7 +309,7 @@
 		rb,
 		lb,
 		country,
-		revisitingTime(bn.Revisiting_time),
+		revisitingTime,
 		limiting,
 		bn.Date_Info,
 		bn.Source,
@@ -295,7 +321,23 @@
 		return nil
 	}
 
-	if _, err := tx.Stmt(trackStmt).ExecContext(
+	if bn.Riverbed != nil {
+		for _, material := range bn.Riverbed.Material {
+			if material != nil {
+				mat := string(*material)
+				if _, err := tx.StmtContext(ctx,
+					insertMaterialStmt).ExecContext(
+					ctx, nid, material); err != nil {
+					feedback.Warn(
+						"Failed to insert riverbed material '%s' for bottleneck '%s'.",
+						mat, bn.OBJNAM)
+					feedback.Warn(handleError(err).Error())
+				}
+			}
+		}
+	}
+
+	if _, err := tx.StmtContext(ctx, trackStmt).ExecContext(
 		ctx, importID, "waterway.bottlenecks", nid,
 	); err != nil {
 		return err
--- a/pkg/imports/config.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/config.go	Mon Jun 03 10:19:18 2019 +0200
@@ -269,7 +269,12 @@
 			if pc.Attributes == nil {
 				pc.Attributes = common.Attributes{}
 			}
-			pc.Attributes.Set(k.String, v.String)
+			// Prevent sending the `password` back to the client.
+			// (See importconfig.infoImportConfig() for the other place
+			//  where this is done.)
+			if k.String != "password" {
+				pc.Attributes.Set(k.String, v.String)
+			}
 		}
 	}
 
--- a/pkg/imports/dma.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/dma.go	Mon Jun 03 10:19:18 2019 +0200
@@ -56,9 +56,10 @@
 
 func (dmaJobCreator) Create() Job { return new(DistanceMarksAshore) }
 
-func (dmaJobCreator) Depends() []string {
-	return []string{
-		"distance_marks",
+func (dmaJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"distance_marks"},
+		{},
 	}
 }
 
@@ -77,36 +78,28 @@
 const (
 	deleteDistanceMarksSQL = `
 WITH resp AS (
-  SELECT best_utm(area::geometry) AS t,
-         ST_Transform(area::geometry, best_utm(area::geometry)) AS a
-  FROM users.responsibility_areas
-  WHERE country = users.current_user_country()
+  SELECT users.current_user_area_utm() AS a
 )
 DELETE FROM waterway.distance_marks
-WHERE ST_Covers(
-  (SELECT a FROM resp),
-  ST_Transform(geom::geometry, (SELECT t FROM resp)))
+WHERE pg_has_role('sys_admin', 'MEMBER')
+    OR ST_Covers((SELECT a FROM resp),
+      ST_Transform(geom::geometry, (SELECT ST_SRID(a) FROM resp)))
 `
 	insertDistanceMarksSQL = `
 WITH resp AS (
-  SELECT best_utm(area::geometry) AS t,
-         ST_Transform(area::geometry, best_utm(area::geometry)) AS a
-  FROM users.responsibility_areas
-  WHERE country = users.current_user_country()
+  SELECT users.current_user_area_utm() AS a
 )
 INSERT INTO waterway.distance_marks (geom, catdis)
-SELECT ST_Transform(clipped.geom, 4326)::geography, $3 FROM (
-    SELECT (ST_Dump(
-       ST_Intersection(
-         (SELECT a FROM resp),
-         ST_Transform(
-           ST_GeomFromWKB($1, $2::integer),
-           (SELECT t FROM resp)
-         )
-       )
-     )).geom AS geom
-  ) AS clipped
-  WHERE clipped.geom IS NOT NULL
+SELECT ST_Transform(new_dma, 4326), $3
+  FROM (SELECT
+      CASE WHEN pg_has_role('sys_admin', 'MEMBER')
+        THEN dma
+        ELSE ST_Intersection((SELECT a FROM resp),
+          ST_Transform(dma, (SELECT ST_SRID(a) FROM resp)))
+        END AS new_dma
+    FROM ST_GeomFromWKB($1, $2::integer) AS dma (dma)) AS new_dma
+  WHERE NOT ST_IsEmpty(new_dma)
+RETURNING id
 `
 )
 
@@ -169,6 +162,7 @@
 		unsupported       = stringCounter{}
 		missingProperties int
 		badProperties     int
+		outside           int
 		features          int
 	)
 
@@ -211,13 +205,19 @@
 				if err := json.Unmarshal(*feature.Geometry.Coordinates, &p); err != nil {
 					return err
 				}
-				if _, err := insertStmt.ExecContext(
+				var dmaid int64
+				err := insertStmt.QueryRowContext(
 					ctx,
 					p.asWKB(),
 					epsg,
 					props.HydroCatdis,
-				); err != nil {
-					feedback.Error("error: %s", err)
+				).Scan(&dmaid)
+				switch {
+				case err == sql.ErrNoRows:
+					outside++
+					// ignore -> filtered by responsibility area
+					continue
+				case err != nil:
 					return err
 				}
 				features++
@@ -242,6 +242,10 @@
 		feedback.Warn("Unsupported types found: %s", unsupported)
 	}
 
+	if outside > 0 {
+		feedback.Info("Features outside responsibility area: %d", outside)
+	}
+
 	if features == 0 {
 		err := errors.New("No features found")
 		feedback.Error("%v", err)
--- a/pkg/imports/dmv.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/dmv.go	Mon Jun 03 10:19:18 2019 +0200
@@ -48,9 +48,10 @@
 
 func (dmvJobCreator) Create() Job { return new(DistanceMarksVirtual) }
 
-func (dmvJobCreator) Depends() []string {
-	return []string{
-		"distance_marks_virtual",
+func (dmvJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"distance_marks_virtual"},
+		{},
 	}
 }
 
@@ -86,15 +87,9 @@
 
 	start := time.Now()
 
-	tx, err := conn.BeginTx(ctx, nil)
-	if err != nil {
-		return nil, err
-	}
-	defer tx.Rollback()
-
-	responseData, err := getRisData(
-		tx,
+	responseData, _, err := getRisData(
 		ctx,
+		conn,
 		feedback,
 		dmv.Username,
 		dmv.Password,
@@ -105,6 +100,12 @@
 		return nil, err
 	}
 
+	tx, err := conn.BeginTx(ctx, nil)
+	if err != nil {
+		return nil, err
+	}
+	defer tx.Rollback()
+
 	insertStmt, err := tx.PrepareContext(ctx, insertDistanceMarksVirtualSQL)
 	if err != nil {
 		return nil, err
--- a/pkg/imports/email.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/email.go	Mon Jun 03 10:19:18 2019 +0200
@@ -28,7 +28,7 @@
 const (
 	selectEmailSQL = `SELECT email_address FROM users.list_users WHERE username = $1`
 
-	importNotificationMailSubject = `import notification mail`
+	importNotificationMailSubject = `Gemma: import notification mail`
 )
 
 var (
@@ -38,15 +38,15 @@
 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 "unchanged" }}The import has not changed any data in the database.{{ end -}}
-{{ if eq .State "failed" }}The import failed for some reasons.{{ end -}}
+{{ if eq .State "accepted" }}The imported data were successfully integrated into the database. {{ end -}}
+{{ if eq .State "unchanged" }}The import has not changed any data in 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 -}}
+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 }}importqueue{{ end }}/{{ .ID }}
+{{ .Server }}/#/imports/overview/{{ .ID }}
 
 Best regards
     Your service team`))
--- a/pkg/imports/erdms.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/erdms.go	Mon Jun 03 10:19:18 2019 +0200
@@ -18,24 +18,59 @@
 	"context"
 	"database/sql"
 	"fmt"
+	"log"
 	"strings"
 
 	"gemma.intevation.de/gemma/pkg/soap"
 	"gemma.intevation.de/gemma/pkg/soap/erdms"
 )
 
-const selectUserCountriesSQL = `SELECT DISTINCT country FROM users.list_users`
+const (
+	selectUserCountriesSQL = `
+SELECT DISTINCT country FROM users.list_users
+WHERE country <> '--'
+`
+)
+
+func userCountries(ctx context.Context, conn *sql.Conn) ([]string, error) {
+	rows, err := conn.QueryContext(ctx, selectUserCountriesSQL)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	var countries []string
+
+	for rows.Next() {
+		var country string
+		if err = rows.Scan(&country); err != nil {
+			return nil, err
+		}
+		countries = append(countries, country)
+	}
+
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+
+	return countries, nil
+}
 
 func getRisData(
-	tx *sql.Tx,
 	ctx context.Context,
+	conn *sql.Conn,
 	feedback Feedback,
 	username string,
 	password string,
 	URL string,
 	insecure bool,
 	funcode string,
-) ([]*erdms.GetRisDataXMLResponse, error) {
+) ([]*erdms.GetRisDataXMLResponse, []string, error) {
+
+	countries, err := userCountries(ctx, conn)
+	if err != nil {
+		return nil, nil, err
+	}
 
 	var auth *soap.BasicAuth
 	if username != "" {
@@ -47,21 +82,10 @@
 
 	client := erdms.NewRefService(URL, insecure, auth)
 
-	rows, err := tx.QueryContext(ctx, selectUserCountriesSQL)
-	if err != nil {
-		return nil, err
-	}
-	defer rows.Close()
+	var responseData []*erdms.GetRisDataXMLResponse
+	for _, country := range countries {
 
-	var country string
-	var countries []string
-	var responseData []*erdms.GetRisDataXMLResponse
-	for rows.Next() {
-		err = rows.Scan(&country)
-		if err != nil {
-			return nil, err
-		}
-		countries = append(countries, country)
+		feedback.Info("Request RIS index for country %s", country)
 
 		request := &erdms.GetRisDataXML{
 			GetRisDataXMLType: &erdms.GetRisDataXMLType{
@@ -70,13 +94,26 @@
 			},
 		}
 
+		const maxTries = 3
+
+		tries := 0
+
+	again:
 		data, err := client.GetRisDataXML(request)
 		if err != nil {
-			return nil, fmt.Errorf("Error requesting ERDMS service: %v", err)
+			if t, ok := err.(interface{ Timeout() bool }); ok && t.Timeout() && tries < maxTries {
+				log.Println("warn: ERDMS SOAP request timed out. Trying again.")
+				tries++
+				goto again
+			}
+			return nil, nil, fmt.Errorf(
+				"Error requesting ERDMS service: %v", err)
 		}
 		responseData = append(responseData, data)
 	}
+
 	feedback.Info("Import data for countries: %s.",
 		strings.Join(countries, ", "))
-	return responseData, nil
+
+	return responseData, countries, nil
 }
--- a/pkg/imports/errors.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/errors.go	Mon Jun 03 10:19:18 2019 +0200
@@ -29,13 +29,41 @@
 
 // Handle PostgreSQL error codes
 const (
-	noDataFound = "P0002"
+	notNullViolation    = "23502"
+	foreignKeyViolation = "23503"
+	noDataFound         = "P0002"
 )
 
 type dbError pgx.PgError
 
 func (err dbError) Error() string {
 	switch err.Code {
+	case notNullViolation:
+		switch err.SchemaName {
+		case "waterway":
+			switch err.TableName {
+			case "gauges":
+				switch err.ColumnName {
+				case "objname":
+					return "Missing objname"
+				case "geom":
+					return "Missing lat/lon"
+				case "zero_point":
+					return "Missing zeropoint"
+				}
+			}
+		}
+	case foreignKeyViolation:
+		switch err.SchemaName {
+		case "waterway":
+			switch err.TableName {
+			case "gauge_measurements", "gauge_predictions", "bottlenecks":
+				switch err.ConstraintName {
+				case "gauge_key":
+					return "Referenced gauge with matching temporal validity not available"
+				}
+			}
+		}
 	case noDataFound:
 		// Most recent line from stacktrace contains name of failed function
 		recent := strings.SplitN(err.Where, "\n", 1)[0]
@@ -44,6 +72,8 @@
 			return "No distance mark found for at least one given ISRS Location Code"
 		case strings.Contains(recent, "isrsrange_axis"):
 			return "No contiguous axis found between given ISRS Location Codes"
+		case strings.Contains(recent, "isrsrange_area"):
+			return "No area around axis between given ISRS Location Codes"
 		}
 	}
 	return "Unexpected database error: " + err.Message
--- a/pkg/imports/fa.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/fa.go	Mon Jun 03 10:19:18 2019 +0200
@@ -161,14 +161,11 @@
 
 func (faJobCreator) Create() Job { return new(FairwayAvailability) }
 
-func (faJobCreator) Depends() []string {
-	return []string{
-		"bottlenecks",
-		"fairway_availability",
-		"bottleneck_pdfs",
-		"effective_fairway_availability",
-		"fa_reference_values",
-		"levels_of_service",
+func (faJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"effective_fairway_availability", "fa_reference_values",
+			"bottleneck_pdfs", "fairway_availability"},
+		{"bottlenecks", "levels_of_service"},
 	}
 }
 
--- a/pkg/imports/fd.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/fd.go	Mon Jun 03 10:19:18 2019 +0200
@@ -80,9 +80,10 @@
 
 func (fdJobCreator) Create() Job { return new(FairwayDimension) }
 
-func (fdJobCreator) Depends() []string {
-	return []string{
-		"fairway_dimensions",
+func (fdJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"fairway_dimensions"},
+		{"level_of_service"},
 	}
 }
 
@@ -139,15 +140,12 @@
 
 	deleteFairwayDimensionSQL = `
 WITH resp AS (
-  SELECT best_utm(area::geometry) AS t,
-         ST_Transform(area::geometry, best_utm(area::geometry)) AS a
-  FROM users.responsibility_areas
-  WHERE country = users.current_user_country()
+  SELECT users.current_user_area_utm() AS a
 )
 DELETE FROM waterway.fairway_dimensions
-WHERE ST_Covers(
-  (SELECT a FROM resp),
-  ST_Transform(area::geometry, (SELECT t FROM resp)))
+WHERE (pg_has_role('sys_admin', 'MEMBER')
+    OR ST_Covers((SELECT a FROM resp),
+      ST_Transform(area::geometry, (SELECT ST_SRID(a) FROM resp))))
   AND staging_done
   AND level_of_service = $1::smallint`
 
@@ -155,24 +153,27 @@
 	// avoid errors due to reprojection.
 	insertFairwayDimensionSQL = `
 WITH resp AS (
-  SELECT best_utm(area::geometry) AS t,
-         ST_Transform(area::geometry, best_utm(area::geometry)) AS a
-  FROM users.responsibility_areas
-  WHERE country = users.current_user_country()
+  SELECT users.current_user_area_utm() AS a
 )
-INSERT INTO waterway.fairway_dimensions (area, level_of_service, min_width, max_width, min_depth, date_info, source_organization)
-SELECT ST_Transform(clipped.geom, 4326)::geography, $3, $4, $5, $6, $7, $8 FROM (
-    SELECT (ST_Dump(
-       ST_Intersection(
-         (SELECT ST_Buffer(a, -0.0001) FROM resp),
-         ST_MakeValid(ST_Transform(
-           ST_GeomFromWKB($1, $2::integer),
-           (SELECT t FROM resp)
-         ))
-       )
-     )).geom AS geom
-  ) AS clipped
-  WHERE clipped.geom IS NOT NULL
+INSERT INTO waterway.fairway_dimensions (
+  area,
+  level_of_service,
+  min_width,
+  max_width,
+  min_depth,
+  date_info,
+  source_organization)
+SELECT ST_Transform(dmp.geom, 4326), $3, $4, $5, $6, $7, $8
+  FROM ST_GeomFromWKB($1, $2::integer) AS new_fd (new_fd),
+    ST_Dump(
+      CASE WHEN pg_has_role('sys_admin', 'MEMBER')
+        THEN ST_MakeValid(ST_Transform(
+          new_fd, best_utm(ST_Transform(new_fd, 4326))))
+        ELSE ST_CollectionExtract(ST_Intersection(
+            (SELECT ST_Buffer(a, -0.0001) FROM resp),
+            ST_MakeValid(ST_Transform(new_fd, (SELECT ST_SRID(a) FROM resp)))),
+          3)
+        END) AS dmp
 RETURNING id,
   ST_X(ST_Centroid(area::geometry)),
   ST_Y(ST_Centroid(area::geometry))
@@ -259,6 +260,8 @@
 
 		feedback.Info("Using EPSG: %d", epsg)
 
+		savepoint := Savepoint(ctx, tx, "feature")
+
 	features:
 		for _, feature := range rfc.Features {
 			if feature.Geometry.Coordinates == nil {
@@ -287,24 +290,28 @@
 				}
 				var fdid int64
 				var lat, lon float64
-				err = insertStmt.QueryRowContext(
-					ctx,
-					p.asWKB(),
-					epsg,
-					fd.LOS,
-					fd.MinWidth,
-					fd.MaxWidth,
-					fd.Depth,
-					dateInfo,
-					fd.SourceOrganization,
-				).Scan(&fdid, &lat, &lon)
+				err = savepoint(func() error {
+					err := insertStmt.QueryRowContext(
+						ctx,
+						p.asWKB(),
+						epsg,
+						fd.LOS,
+						fd.MinWidth,
+						fd.MaxWidth,
+						fd.Depth,
+						dateInfo,
+						fd.SourceOrganization,
+					).Scan(&fdid, &lat, &lon)
+					return err
+				})
 				switch {
 				case err == sql.ErrNoRows:
 					outside++
 					// ignore -> filtered by responsibility_areas
 					continue features
 				case err != nil:
-					return err
+					feedback.Warn(handleError(err).Error())
+					continue features
 				}
 				// Store for potential later removal.
 				if err = track(ctx, tx, importID, "waterway.fairway_dimensions", fdid); err != nil {
--- a/pkg/imports/gm.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/gm.go	Mon Jun 03 10:19:18 2019 +0200
@@ -4,13 +4,14 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
 // License-Filename: LICENSES/AGPL-3.0.txt
 //
-// Copyright (C) 2018 by via donau
+// Copyright (C) 2018, 2019 by via donau
 //   – Österreichische Wasserstraßen-Gesellschaft mbH
 // Software engineering by Intevation GmbH
 //
 // Author(s):
 //  * Raimund Renkert <raimund.renkert@intevation.de>
 //  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+//  * Tom Gottfried <tom.gottfried@intevation.de>
 
 package imports
 
@@ -24,6 +25,7 @@
 
 	"gemma.intevation.de/gemma/pkg/models"
 	"gemma.intevation.de/gemma/pkg/soap/nts"
+	"github.com/jackc/pgx/pgtype"
 )
 
 // GaugeMeasurement is an import job to import
@@ -48,14 +50,17 @@
   (location).orc,
   (location).hectometre
 FROM waterway.gauges
-WHERE (location).country_code = users.current_user_country()`
+WHERE (location).country_code = users.current_user_country()
+  OR pg_has_role('sys_admin', 'MEMBER')
+`
 
-	// TODO: Currently this statement updates existing data sets. In case we want
-	// 'historization' we need to develop an other mechanism to keep existing
-	// data.
+	// Note: we do not expect corrections of data through this service.  So
+	// any constraint conflicts are triggered by redundant data which
+	// can be dropped.
 	insertGMSQL = `
 INSERT INTO waterway.gauge_measurements (
-  fk_gauge_id,
+  location,
+  validity,
   measure_date,
   sender,
   language_code,
@@ -63,15 +68,17 @@
   date_issue,
   reference_code,
   water_level,
-  predicted,
-  is_waterlevel,
-  value_min,
-  value_max,
   date_info,
   source_organization,
   staging_done
-) VALUES(
+) VALUES (
   ($1, $2, $3, $4, $5),
+  COALESCE(
+    (SELECT validity FROM waterway.gauges
+       WHERE location
+            = ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int)
+         AND validity @> CAST($6 AS timestamp with time zone)),
+    tstzrange(NULL, NULL)),
   $6,
   $7,
   $8,
@@ -81,27 +88,47 @@
   $12,
   $13,
   $14,
-  $15,
-  $16,
-  $17,
-  $18,
-  $19
+  true
 )
-ON CONFLICT ON CONSTRAINT gauge_measurements_fk_gauge_id_measure_date_staging_done_key
-DO UPDATE SET
-country_code = EXCLUDED.country_code,
-sender = EXCLUDED.sender,
-language_code = EXCLUDED.language_code,
-date_issue = EXCLUDED.date_issue,
-reference_code= EXCLUDED.reference_code,
-water_level = EXCLUDED.water_level,
-predicted = EXCLUDED.predicted,
-is_waterlevel = EXCLUDED.is_waterlevel,
-value_min = EXCLUDED.value_min,
-value_max = EXCLUDED.value_max,
-date_info = EXCLUDED.date_info,
-source_organization = EXCLUDED.source_organization
-RETURNING id
+ON CONFLICT DO NOTHING
+RETURNING 1
+`
+
+	insertGPSQL = `
+INSERT INTO waterway.gauge_predictions (
+  location,
+  validity,
+  measure_date,
+  sender,
+  language_code,
+  country_code,
+  date_issue,
+  reference_code,
+  water_level,
+  conf_interval,
+  date_info,
+  source_organization
+) VALUES(
+  ($1, $2, $3, $4, $5),
+  COALESCE(
+    (SELECT validity FROM waterway.gauges
+       WHERE location
+            = ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int)
+         AND validity @> CAST($6 AS timestamp with time zone)),
+    tstzrange(NULL, NULL)),
+  $6,
+  $7,
+  $8,
+  $9,
+  $10,
+  $11,
+  $12,
+  $13,
+  $14,
+  $15
+)
+ON CONFLICT DO NOTHING
+RETURNING 1
 `
 )
 
@@ -115,10 +142,10 @@
 
 func (gmJobCreator) Create() Job { return new(GaugeMeasurement) }
 
-func (gmJobCreator) Depends() []string {
-	return []string{
-		"gauges",
-		"gauge_measurements",
+func (gmJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"gauge_measurements"},
+		{"gauges"},
 	}
 }
 
@@ -177,9 +204,9 @@
 	)
 }
 
-func loadGauges(ctx context.Context, tx *sql.Tx) ([]string, error) {
+func loadGauges(ctx context.Context, conn *sql.Conn) ([]string, error) {
 
-	rows, err := tx.QueryContext(ctx, listGaugesSQL)
+	rows, err := conn.QueryContext(ctx, listGaugesSQL)
 	if err != nil {
 		return nil, err
 	}
@@ -205,6 +232,11 @@
 		return nil, err
 	}
 
+	if len(gauges) == 0 {
+		return nil, UnchangedError(
+			"No gauges for which measurements can be imported in database")
+	}
+
 	sort.Strings(gauges)
 
 	return gauges, nil
@@ -220,37 +252,24 @@
 
 	start := time.Now()
 
-	tx, err := conn.BeginTx(ctx, nil)
-	if err != nil {
-		return nil, err
-	}
-	defer tx.Rollback()
-
-	// Get available gauges from database for use as filter in SOAP request
-	gauges, err := loadGauges(ctx, tx)
+	// Get gauges from database, for which user is allowed to import data
+	gauges, err := loadGauges(ctx, conn)
 	if err != nil {
 		return nil, err
 	}
 
-	// TODO get date_issue for selected gauges
-	gids, err := doForGM(ctx, gauges, fetch, tx, feedback)
+	gids, err := doForGM(ctx, gauges, fetch, conn, feedback)
 	if err != nil {
-		feedback.Error("Error processing %d gauges: %v", len(gauges), err)
+		feedback.Error("Error processing gauges: %v", err)
 		return nil, err
 	}
 
 	if len(gids) == 0 {
-		feedback.Info("No new gauge measurements found")
 		return nil, UnchangedError("No new gauge measurements found")
 	}
 
-	if err = tx.Commit(); err != nil {
-		feedback.Info(
-			"Importing gauge measurements failed after %s", time.Since(start))
-		return nil, err
-	}
 	feedback.Info(
-		"Importing gauge measurements successfully took %s", time.Since(start))
+		"Importing gauge measurements took %s", time.Since(start))
 
 	// TODO: needs to be filled more useful.
 	summary := struct {
@@ -262,7 +281,7 @@
 }
 
 // rescale returns a scaling function to bring the unit all to cm.
-func rescale(unit string) (func(float32) float32, error) {
+func rescale(unit string) (func(*float32), error) {
 
 	var scale float32
 
@@ -283,7 +302,11 @@
 		return nil, fmt.Errorf("unknown unit '%s'", unit)
 	}
 
-	fn := func(x float32) float32 { return scale * x }
+	fn := func(x *float32) {
+		if x != nil {
+			*x *= scale
+		}
+	}
 	return fn, nil
 }
 
@@ -291,17 +314,23 @@
 	ctx context.Context,
 	gauges []string,
 	fetch func() ([]*nts.RIS_Message_Type, error),
-	tx *sql.Tx,
+	conn *sql.Conn,
 	feedback Feedback,
 ) ([]string, error) {
 
-	insertStmt, err := tx.PrepareContext(ctx, insertGMSQL)
+	insertGPStmt, err := conn.PrepareContext(ctx, insertGPSQL)
 	if err != nil {
 		return nil, err
 	}
-	defer insertStmt.Close()
+	defer insertGPStmt.Close()
 
-	// lookup to see if we have gauges in the database.
+	insertGMStmt, err := conn.PrepareContext(ctx, insertGMSQL)
+	if err != nil {
+		return nil, err
+	}
+	defer insertGMStmt.Close()
+
+	// lookup to see if data can be imported for gauge
 	isKnown := func(s string) bool {
 		idx := sort.SearchStrings(gauges, s)
 		return idx < len(gauges) && gauges[idx] == s
@@ -314,7 +343,7 @@
 
 	var gids []string
 	for _, msg := range result {
-		var gid int64
+		var dummy int
 		for _, wrm := range msg.Wrm {
 			curr := string(*wrm.Geo_object.Id)
 			currIsrs, err := models.IsrsFromString(curr)
@@ -322,9 +351,9 @@
 				feedback.Warn("Invalid ISRS code %v", err)
 				continue
 			}
-			feedback.Info("Found measurements for %s", curr)
+			feedback.Info("Found measurements/predictions for %s", curr)
 			if !isKnown(curr) {
-				feedback.Warn("Gauge '%s' is not in database.", curr)
+				feedback.Warn("Cannot import data for %s", curr)
 				continue
 			}
 
@@ -335,6 +364,8 @@
 			} else {
 				referenceCode = string(*wrm.Reference_code)
 			}
+
+			newM, newP := 0, 0
 			for _, measure := range wrm.Measure {
 				var unit string
 				if measure.Unit == nil {
@@ -347,35 +378,93 @@
 				if err != nil {
 					return nil, err
 				}
-				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.Country_code,
-					msg.Identification.Date_issue,
-					referenceCode,
-					convert(measure.Value),
-					measure.Predicted,
-					isWaterlevel,
-					convert(measure.Value_min),
-					convert(measure.Value_max),
-					msg.Identification.Date_issue,
-					msg.Identification.Originator,
-					true, // staging_done
-				).Scan(&gid)
-				if err != nil {
-					return nil, err
+				convert(measure.Value)
+				convert(measure.Value_min)
+				convert(measure.Value_max)
+
+				if *measure.Measure_code != nts.Measure_code_enumWAL {
+					feedback.Warn("Ignored message with measure_code %s",
+						*measure.Measure_code)
+					continue
+				}
+
+				if measure.Predicted {
+					var confInterval pgtype.Numrange
+					if measure.Value_min != nil && measure.Value_max != nil {
+						var valueMin, valueMax pgtype.Numeric
+						valueMin.Set(measure.Value_min)
+						valueMax.Set(measure.Value_max)
+						confInterval = pgtype.Numrange{
+							Lower:     valueMin,
+							Upper:     valueMax,
+							LowerType: pgtype.Inclusive,
+							UpperType: pgtype.Inclusive,
+							Status:    pgtype.Present,
+						}
+					}
+					err = insertGPStmt.QueryRowContext(
+						ctx,
+						currIsrs.CountryCode,
+						currIsrs.LoCode,
+						currIsrs.FairwaySection,
+						currIsrs.Orc,
+						currIsrs.Hectometre,
+						measure.Measuredate,
+						msg.Identification.From,
+						msg.Identification.Language_code,
+						msg.Identification.Country_code,
+						msg.Identification.Date_issue,
+						referenceCode,
+						measure.Value,
+						&confInterval,
+						msg.Identification.Date_issue,
+						msg.Identification.Originator,
+					).Scan(&dummy)
+					switch {
+					case err == sql.ErrNoRows:
+						// thats expected, nothing to do
+					case err != nil:
+						feedback.Warn(handleError(err).Error())
+					default:
+						newP++
+					}
+				} else {
+					if measure.Value == nil {
+						feedback.Info("Missing value at %s. Ignored",
+							measure.Measuredate.Format(time.RFC3339))
+						continue
+					}
+					err = insertGMStmt.QueryRowContext(
+						ctx,
+						currIsrs.CountryCode,
+						currIsrs.LoCode,
+						currIsrs.FairwaySection,
+						currIsrs.Orc,
+						currIsrs.Hectometre,
+						measure.Measuredate,
+						msg.Identification.From,
+						msg.Identification.Language_code,
+						msg.Identification.Country_code,
+						msg.Identification.Date_issue,
+						referenceCode,
+						measure.Value,
+						msg.Identification.Date_issue,
+						msg.Identification.Originator,
+					).Scan(&dummy)
+					switch {
+					case err == sql.ErrNoRows:
+						// thats expected, nothing to do
+					case err != nil:
+						feedback.Warn(handleError(err).Error())
+					default:
+						newM++
+					}
 				}
 			}
 			feedback.Info("Inserted %d measurements for %s",
-				len(wrm.Measure), curr)
+				newM, curr)
+			feedback.Info("Inserted %d predictions for %s",
+				newP, curr)
 			gids = append(gids, curr)
 		}
 	}
--- a/pkg/imports/modelconvert.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/modelconvert.go	Mon Jun 03 10:19:18 2019 +0200
@@ -28,6 +28,7 @@
 	FDJobKind:  func() interface{} { return new(models.FairwayDimensionImport) },
 	DMAJobKind: func() interface{} { return new(models.DistanceMarksAshoreImport) },
 	STJobKind:  func() interface{} { return new(models.StretchImport) },
+	SECJobKind: func() interface{} { return new(models.SectionImport) },
 }
 
 func ImportModelForJobKind(kind JobKind) func() interface{} {
@@ -144,6 +145,20 @@
 			Countries: sti.Countries,
 		}
 	},
+
+	SECJobKind: func(input interface{}) interface{} {
+		seci := input.(*models.SectionImport)
+		return &Section{
+			Name:      seci.Name,
+			From:      seci.From,
+			To:        seci.To,
+			Tolerance: seci.Tolerance,
+			ObjNam:    seci.ObjNam,
+			NObjNam:   seci.NObjNam,
+			Source:    seci.Source,
+			Date:      seci.Date,
+		}
+	},
 }
 
 func nilString(s *string) string {
--- a/pkg/imports/queue.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/queue.go	Mon Jun 03 10:19:18 2019 +0200
@@ -20,6 +20,7 @@
 	"fmt"
 	"log"
 	"runtime/debug"
+	"sort"
 	"strings"
 	"sync"
 	"time"
@@ -75,10 +76,12 @@
 		Description() string
 		// Create build the actual job.
 		Create() Job
-		// Depends returns a list of ressources locked by this type of import.
+		// Depends returns two lists of ressources locked by this type of import.
 		// Imports are run concurrently if they have disjoint sets
 		// of dependencies.
-		Depends() []string
+		// The first list are locked exclusively.
+		// The second allows multiple read users but only one writing one.
+		Depends() [2][]string
 		// StageDone is called if an import is positively reviewed
 		// (state = accepted). This can be used to finalize the imported
 		// data to move it e.g from the staging area.
@@ -99,19 +102,22 @@
 	}
 )
 
-const pollDuration = time.Second * 10
+const (
+	pollDuration = time.Second * 10
+	runExclusive = -66666
+)
 
 type importQueue struct {
 	signalChan chan struct{}
 	creatorsMu sync.Mutex
 	creators   map[JobKind]JobCreator
-	usedDeps   map[string]struct{}
+	usedDeps   map[string]int
 }
 
 var iqueue = importQueue{
 	signalChan: make(chan struct{}),
 	creators:   map[JobKind]JobCreator{},
-	usedDeps:   map[string]struct{}{},
+	usedDeps:   map[string]int{},
 }
 
 var (
@@ -220,6 +226,15 @@
 	return iqueue.importKindNames()
 }
 
+// LogImportKindNames logs a list of importer types registered
+// to the global import queue.
+func LogImportKindNames() {
+	kinds := ImportKindNames()
+	sort.Strings(kinds)
+	log.Printf("info: registered import kinds: %s",
+		strings.Join(kinds, ", "))
+}
+
 // HasImportKindName checks if the import queue supports a given kind.
 func HasImportKindName(kind string) bool {
 	return iqueue.hasImportKindName(kind)
@@ -236,7 +251,6 @@
 // This a good candidate to be called in a init function for
 // a particular JobCreator.
 func RegisterJobCreator(kind JobKind, jc JobCreator) {
-	log.Printf("info: register import job creator for kind '%s'\n", kind)
 	iqueue.registerJobCreator(kind, jc)
 }
 
@@ -304,6 +318,30 @@
 	return d
 }
 
+func (q *importQueue) lockDependencies(jc JobCreator) {
+	deps := jc.Depends()
+	q.creatorsMu.Lock()
+	defer q.creatorsMu.Unlock()
+	for _, d := range deps[0] {
+		q.usedDeps[d] = runExclusive
+	}
+	for _, d := range deps[1] {
+		q.usedDeps[d]++
+	}
+}
+
+func (q *importQueue) unlockDependencies(jc JobCreator) {
+	deps := jc.Depends()
+	q.creatorsMu.Lock()
+	defer q.creatorsMu.Unlock()
+	for _, d := range deps[0] {
+		q.usedDeps[d] = 0
+	}
+	for _, d := range deps[1] {
+		q.usedDeps[d]--
+	}
+}
+
 func (q *importQueue) jobCreator(kind JobKind) JobCreator {
 	q.creatorsMu.Lock()
 	defer q.creatorsMu.Unlock()
@@ -436,8 +474,14 @@
 	q.creatorsMu.Lock()
 nextCreator:
 	for kind, jc := range q.creators {
-		for _, d := range jc.Depends() {
-			if _, found := q.usedDeps[d]; found {
+		deps := jc.Depends()
+		for _, d := range deps[0] {
+			if q.usedDeps[d] != 0 {
+				continue nextCreator
+			}
+		}
+		for _, d := range deps[1] {
+			if q.usedDeps[d] == runExclusive {
 				continue nextCreator
 			}
 		}
@@ -488,6 +532,30 @@
 	return &ji, nil
 }
 
+func tryHardToStoreState(ctx context.Context, fn func(*sql.Conn) error) error {
+	// As it is important to keep the persistent model
+	// in sync with the in-memory model try harder to store
+	// the state.
+	const maxTries = 10
+	var sleep = time.Second
+
+	for try := 1; ; try++ {
+		var err error
+		if err = auth.RunAs(ctx, queueUser, fn); err == nil || try == maxTries {
+			return err
+		}
+		log.Printf("warn: [try %d/%d] Storing state failed: %v (try again in %s).\n",
+			try, maxTries, err, sleep)
+
+		time.Sleep(sleep)
+		if sleep < time.Minute {
+			if sleep *= 2; sleep > time.Minute {
+				sleep = time.Minute
+			}
+		}
+	}
+}
+
 func updateStateSummary(
 	ctx context.Context,
 	id int64,
@@ -502,7 +570,8 @@
 		}
 		s = sql.NullString{String: b.String(), Valid: true}
 	}
-	return auth.RunAs(ctx, queueUser, func(conn *sql.Conn) error {
+
+	return tryHardToStoreState(ctx, func(conn *sql.Conn) error {
 		_, err := conn.ExecContext(ctx, updateStateSummarySQL, state, s, id)
 		return err
 	})
@@ -510,7 +579,7 @@
 
 func errorAndFail(id int64, format string, args ...interface{}) error {
 	ctx := context.Background()
-	err := auth.RunAs(ctx, queueUser, func(conn *sql.Conn) error {
+	return tryHardToStoreState(ctx, func(conn *sql.Conn) error {
 		tx, err := conn.BeginTx(ctx, nil)
 		if err != nil {
 			return err
@@ -528,7 +597,6 @@
 		}
 		return err
 	})
-	return err
 }
 
 func (q *importQueue) importLoop() {
@@ -565,21 +633,13 @@
 		}
 
 		// Lock dependencies.
-		q.creatorsMu.Lock()
-		for _, d := range jc.Depends() {
-			q.usedDeps[d] = struct{}{}
-		}
-		q.creatorsMu.Unlock()
+		q.lockDependencies(jc)
 
 		go func(jc JobCreator, idj *idJob) {
 
 			// Unlock the dependencies.
 			defer func() {
-				q.creatorsMu.Lock()
-				for _, d := range jc.Depends() {
-					delete(q.usedDeps, d)
-				}
-				q.creatorsMu.Unlock()
+				q.unlockDependencies(jc)
 				select {
 				case q.signalChan <- struct{}{}:
 				default:
@@ -619,7 +679,7 @@
 			}
 
 			var errCleanup error
-			if retry { // cleanup debris
+			if !retry { // cleanup debris
 				if errCleanup = survive(job.CleanUp)(); errCleanup != nil {
 					feedback.Error("error cleanup: %v", errCleanup)
 				}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/imports/sec.go	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,200 @@
+// 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,2019 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"
+	"time"
+
+	"gemma.intevation.de/gemma/pkg/models"
+)
+
+type Section struct {
+	Name      string      `json:"name"`
+	From      models.Isrs `json:"from"`
+	To        models.Isrs `json:"to"`
+	Tolerance float32     `json:"tolerance"`
+	ObjNam    string      `json:"objnam"`
+	NObjNam   *string     `json:"nobjnam"`
+	Source    string      `json:"source-organization"`
+	Date      models.Date `json:"date-info"`
+}
+
+const SECJobKind JobKind = "sec"
+
+type secJobCreator struct{}
+
+func init() {
+	RegisterJobCreator(SECJobKind, secJobCreator{})
+}
+
+func (secJobCreator) Description() string { return "section" }
+
+func (secJobCreator) AutoAccept() bool { return false }
+
+func (secJobCreator) Create() Job { return new(Section) }
+
+func (secJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"sections"},
+		{"distance_marks_virtual", "waterway_axis", "waterway_area"},
+	}
+}
+
+const (
+	secDeleteSQL = `
+DELETE FROM waterway.sections WHERE
+staging_done AND name = (
+  SELECT name
+  FROM waterway.sections WHERE
+  id = (
+    SELECT key from import.track_imports
+    WHERE import_id = $1 AND
+      relation = 'waterway.sections'::regclass)
+  AND NOT staging_done
+)`
+
+	secStageDoneSQL = `
+UPDATE waterway.sections SET staging_done = true
+WHERE id IN (
+  SELECT key from import.track_imports
+  WHERE import_id = $1 AND
+        relation = 'waterway.sections'::regclass)`
+
+	secInsertSQL = `
+WITH
+bounds (b) AS (VALUES (
+    ($1::char(2),
+     $2::char(3),
+     $3::char(5),
+     $4::char(5),
+     $5::int)::isrs
+  ), (
+    ($6::char(2),
+     $7::char(3),
+     $8::char(5),
+     $9::char(5),
+     $10::int)::isrs)),
+r AS (SELECT isrsrange(
+    (SELECT b FROM bounds ORDER BY b USING <~ FETCH FIRST ROW ONLY),
+    (SELECT b FROM bounds ORDER BY b USING >~ FETCH FIRST ROW ONLY)) AS r),
+axs AS (
+  SELECT ISRSrange_axis((SELECT r FROM r), $16::double precision) AS axs)
+INSERT INTO waterway.sections (
+  name,
+  section,
+  area,
+  objnam,
+  nobjnam,
+  date_info,
+  source_organization
+) VALUES (
+  $11,
+  (SELECT r FROM r),
+  ISRSrange_area(
+    (SELECT axs FROM axs),
+    (SELECT ST_Collect(CAST(area AS geometry))
+       FROM waterway.waterway_area)),
+  $12,
+  $13,
+  $14,
+  $15)
+RETURNING id`
+)
+
+// StageDone moves the imported section out of the staging area.
+func (secJobCreator) StageDone(
+	ctx context.Context,
+	tx *sql.Tx,
+	id int64,
+) error {
+	if _, err := tx.ExecContext(ctx, secDeleteSQL, id); err != nil {
+		return err
+	}
+	_, err := tx.ExecContext(ctx, secStageDoneSQL, id)
+	return err
+}
+
+// CleanUp of a section import is a NOP.
+func (*Section) CleanUp() error { return nil }
+
+// Do executes the actual section import.
+func (sec *Section) Do(
+	ctx context.Context,
+	importID int64,
+	conn *sql.Conn,
+	feedback Feedback,
+) (interface{}, error) {
+
+	start := time.Now()
+
+	if sec.Date.Time.IsZero() {
+		sec.Date = models.Date{Time: start}
+	}
+
+	feedback.Info("Storing section '%s'", sec.Name)
+
+	tx, err := conn.BeginTx(ctx, nil)
+	if err != nil {
+		return nil, err
+	}
+	defer tx.Rollback()
+
+	var nobjnm sql.NullString
+	if sec.NObjNam != nil {
+		nobjnm = sql.NullString{String: *sec.NObjNam, Valid: true}
+	}
+
+	feedback.Info("Section from %s to %s.", sec.From.String(), sec.To.String())
+	feedback.Info("Tolerance used to snap waterway axis: %g", sec.Tolerance)
+
+	var id int64
+	if err := tx.QueryRowContext(
+		ctx,
+		secInsertSQL,
+		sec.From.CountryCode,
+		sec.From.LoCode,
+		sec.From.FairwaySection,
+		sec.From.Orc,
+		sec.From.Hectometre,
+		sec.To.CountryCode,
+		sec.To.LoCode,
+		sec.To.FairwaySection,
+		sec.To.Orc,
+		sec.To.Hectometre,
+		sec.Name,
+		sec.ObjNam,
+		nobjnm,
+		sec.Date.Time,
+		sec.Source,
+		sec.Tolerance,
+	).Scan(&id); err != nil {
+		return nil, handleError(err)
+	}
+
+	if err := track(ctx, tx, importID, "waterway.sections", id); err != nil {
+		return nil, err
+	}
+
+	feedback.Info("Storing section '%s' took %s", sec.Name, time.Since(start))
+	if err := tx.Commit(); err != nil {
+		return nil, err
+	}
+	feedback.Info("Import of section was successful")
+
+	summary := sec // to provide full data for review
+
+	return summary, nil
+}
--- a/pkg/imports/sr.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/sr.go	Mon Jun 03 10:19:18 2019 +0200
@@ -35,6 +35,7 @@
 	shp "github.com/jonas-p/go-shp"
 
 	"gemma.intevation.de/gemma/pkg/common"
+	"gemma.intevation.de/gemma/pkg/misc"
 	"gemma.intevation.de/gemma/pkg/models"
 	"gemma.intevation.de/gemma/pkg/octree"
 )
@@ -79,11 +80,10 @@
 
 func (srJobCreator) Create() Job { return new(SoundingResult) }
 
-func (srJobCreator) Depends() []string {
-	return []string{
-		"sounding_results",
-		"sounding_results_contour_lines",
-		"bottlenecks",
+func (srJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"sounding_results", "sounding_results_contour_lines"},
+		{"bottlenecks"},
 	}
 }
 
@@ -124,8 +124,8 @@
   id,
   ST_X(ST_Centroid(area::geometry)),
   ST_Y(ST_Centroid(area::geometry)),
-  best_utm(area::geometry),
-  ST_AsBinary(ST_Transform(area::geometry, best_utm(area::geometry)))
+  best_utm(area),
+  ST_AsBinary(ST_Transform(area::geometry, best_utm(area)))
 `
 
 	reprojectPointsSQL = `
@@ -161,6 +161,17 @@
 FROM waterway.sounding_results sr
 WHERE id = $1
 `
+
+	selectGaugeLDCSQL = `
+SELECT
+  grwl.value,
+  grwl.depth_reference
+FROM waterway.gauges_reference_water_levels grwl
+  JOIN waterway.bottlenecks bns
+    ON grwl.location = bns.gauge_location
+      AND grwl.validity = bns.gauge_validity
+WHERE bns.objnam = $1 AND grwl.depth_reference like 'LDC%'
+`
 )
 
 // Do executes the actual sounding result import.
@@ -190,6 +201,31 @@
 		return nil, err
 	}
 
+	feedback.Info("Bottleneck: %s", m.Bottleneck)
+	feedback.Info("Survey date: %s", m.Date.Format(common.DateFormat))
+
+	var xform vertexTransform
+
+	if m.DepthReference == "ZPG" {
+		feedback.Info("Found ZPG as reference system -> translating Z values to LDC")
+		var ldc float64
+		var depthReference string
+		err := conn.QueryRowContext(ctx, selectGaugeLDCSQL, m.Bottleneck).Scan(
+			&ldc,
+			&depthReference,
+		)
+		switch {
+		case err == sql.ErrNoRows:
+			return nil, errors.New("Cannot load LDC value")
+		case err != nil:
+			return nil, err
+		}
+		xform = func(v octree.Vertex) octree.Vertex {
+			return octree.Vertex{X: v.X, Y: v.Y, Z: ldc - v.Z}
+		}
+		m.DepthReference = depthReference
+	}
+
 	if err := m.Validate(ctx, conn); err != nil {
 		return nil, common.ToError(err)
 	}
@@ -205,7 +241,7 @@
 		return nil, errors.New("Cannot find any *.xyz or *.txt file")
 	}
 
-	xyz, err := loadXYZ(xyzf, feedback)
+	xyz, err := loadXYZ(xyzf, feedback, xform)
 	if err != nil {
 		return nil, err
 	}
@@ -422,24 +458,30 @@
 	return &m, nil
 }
 
-func loadXYZReader(r io.Reader, feedback Feedback) (octree.MultiPointZ, error) {
+type vertexTransform func(octree.Vertex) octree.Vertex
+
+func loadXYZReader(r io.Reader, feedback Feedback, xform vertexTransform) (octree.MultiPointZ, error) {
 	mpz := make(octree.MultiPointZ, 0, 250000)
 	s := bufio.NewScanner(r)
 
 	var hasNegZ bool
 
+	warnLimiter := misc.WarningLimiter{Log: feedback.Warn, MaxWarnings: 100}
+	warn := warnLimiter.Warn
+	defer warnLimiter.Close()
+
 	for line := 1; s.Scan(); line++ {
 		text := s.Text()
 		var p octree.Vertex
 		// fmt.Sscanf(text, "%f,%f,%f") is 4 times slower.
 		idx := strings.IndexByte(text, ',')
 		if idx == -1 {
-			feedback.Warn("format error in line %d", line)
+			warn("format error in line %d", line)
 			continue
 		}
 		var err error
 		if p.X, err = strconv.ParseFloat(text[:idx], 64); err != nil {
-			feedback.Warn("format error in line %d: %v", line, err)
+			warn("format error in line %d: %v", line, err)
 			continue
 		}
 		text = text[idx+1:]
@@ -448,21 +490,24 @@
 			continue
 		}
 		if p.Y, err = strconv.ParseFloat(text[:idx], 64); err != nil {
-			feedback.Warn("format error in line %d: %v", line, err)
+			warn("format error in line %d: %v", line, err)
 			continue
 		}
 		text = text[idx+1:]
 		if p.Z, err = strconv.ParseFloat(text, 64); err != nil {
-			feedback.Warn("format error in line %d: %v", line, err)
+			warn("format error in line %d: %v", line, err)
 			continue
 		}
 		if p.Z < 0 {
 			p.Z = -p.Z
 			if !hasNegZ {
 				hasNegZ = true
-				feedback.Warn("Negative Z value found: Using -Z")
+				warn("Negative Z value found: Using -Z")
 			}
 		}
+		if xform != nil {
+			p = xform(p)
+		}
 		mpz = append(mpz, p)
 	}
 
@@ -473,13 +518,13 @@
 	return mpz, nil
 }
 
-func loadXYZ(f *zip.File, feedback Feedback) (octree.MultiPointZ, error) {
+func loadXYZ(f *zip.File, feedback Feedback, xform vertexTransform) (octree.MultiPointZ, error) {
 	r, err := f.Open()
 	if err != nil {
 		return nil, err
 	}
 	defer r.Close()
-	return loadXYZReader(r, feedback)
+	return loadXYZReader(r, feedback, xform)
 }
 
 func loadBoundary(z *zip.ReadCloser) (polygonSlice, error) {
--- a/pkg/imports/st.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/st.go	Mon Jun 03 10:19:18 2019 +0200
@@ -48,9 +48,10 @@
 
 func (stJobCreator) Create() Job { return new(Stretch) }
 
-func (stJobCreator) Depends() []string {
-	return []string{
-		"stretches",
+func (stJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"stretches", "stretch_countries"},
+		{"distance_marks_virtual", "waterway_axis", "waterway_area"},
 	}
 }
 
@@ -75,30 +76,24 @@
         relation = 'waterway.stretches'::regclass)`
 
 	stInsertSQL = `
-WITH r AS (
-  SELECT isrsrange(
-    least(($1::char(2),
-           $2::char(3),
-           $3::char(5),
-           $4::char(5),
-           $5::int)::isrs,
-          ($6::char(2),
-           $7::char(3),
-           $8::char(5),
-           $9::char(5),
-           $10::int)::isrs),
-    greatest(($1::char(2),
-              $2::char(3),
-              $3::char(5),
-              $4::char(5),
-              $5::int)::isrs,
-             ($6::char(2),
-              $7::char(3),
-              $8::char(5),
-              $9::char(5),
-              $10::int)::isrs)
-    ) AS r
-)
+WITH
+bounds (b) AS (VALUES (
+    ($1::char(2),
+     $2::char(3),
+     $3::char(5),
+     $4::char(5),
+     $5::int)::isrs
+  ), (
+    ($6::char(2),
+     $7::char(3),
+     $8::char(5),
+     $9::char(5),
+     $10::int)::isrs)),
+r AS (SELECT isrsrange(
+    (SELECT b FROM bounds ORDER BY b USING <~ FETCH FIRST ROW ONLY),
+    (SELECT b FROM bounds ORDER BY b USING >~ FETCH FIRST ROW ONLY)) AS r),
+axs AS (
+  SELECT ISRSrange_axis((SELECT r FROM r), $16::double precision) AS axs)
 INSERT INTO waterway.stretches (
   name,
   stretch,
@@ -110,10 +105,10 @@
 ) VALUES (
   $11,
   (SELECT r FROM r),
-  ISRSrange_area(
-    ISRSrange_axis((SELECT r FROM r), $16::double precision),
-    (SELECT ST_Collect(CAST(area AS geometry))
-      FROM waterway.waterway_area)),
+  ST_Transform(ISRSrange_area(
+      (SELECT axs FROM axs),
+      (SELECT ST_Buffer(axs, 10000) FROM axs)),
+    4326),
   $12,
   $13,
   $14,
@@ -176,6 +171,7 @@
 	if err != nil {
 		return nil, err
 	}
+	defer insertCountryStmt.Close()
 
 	var nobjnm sql.NullString
 	if st.NObjNam != nil {
@@ -228,11 +224,7 @@
 	}
 	feedback.Info("Import of stretch was successful")
 
-	summary := struct {
-		Stretch string `json:"stretch"`
-	}{
-		Stretch: st.Name,
-	}
+	summary := st // provide full information for summary
 
-	return &summary, nil
+	return summary, nil
 }
--- a/pkg/imports/ubn.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/ubn.go	Mon Jun 03 10:19:18 2019 +0200
@@ -43,7 +43,7 @@
 
 func (ubnJobCreator) Create() Job { return new(UploadedBottleneck) }
 
-func (ubnJobCreator) Depends() []string {
+func (ubnJobCreator) Depends() [2][]string {
 	// Same as normal bottleneck import.
 	return bnJobCreator{}.Depends()
 }
--- a/pkg/imports/ufa.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/ufa.go	Mon Jun 03 10:19:18 2019 +0200
@@ -42,7 +42,7 @@
 
 func (ufaJobCreator) Create() Job { return new(UploadedFairwayAvailability) }
 
-func (ufaJobCreator) Depends() []string {
+func (ufaJobCreator) Depends() [2][]string {
 	// Same as faJobCreator
 	return faJobCreator{}.Depends()
 }
--- a/pkg/imports/ugm.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/ugm.go	Mon Jun 03 10:19:18 2019 +0200
@@ -38,7 +38,7 @@
 
 func (ugmJobCreator) Create() Job { return new(UploadedGaugeMeasurement) }
 
-func (ugmJobCreator) Depends() []string { return gmJobCreator{}.Depends() }
+func (ugmJobCreator) Depends() [2][]string { return gmJobCreator{}.Depends() }
 
 func (ugmJobCreator) AutoAccept() bool { return true }
 
--- a/pkg/imports/wa.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/wa.go	Mon Jun 03 10:19:18 2019 +0200
@@ -59,9 +59,10 @@
 
 func (waJobCreator) Create() Job { return new(WaterwayArea) }
 
-func (waJobCreator) Depends() []string {
-	return []string{
-		"waterway_area",
+func (waJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"waterway_area"},
+		{},
 	}
 }
 
@@ -81,35 +82,29 @@
 const (
 	deleteWaterwayAreaSQL = `
 WITH resp AS (
-  SELECT best_utm(area::geometry) AS t,
-         ST_Transform(area::geometry, best_utm(area::geometry)) AS a
-  FROM users.responsibility_areas
-  WHERE country = users.current_user_country()
+  SELECT users.current_user_area_utm() AS a
 )
 DELETE FROM waterway.waterway_area
 WHERE pg_has_role('sys_admin', 'MEMBER')
   OR ST_Covers((SELECT a FROM resp),
-    ST_Transform(area::geometry, (SELECT t FROM resp)))
+    ST_Transform(area::geometry, (SELECT ST_SRID(a) FROM resp)))
 `
 	insertWaterwayAreaSQL = `
 WITH resp AS (
-  SELECT best_utm(area::geometry) AS t,
-         ST_Transform(area::geometry, best_utm(area::geometry)) AS a
-  FROM users.responsibility_areas
-  WHERE country = users.current_user_country()
+  SELECT users.current_user_area_utm() AS a
 )
 INSERT INTO waterway.waterway_area (area, catccl, dirimp)
-SELECT ST_Transform(clipped.geom, 4326)::geography, $3, $4
-  FROM resp,
-    ST_CollectionExtract(ST_MakeValid(ST_Transform(
-      ST_GeomFromWKB($1, $2::integer), t)), 3) AS new_area (new_area),
-    LATERAL (SELECT (ST_Dump(
+SELECT dmp.geom, $3, $4
+  FROM ST_GeomFromWKB($1, $2::integer) AS new_area (new_area),
+    ST_Dump(ST_Transform(ST_CollectionExtract(
       CASE WHEN pg_has_role('sys_admin', 'MEMBER')
-        THEN new_area
-        ELSE ST_Intersection(a, new_area)
-        END
-      )).geom AS geom
-    ) AS clipped
+        THEN ST_MakeValid(ST_Transform(new_area,
+          best_utm(ST_Transform(new_area, 4326))))
+        ELSE ST_Intersection((SELECT a FROM resp),
+          ST_MakeValid(ST_Transform(new_area, (SELECT ST_SRID(a) FROM resp))))
+        END,
+      3), 4326)) AS dmp
+RETURNING id
 `
 )
 
@@ -176,6 +171,7 @@
 		unsupported       = stringCounter{}
 		missingProperties int
 		badProperties     int
+		outside           int
 		features          int
 	)
 
@@ -234,18 +230,24 @@
 				if err := json.Unmarshal(*feature.Geometry.Coordinates, &p); err != nil {
 					return err
 				}
-				if err := savepoint(func() error {
-					_, err := insertStmt.ExecContext(
+				var waid int64
+				err := savepoint(func() error {
+					err := insertStmt.QueryRowContext(
 						ctx,
 						p.asWKB(),
 						epsg,
 						catccl,
 						dirimp,
-					)
+					).Scan(&waid)
 					return err
-				}); err != nil {
+				})
+				switch {
+				case err == sql.ErrNoRows:
+					outside++
+					// ignore -> filtered by responsibility_areas
+				case err != nil:
 					feedback.Warn(handleError(err).Error())
-				} else {
+				default:
 					features++
 				}
 			default:
@@ -269,6 +271,10 @@
 		feedback.Warn("Unsupported types found: %s", unsupported)
 	}
 
+	if outside > 0 {
+		feedback.Info("Features outside responsibility area: %d", outside)
+	}
+
 	if features == 0 {
 		err := errors.New("No features found")
 		feedback.Error("%v", err)
--- a/pkg/imports/wg.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/wg.go	Mon Jun 03 10:19:18 2019 +0200
@@ -17,7 +17,6 @@
 import (
 	"context"
 	"database/sql"
-	"errors"
 	"time"
 
 	"github.com/jackc/pgx/pgtype"
@@ -52,10 +51,10 @@
 
 func (wgJobCreator) Create() Job { return new(WaterwayGauge) }
 
-func (wgJobCreator) Depends() []string {
-	return []string{
-		"gauges",
-		"gauges_reference_water_levels",
+func (wgJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"gauges_reference_water_levels", "gauges"},
+		{"depth_references"},
 	}
 }
 
@@ -66,14 +65,28 @@
 func (*WaterwayGauge) CleanUp() error { return nil }
 
 const (
-	hasGaugeSQL = `
-SELECT true
-FROM waterway.gauges
-WHERE location = ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int)`
+	eraseObsoleteGaugesSQL = `
+UPDATE waterway.gauges SET erased = true
+WHERE NOT erased
+  AND (location).country_code = ANY($1)
+  AND isrs_astext(location) <> ALL($2)
+RETURNING isrs_astext(location)
+`
 
-	deleteReferenceWaterLevelsSQL = `
-DELETE FROM waterway.gauges_reference_water_levels
-WHERE gauge_id = ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int)`
+	eraseGaugeSQL = `
+WITH upd AS (
+  UPDATE waterway.gauges SET
+    erased = true
+  WHERE isrs_astext(location) = $1
+    AND NOT erased
+    -- Don't touch old entry if validity did not change: will be updated
+    AND validity <> $2
+  RETURNING 1
+)
+-- Decide whether a new version will be INSERTed
+SELECT EXISTS(SELECT 1 FROM upd)
+  OR NOT EXISTS(SELECT 1 FROM waterway.gauges WHERE isrs_astext(location) = $1)
+`
 
 	insertGaugeSQL = `
 INSERT INTO waterway.gauges (
@@ -86,7 +99,8 @@
   zero_point,
   geodref,
   date_info,
-  source_organization
+  source_organization,
+  lastupdate
 ) VALUES (
   ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int),
   $6,
@@ -97,31 +111,69 @@
   $12,
   $13,
   $14,
-  $15
-) ON CONFLICT (location) DO UPDATE SET
+  $15,
+  $16
+)
+`
+
+	moveGMSQL = `
+UPDATE waterway.gauge_measurements
+-- Associate measurements to matching gauge version
+SET validity = $2
+WHERE isrs_astext(location) = $1
+  AND measure_date <@ CAST($2 AS tstzrange)
+`
+
+	fixValiditySQL = `
+UPDATE waterway.gauges SET
+   -- Set enddate of old entry to new startdate in case of overlap:
+  validity = validity - $2
+WHERE isrs_astext(location) = $1
+  AND validity && $2
+  AND erased
+`
+
+	updateGaugeSQL = `
+UPDATE waterway.gauges SET
   objname = $6,
-  geom = ST_SetSRID(ST_MakePoint($7, $8), 4326)::geography,
+  geom = ST_SetSRID(ST_MakePoint($7, $8), 4326),
   applicability_from_km = $9,
   applicability_to_km = $10,
-  validity = $11,
-  zero_point = $12,
-  geodref = $13,
-  date_info = $14,
-  source_organization = $15
+  zero_point = $11,
+  geodref = $12,
+  date_info = $13,
+  source_organization = $14,
+  lastupdate = $15
+WHERE location = ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int)
+  AND NOT erased
+  AND $15 > lastupdate
+RETURNING 1
 `
+
+	deleteReferenceWaterLevelsSQL = `
+DELETE FROM waterway.gauges_reference_water_levels
+WHERE isrs_astext(location) = $1
+  AND validity = $2
+  AND depth_reference <> ALL($3)
+RETURNING depth_reference
+`
+
 	isNtSDepthRefSQL = `
 SELECT EXISTS(SELECT 1 FROM depth_references WHERE depth_reference = $1)`
 
 	insertReferenceWaterLevelsSQL = `
 INSERT INTO waterway.gauges_reference_water_levels (
-  gauge_id,
+  location,
+  validity,
   depth_reference,
   value
 ) VALUES (
   ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int),
   $6,
-  $7
-)
+  $7,
+  $8
+) ON CONFLICT (location, validity, depth_reference) DO UPDATE SET
+    value = EXCLUDED.value
 `
 )
 
@@ -134,15 +186,9 @@
 
 	start := time.Now()
 
-	tx, err := conn.BeginTx(ctx, nil)
-	if err != nil {
-		return nil, err
-	}
-	defer tx.Rollback()
-
-	responseData, err := getRisData(
-		tx,
+	responseData, countries, err := getRisData(
 		ctx,
+		conn,
 		feedback,
 		wg.Username,
 		wg.Password,
@@ -153,273 +199,353 @@
 		return nil, err
 	}
 
-	hasGaugeStmt, err := tx.PrepareContext(ctx, hasGaugeSQL)
-	if err != nil {
-		return nil, err
-	}
-	defer hasGaugeStmt.Close()
-
-	var ignored int
-
-	type idxCode struct {
-		jdx  int
-		idx  int
-		code *models.Isrs
+	var eraseGaugeStmt, insertStmt, moveGMStmt, fixValidityStmt, updateStmt,
+		deleteReferenceWaterLevelsStmt,
+		isNtSDepthRefStmt, insertWaterLevelStmt *sql.Stmt
+	for _, x := range []struct {
+		sql  string
+		stmt **sql.Stmt
+	}{
+		{eraseGaugeSQL, &eraseGaugeStmt},
+		{insertGaugeSQL, &insertStmt},
+		{moveGMSQL, &moveGMStmt},
+		{fixValiditySQL, &fixValidityStmt},
+		{updateGaugeSQL, &updateStmt},
+		{deleteReferenceWaterLevelsSQL, &deleteReferenceWaterLevelsStmt},
+		{isNtSDepthRefSQL, &isNtSDepthRefStmt},
+		{insertReferenceWaterLevelsSQL, &insertWaterLevelStmt},
+	} {
+		var err error
+		if *x.stmt, err = conn.PrepareContext(ctx, x.sql); err != nil {
+			return nil, err
+		}
+		defer (*x.stmt).Close()
 	}
 
-	var news, olds []idxCode
+	var gauges []string
+	var unchanged int
 
-	for j, data := range responseData {
-		for i, dr := range data.RisdataReturn {
-			if dr.RisidxCode == nil {
-				ignored++
+	for _, data := range responseData {
+		for _, dr := range data.RisdataReturn {
+
+			isrs := string(*dr.RisidxCode)
+			code, err := models.IsrsFromString(isrs)
+			if err != nil {
+				feedback.Warn("Invalid ISRS code '%s': %v", isrs, err)
 				continue
 			}
-			code, err := models.IsrsFromString(string(*dr.RisidxCode))
-			if err != nil {
-				feedback.Warn("invalid ISRS code %v", err)
-				ignored++
-				continue
-			}
+			gauges = append(gauges, isrs)
+			feedback.Info("Processing %s", code)
 
-			if dr.Objname.Loc == nil {
-				feedback.Warn("missing objname: %s", code)
-				ignored++
-				continue
-			}
-
-			if dr.Lat == nil || dr.Lon == nil {
-				feedback.Warn("missing lat/lon: %s", code)
-				ignored++
-				continue
-			}
-
-			if dr.Zeropoint == nil {
-				feedback.Warn("missing zeropoint: %s", code)
-				ignored++
+			// We need a valid, non-empty time range to identify gauge versions
+			if dr.Enddate != nil && dr.Startdate != nil &&
+				!time.Time(*dr.Enddate).After(time.Time(*dr.Startdate)) {
+				feedback.Warn("End date not after start date")
+				unchanged++
 				continue
 			}
 
-			var dummy bool
-			err = hasGaugeStmt.QueryRowContext(ctx,
-				code.CountryCode,
-				code.LoCode,
-				code.FairwaySection,
-				code.Orc,
-				code.Hectometre,
-			).Scan(&dummy)
-			switch {
-			case err == sql.ErrNoRows:
-				news = append(news, idxCode{jdx: j, idx: i, code: code})
-			case err != nil:
-				return nil, err
-			case !dummy:
-				return nil, errors.New("Unexpected result")
-			default:
-				olds = append(olds, idxCode{jdx: j, idx: i, code: code})
+			var from, to sql.NullInt64
+
+			if dr.Applicabilityfromkm != nil {
+				from = sql.NullInt64{
+					Int64: int64(*dr.Applicabilityfromkm),
+					Valid: true,
+				}
+			}
+			if dr.Applicabilitytokm != nil {
+				to = sql.NullInt64{
+					Int64: int64(*dr.Applicabilitytokm),
+					Valid: true,
+				}
+			}
+
+			var tfrom, tto, dateInfo pgtype.Timestamptz
+
+			if dr.Startdate != nil {
+				tfrom = pgtype.Timestamptz{
+					Time:   time.Time(*dr.Startdate),
+					Status: pgtype.Present,
+				}
+			} else {
+				tfrom = pgtype.Timestamptz{
+					Status: pgtype.Null,
+				}
+			}
+
+			if dr.Enddate != nil {
+				tto = pgtype.Timestamptz{
+					Time:   time.Time(*dr.Enddate),
+					Status: pgtype.Present,
+				}
+			} else {
+				tto = pgtype.Timestamptz{
+					Status: pgtype.Null,
+				}
 			}
-		}
-	}
-	feedback.Info("ignored gauges: %d", ignored)
-	feedback.Info("new gauges: %d", len(news))
-	feedback.Info("update gauges: %d", len(olds))
 
-	if len(news) == 0 && len(olds) == 0 {
-		return nil, UnchangedError("nothing to do")
-	}
+			validity := pgtype.Tstzrange{
+				Lower:     tfrom,
+				Upper:     tto,
+				LowerType: pgtype.Inclusive,
+				UpperType: pgtype.Exclusive,
+				Status:    pgtype.Present,
+			}
 
-	// delete reference water leves of the old.
-	if len(olds) > 0 {
-		deleteReferenceWaterLevelsStmt, err := tx.PrepareContext(
-			ctx, deleteReferenceWaterLevelsSQL)
-		if err != nil {
-			return nil, err
-		}
-		defer deleteReferenceWaterLevelsStmt.Close()
-		for i := range olds {
-			code := olds[i].code
-			if _, err := deleteReferenceWaterLevelsStmt.ExecContext(ctx,
-				code.CountryCode,
-				code.LoCode,
-				code.FairwaySection,
-				code.Orc,
-				code.Hectometre,
-			); err != nil {
+			if dr.Infodate != nil {
+				dateInfo = pgtype.Timestamptz{
+					Time:   time.Time(*dr.Infodate),
+					Status: pgtype.Present,
+				}
+			} else {
+				dateInfo = pgtype.Timestamptz{
+					Status: pgtype.Null,
+				}
+			}
+
+			var geodref sql.NullString
+			if dr.Geodref != nil {
+				geodref = sql.NullString{
+					String: string(*dr.Geodref),
+					Valid:  true,
+				}
+			}
+
+			var source sql.NullString
+			if dr.Source != nil {
+				source = sql.NullString{
+					String: string(*dr.Source),
+					Valid:  true,
+				}
+			}
+
+			tx, err := conn.BeginTx(ctx, nil)
+			if err != nil {
 				return nil, err
 			}
-		}
-		// treat them as new
-		news = append(news, olds...)
-	}
-
-	insertStmt, err := tx.PrepareContext(ctx, insertGaugeSQL)
-	if err != nil {
-		return nil, err
-	}
-	defer insertStmt.Close()
-
-	insertWaterLevelStmt, err := tx.PrepareContext(
-		ctx, insertReferenceWaterLevelsSQL)
-	if err != nil {
-		return nil, err
-	}
-	defer insertWaterLevelStmt.Close()
-
-	isNtSDepthRefStmt, err := tx.PrepareContext(ctx, isNtSDepthRefSQL)
-	if err != nil {
-		return nil, err
-	}
-	defer isNtSDepthRefStmt.Close()
-
-	// insert/update the gauges
-	for i := range news {
-		ic := &news[i]
-		dr := responseData[ic.jdx].RisdataReturn[ic.idx]
-
-		feedback.Info("insert/update %s", ic.code)
-
-		var from, to sql.NullInt64
-
-		if dr.Applicabilityfromkm != nil {
-			from = sql.NullInt64{
-				Int64: int64(*dr.Applicabilityfromkm),
-				Valid: true,
-			}
-		}
-		if dr.Applicabilitytokm != nil {
-			to = sql.NullInt64{
-				Int64: int64(*dr.Applicabilitytokm),
-				Valid: true,
-			}
-		}
-
-		var tfrom, tto, dateInfo pgtype.Timestamptz
-
-		if dr.Startdate != nil {
-			tfrom = pgtype.Timestamptz{
-				Time:   time.Time(*dr.Startdate),
-				Status: pgtype.Present,
-			}
-		} else {
-			tfrom = pgtype.Timestamptz{
-				Status: pgtype.Null,
-			}
-		}
+			defer tx.Rollback()
 
-		if dr.Enddate != nil {
-			tto = pgtype.Timestamptz{
-				Time:   time.Time(*dr.Enddate),
-				Status: pgtype.Present,
-			}
-		} else {
-			tto = pgtype.Timestamptz{
-				Status: pgtype.Null,
-			}
-		}
-
-		validity := pgtype.Tstzrange{
-			Lower:     tfrom,
-			Upper:     tto,
-			LowerType: pgtype.Inclusive,
-			UpperType: pgtype.Inclusive,
-			Status:    pgtype.Present,
-		}
-
-		if dr.Infodate != nil {
-			dateInfo = pgtype.Timestamptz{
-				Time:   time.Time(*dr.Infodate),
-				Status: pgtype.Present,
-			}
-		} else {
-			dateInfo = pgtype.Timestamptz{
-				Status: pgtype.Null,
-			}
-		}
-
-		var geodref sql.NullString
-		if dr.Geodref != nil {
-			geodref = sql.NullString{
-				String: string(*dr.Geodref),
-				Valid:  true,
-			}
-		}
+			// Mark old entry of gauge as erased, if applicable
+			var isNew bool
+			err = tx.StmtContext(ctx, eraseGaugeStmt).QueryRowContext(ctx,
+				code.String(),
+				validity,
+			).Scan(&isNew)
+			switch {
+			case err != nil:
+				feedback.Warn(handleError(err).Error())
+				if err2 := tx.Rollback(); err2 != nil {
+					return nil, err2
+				}
+				unchanged++
+				continue
+			case isNew:
+				// insert gauge version entry
+				if _, err = tx.StmtContext(ctx, insertStmt).ExecContext(ctx,
+					code.CountryCode,
+					code.LoCode,
+					code.FairwaySection,
+					code.Orc,
+					code.Hectometre,
+					dr.Objname.Loc,
+					dr.Lon, dr.Lat,
+					from,
+					to,
+					&validity,
+					dr.Zeropoint,
+					geodref,
+					&dateInfo,
+					source,
+					time.Time(*dr.Lastupdate),
+				); err != nil {
+					feedback.Warn(handleError(err).Error())
+					if err2 := tx.Rollback(); err2 != nil {
+						return nil, err2
+					}
+					unchanged++
+					continue
+				}
+				// Move gauge measurements to new matching gauge version,
+				// if applicable
+				if _, err = tx.StmtContext(ctx, moveGMStmt).ExecContext(ctx,
+					code.String(),
+					&validity,
+				); err != nil {
+					feedback.Warn(handleError(err).Error())
+					if err2 := tx.Rollback(); err2 != nil {
+						return nil, err2
+					}
+					unchanged++
+					continue
+				}
+				// Set end of validity of old version to start of new version
+				// in case of overlap
+				if _, err = tx.StmtContext(ctx, fixValidityStmt).ExecContext(
+					ctx,
+					code.String(),
+					&validity,
+				); err != nil {
+					feedback.Warn(handleError(err).Error())
+					if err2 := tx.Rollback(); err2 != nil {
+						return nil, err2
+					}
+					unchanged++
+					continue
+				}
+				feedback.Info("insert new version")
+			case !isNew:
+				// try to update
+				var dummy int
+				err2 := tx.StmtContext(ctx, updateStmt).QueryRowContext(ctx,
+					code.CountryCode,
+					code.LoCode,
+					code.FairwaySection,
+					code.Orc,
+					code.Hectometre,
+					dr.Objname.Loc,
+					dr.Lon, dr.Lat,
+					from,
+					to,
+					dr.Zeropoint,
+					geodref,
+					&dateInfo,
+					source,
+					time.Time(*dr.Lastupdate),
+				).Scan(&dummy)
+				switch {
+				case err2 == sql.ErrNoRows:
+					feedback.Info("unchanged")
+					if err3 := tx.Rollback(); err3 != nil {
+						return nil, err3
+					}
+					unchanged++
+					continue
+				case err2 != nil:
+					feedback.Warn(handleError(err2).Error())
+					if err3 := tx.Rollback(); err3 != nil {
+						return nil, err3
+					}
+					unchanged++
+					continue
+				default:
+					feedback.Info("update")
+				}
 
-		var source sql.NullString
-		if dr.Source != nil {
-			source = sql.NullString{
-				String: string(*dr.Source),
-				Valid:  true,
-			}
-		}
-
-		if _, err := insertStmt.ExecContext(ctx,
-			ic.code.CountryCode,
-			ic.code.LoCode,
-			ic.code.FairwaySection,
-			ic.code.Orc,
-			ic.code.Hectometre,
-			string(*dr.Objname.Loc),
-			float64(*dr.Lon), float64(*dr.Lat),
-			from,
-			to,
-			&validity,
-			float64(*dr.Zeropoint),
-			geodref,
-			&dateInfo,
-			source,
-		); err != nil {
-			return nil, err
-		}
-
-		for _, wl := range []struct {
-			level **erdms.RisreflevelcodeType
-			value **erdms.RisreflevelvalueType
-		}{
-			{&dr.Reflevel1code, &dr.Reflevel1value},
-			{&dr.Reflevel2code, &dr.Reflevel2value},
-			{&dr.Reflevel3code, &dr.Reflevel3value},
-		} {
-			if *wl.level == nil || *wl.value == nil {
-				continue
+				// Remove obsolete reference water levels
+				var currLevels pgtype.VarcharArray
+				currLevels.Set([]string{
+					string(*dr.Reflevel1code),
+					string(*dr.Reflevel2code),
+					string(*dr.Reflevel3code),
+				})
+				rwls, err := tx.StmtContext(ctx,
+					deleteReferenceWaterLevelsStmt).QueryContext(ctx,
+					code.String(),
+					&validity,
+					&currLevels,
+				)
+				if err != nil {
+					return nil, err
+				}
+				defer rwls.Close()
+				for rwls.Next() {
+					var delRef string
+					if err := rwls.Scan(&delRef); err != nil {
+						return nil, err
+					}
+					feedback.Warn("Removed reference water level %s from %s",
+						delRef, code)
+				}
+				if err := rwls.Err(); err != nil {
+					return nil, err
+				}
 			}
 
-			var isNtSDepthRef bool
-			if err := isNtSDepthRefStmt.QueryRowContext(
-				ctx,
-				string(**wl.level),
-			).Scan(
-				&isNtSDepthRef,
-			); err != nil {
-				return nil, err
-			}
-			if !isNtSDepthRef {
-				feedback.Warn(
-					"Reference level code '%s' is not in line with the NtS reference_code table",
-					string(**wl.level))
+			// "Upsert" reference water levels
+			for _, wl := range []struct {
+				level **erdms.RisreflevelcodeType
+				value **erdms.RisreflevelvalueType
+			}{
+				{&dr.Reflevel1code, &dr.Reflevel1value},
+				{&dr.Reflevel2code, &dr.Reflevel2value},
+				{&dr.Reflevel3code, &dr.Reflevel3value},
+			} {
+				if *wl.level == nil || *wl.value == nil {
+					continue
+				}
+
+				var isNtSDepthRef bool
+				if err := tx.StmtContext(
+					ctx, isNtSDepthRefStmt).QueryRowContext(ctx,
+					string(**wl.level),
+				).Scan(
+					&isNtSDepthRef,
+				); err != nil {
+					return nil, err
+				}
+				if !isNtSDepthRef {
+					feedback.Warn(
+						"Reference level code '%s' is not in line "+
+							"with the NtS reference_code table",
+						string(**wl.level))
+				}
+
+				if _, err := tx.StmtContext(
+					ctx, insertWaterLevelStmt).ExecContext(ctx,
+					code.CountryCode,
+					code.LoCode,
+					code.FairwaySection,
+					code.Orc,
+					code.Hectometre,
+					&validity,
+					string(**wl.level),
+					int64(**wl.value),
+				); err != nil {
+					feedback.Warn(handleError(err).Error())
+					tx.Rollback()
+					continue
+				}
 			}
 
-			if _, err := insertWaterLevelStmt.ExecContext(
-				ctx,
-				ic.code.CountryCode,
-				ic.code.LoCode,
-				ic.code.FairwaySection,
-				ic.code.Orc,
-				ic.code.Hectometre,
-				string(**wl.level),
-				int64(**wl.value),
-			); err != nil {
+			if err = tx.Commit(); err != nil {
 				return nil, err
 			}
 		}
 	}
 
-	if err = tx.Commit(); err == nil {
-		feedback.Info("Refreshing gauges successfully took %s.",
-			time.Since(start))
-	} else {
-		feedback.Error("Refreshing gauges failed after %s.",
-			time.Since(start))
+	if len(gauges) == 0 {
+		return nil, UnchangedError("No gauges returned from ERDMS")
+	}
+
+	var pgCountries, pgGauges pgtype.VarcharArray
+	pgCountries.Set(countries)
+	pgGauges.Set(gauges)
+	obsGauges, err := conn.QueryContext(ctx,
+		eraseObsoleteGaugesSQL,
+		&pgCountries,
+		&pgGauges)
+	if err != nil {
+		return nil, err
 	}
+	defer obsGauges.Close()
+	for obsGauges.Next() {
+		var isrs string
+		if err := obsGauges.Scan(&isrs); err != nil {
+			return nil, err
+		}
+		feedback.Info("Erased %s", isrs)
+		unchanged--
+	}
+	if err := obsGauges.Err(); err != nil {
+		return nil, err
+	}
+
+	if unchanged == len(gauges) {
+		return nil, UnchangedError("All gauges unchanged")
+	}
+
+	feedback.Info("Importing gauges took %s",
+		time.Since(start))
 
 	return nil, err
 }
--- a/pkg/imports/wp.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/wp.go	Mon Jun 03 10:19:18 2019 +0200
@@ -72,9 +72,10 @@
 	return "waterway profiles"
 }
 
-func (wpJobCreator) Depends() []string {
-	return []string{
-		"waterway_profiles",
+func (wpJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"waterway_profiles"},
+		{"distance_marks_virtual"},
 	}
 }
 
--- a/pkg/imports/wx.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/imports/wx.go	Mon Jun 03 10:19:18 2019 +0200
@@ -58,9 +58,10 @@
 
 func (wxJobCreator) Create() Job { return new(WaterwayAxis) }
 
-func (wxJobCreator) Depends() []string {
-	return []string{
-		"waterway_axis",
+func (wxJobCreator) Depends() [2][]string {
+	return [2][]string{
+		{"waterway_axis"},
+		{},
 	}
 }
 
@@ -80,43 +81,29 @@
 const (
 	deleteWaterwayAxisSQL = `
 WITH resp AS (
-  SELECT best_utm(area::geometry) AS t,
-         ST_Transform(area::geometry, best_utm(area::geometry)) AS a
-  FROM users.responsibility_areas
-  WHERE country = users.current_user_country()
+  SELECT users.current_user_area_utm() AS a
 )
 DELETE FROM waterway.waterway_axis
 WHERE pg_has_role('sys_admin', 'MEMBER')
   OR ST_Covers((SELECT a FROM resp),
-    ST_Transform(wtwaxs::geometry, (SELECT t FROM resp)))
-`
-
-	checkCrossingAxisSQL = `
-SELECT ST_AsText(ST_Intersection(new_line.wtwaxs, axis.wtwaxs))
-  FROM waterway.waterway_axis AS axis, waterway.waterway_axis AS new_line
-  WHERE new_line.id = $1 AND axis.id <> $1
-    AND ST_Crosses(new_line.wtwaxs::geometry, axis.wtwaxs::geometry)
+    ST_Transform(wtwaxs::geometry, (SELECT ST_SRID(a) FROM resp)))
 `
 
 	insertWaterwayAxisSQL = `
 WITH resp AS (
-  SELECT best_utm(area::geometry) AS t,
-         ST_Transform(area::geometry, best_utm(area::geometry)) AS a
-  FROM users.responsibility_areas
-  WHERE country = users.current_user_country()
+  SELECT users.current_user_area_utm() AS a
 )
 INSERT INTO waterway.waterway_axis (wtwaxs, objnam, nobjnam)
-SELECT ST_Transform(clipped.geom, 4326)::geography, $3, $4
-  FROM resp,
-    ST_Node(ST_Transform(
-      ST_GeomFromWKB($1, $2::integer), t)) AS new_line (new_line),
-    LATERAL (SELECT (ST_Dump(
+SELECT dmp.geom, $3, $4
+  FROM ST_GeomFromWKB($1, $2::integer) AS new_line (new_line),
+    ST_Dump(ST_Transform(ST_CollectionExtract(
       CASE WHEN pg_has_role('sys_admin', 'MEMBER')
-        THEN new_line
-        ELSE ST_Intersection(a, new_line)
-        END
-      )).geom AS geom
-    ) AS clipped
+        THEN ST_Node(ST_Transform(new_line,
+          best_utm(ST_Transform(new_line, 4326))))
+        ELSE ST_Intersection((SELECT a FROM resp),
+          ST_Node(ST_Transform(new_line, (SELECT ST_SRID(a) FROM resp))))
+        END,
+      2), 4326)) AS dmp
 RETURNING id
 `
 )
@@ -175,12 +162,6 @@
 	}
 	defer insertStmt.Close()
 
-	checkCrossingStmt, err := tx.PrepareContext(ctx, checkCrossingAxisSQL)
-	if err != nil {
-		return nil, err
-	}
-	defer checkCrossingStmt.Close()
-
 	// Delete the old features.
 	if _, err := tx.ExecContext(ctx, deleteWaterwayAxisSQL); err != nil {
 		return nil, err
@@ -190,6 +171,7 @@
 		unsupported       = stringCounter{}
 		missingProperties int
 		badProperties     int
+		outside           int
 		features          int
 	)
 
@@ -248,11 +230,11 @@
 					epsg,
 					props,
 					nobjnam,
-					checkCrossingStmt,
+					&outside,
+					&features,
 					insertStmt); err != nil {
 					return err
 				}
-				features++
 			case "MultiLineString":
 				var ls []lineSlice
 				if err := json.Unmarshal(*feature.Geometry.Coordinates, &ls); err != nil {
@@ -267,11 +249,11 @@
 						epsg,
 						props,
 						nobjnam,
-						checkCrossingStmt,
+						&outside,
+						&features,
 						insertStmt); err != nil {
 						return err
 					}
-					features++
 				}
 			default:
 				unsupported[feature.Geometry.Type]++
@@ -294,10 +276,12 @@
 		feedback.Warn("Unsupported types found: %s", unsupported)
 	}
 
+	if outside > 0 {
+		feedback.Info("Features outside responsibility area: %d", outside)
+	}
+
 	if features == 0 {
-		err := errors.New("No features found")
-		feedback.Error("%v", err)
-		return nil, err
+		return nil, errors.New("No features found")
 	}
 
 	if err = tx.Commit(); err == nil {
@@ -316,10 +300,11 @@
 	epsg int,
 	props waterwayAxisProperties,
 	nobjnam sql.NullString,
-	checkCrossingStmt, insertStmt *sql.Stmt,
+	outside, features *int,
+	insertStmt *sql.Stmt,
 ) error {
 	var id int
-	if err := savepoint(func() error {
+	err := savepoint(func() error {
 		err := insertStmt.QueryRowContext(
 			ctx,
 			l.asWKB(),
@@ -328,22 +313,16 @@
 			nobjnam,
 		).Scan(&id)
 		return err
-	}); err != nil {
+	})
+	switch {
+	case err == sql.ErrNoRows:
+		*outside++
+		// ignore -> filtered by responsibility_areas
+		return nil
+	case err != nil:
 		feedback.Warn(handleError(err).Error())
-	}
-
-	var crossing string
-	switch err := checkCrossingStmt.QueryRowContext(
-		ctx,
-		id,
-	).Scan(&crossing); {
-	case err != nil && err != sql.ErrNoRows:
-		return err
-	case err == nil:
-		feedback.Warn(
-			"Linestring %d crosses previously imported linestring near %s. "+
-				"Finding a contiguous axis may not work here",
-			id, crossing)
+	default:
+		*features++
 	}
 	return nil
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/misc/warn.go	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,32 @@
+// 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) 2019 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package misc
+
+type WarningLimiter struct {
+	Log         func(format string, args ...interface{})
+	MaxWarnings int
+	Warnings    int
+}
+
+func (w *WarningLimiter) Warn(format string, args ...interface{}) {
+	if w.Warnings++; w.Warnings <= w.MaxWarnings {
+		w.Log(format, args...)
+	}
+}
+
+func (w *WarningLimiter) Close() {
+	if w.Warnings > w.MaxWarnings {
+		w.Log("Too many warnings. %d ignored.", w.Warnings-w.MaxWarnings)
+	}
+}
--- a/pkg/models/cross.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/models/cross.go	Mon Jun 03 10:19:18 2019 +0200
@@ -22,6 +22,7 @@
 	"math"
 	"time"
 
+	"gemma.intevation.de/gemma/pkg/common"
 	"gemma.intevation.de/gemma/pkg/wkb"
 )
 
@@ -103,7 +104,7 @@
 	if err := json.Unmarshal(data, &s); err != nil {
 		return err
 	}
-	t, err := time.Parse("2006-01-02", s)
+	t, err := time.Parse(common.DateFormat, s)
 	if err != nil {
 		return err
 	}
--- a/pkg/models/importbase.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/models/importbase.go	Mon Jun 03 10:19:18 2019 +0200
@@ -91,8 +91,7 @@
 }
 
 func (cd *ConfigDuration) MarshalJSON() ([]byte, error) {
-	s := cd.Duration.String()
-	return json.Marshal([]byte(s))
+	return json.Marshal(cd.Duration.String())
 }
 
 func (ct *ConfigTime) UnmarshalJSON(data []byte) error {
--- a/pkg/models/imports.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/models/imports.go	Mon Jun 03 10:19:18 2019 +0200
@@ -107,6 +107,19 @@
 		Date      Date            `json:"date-info"`
 		Countries UniqueCountries `json:"countries"`
 	}
+
+	SectionImport struct {
+		EmailType
+
+		Name      string  `json:"name"`
+		From      Isrs    `json:"from"`
+		To        Isrs    `json:"to"`
+		Tolerance float32 `json:"tolerance"`
+		ObjNam    string  `json:"objnam"`
+		NObjNam   *string `json:"nobjnam"`
+		Source    string  `json:"source-organization"`
+		Date      Date    `json:"date-info"`
+	}
 )
 
 func (cui *ConfigurableURLImport) MarshalAttributes(attrs common.Attributes) error {
@@ -294,3 +307,69 @@
 	}
 	return nil
 }
+
+func (seci *SectionImport) MarshalAttributes(attrs common.Attributes) error {
+	if err := seci.EmailType.MarshalAttributes(attrs); err != nil {
+		return err
+	}
+	attrs.Set("name", seci.Name)
+	attrs.Set("from", seci.From.String())
+	attrs.Set("to", seci.To.String())
+	attrs.Set("objnam", seci.ObjNam)
+	if seci.NObjNam != nil {
+		attrs.Set("nobjnam", *seci.NObjNam)
+	}
+	attrs.Set("source-organization", seci.Source)
+	attrs.SetDate("date-info", seci.Date.Time)
+
+	return nil
+}
+
+func (seci *SectionImport) UnmarshalAttributes(attrs common.Attributes) error {
+	if err := seci.EmailType.UnmarshalAttributes(attrs); err != nil {
+		return err
+	}
+	name, found := attrs.Get("name")
+	if !found {
+		return errors.New("missing 'name' attribute")
+	}
+	seci.Name = name
+	from, found := attrs.Get("from")
+	if !found {
+		return errors.New("missing 'from' attribute")
+	}
+	f, err := IsrsFromString(from)
+	if err != nil {
+		return err
+	}
+	seci.From = *f
+	to, found := attrs.Get("to")
+	if !found {
+		return errors.New("missing 'to' attribute")
+	}
+	t, err := IsrsFromString(to)
+	if err != nil {
+		return err
+	}
+	seci.To = *t
+	objnam, found := attrs.Get("objnam")
+	if !found {
+		return errors.New("missing 'objnam' attribute")
+	}
+	seci.ObjNam = objnam
+	nobjnam, found := attrs.Get("nobjnam")
+	if found {
+		seci.NObjNam = &nobjnam
+	}
+	source, found := attrs.Get("source-organization")
+	if !found {
+		return errors.New("missing 'source' attribute")
+	}
+	seci.Source = source
+	date, found := attrs.Date("date-info")
+	if !found {
+		return errors.New("missing 'date-info' attribute")
+	}
+	seci.Date = Date{date}
+	return nil
+}
--- a/pkg/models/sr.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/models/sr.go	Mon Jun 03 10:19:18 2019 +0200
@@ -36,7 +36,13 @@
 
 const (
 	checkDepthReferenceSQL = `
-SELECT true FROM depth_references WHERE depth_reference = $1`
+SELECT EXISTS(SELECT 1
+  FROM waterway.bottlenecks bn
+    JOIN waterway.gauges g
+      ON bn.gauge_location = g.location AND bn.gauge_validity = g.validity
+    JOIN waterway.gauges_reference_water_levels rl USING (location, validity)
+  WHERE bn.objnam = $1
+    AND rl.depth_reference = $2)`
 
 	checkBottleneckSQL = `
 SELECT true FROM waterway.bottlenecks WHERE objnam = $1`
@@ -61,18 +67,6 @@
 
 	var b bool
 	err := conn.QueryRowContext(ctx,
-		checkDepthReferenceSQL,
-		m.DepthReference).Scan(&b)
-	switch {
-	case err == sql.ErrNoRows:
-		errs = append(errs, fmt.Errorf("unknown depth reference '%s'", m.DepthReference))
-	case err != nil:
-		errs = append(errs, err)
-	case !b:
-		errs = append(errs, errors.New("unexpected depth reference"))
-	}
-
-	err = conn.QueryRowContext(ctx,
 		checkBottleneckSQL,
 		m.Bottleneck).Scan(&b)
 	switch {
@@ -85,6 +79,18 @@
 	}
 
 	err = conn.QueryRowContext(ctx,
+		checkDepthReferenceSQL,
+		m.Bottleneck,
+		m.DepthReference).Scan(&b)
+	switch {
+	case !b:
+		errs = append(errs,
+			fmt.Errorf("unknown depth reference '%s'", m.DepthReference))
+	case err != nil:
+		errs = append(errs, err)
+	}
+
+	err = conn.QueryRowContext(ctx,
 		checkBottleneckDateUniqueSQL,
 		m.Bottleneck, m.Date.Time).Scan(&b)
 	switch {
--- a/pkg/scheduler/scheduler.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/scheduler/scheduler.go	Mon Jun 03 10:19:18 2019 +0200
@@ -15,6 +15,8 @@
 
 import (
 	"log"
+	"sort"
+	"strings"
 	"sync"
 
 	"github.com/robfig/cron"
@@ -78,6 +80,8 @@
 
 	cr := cron.New()
 
+	var numJobs int
+
 	for {
 		var ba BoundAction
 		ok, err := next(&ba)
@@ -97,8 +101,11 @@
 			cfgID:     ba.CfgID,
 		}
 		cr.Schedule(schedule, job)
+		numJobs++
 	}
 
+	log.Printf("info: booting %d scheduler jobs from database.\n", numJobs)
+
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
@@ -288,8 +295,26 @@
 	return s.actions[name]
 }
 
+func LogActionNames() {
+	names := global.actionNames()
+	sort.Strings(names)
+	log.Printf("info: actions registered to scheduler: %s\n",
+		strings.Join(names, ", "))
+}
+
+func (s *scheduler) actionNames() []string {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	names := make([]string, len(s.actions))
+	var i int
+	for k := range s.actions {
+		names[i] = k
+		i++
+	}
+	return names
+}
+
 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
--- a/pkg/soap/erdms/service.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/soap/erdms/service.go	Mon Jun 03 10:19:18 2019 +0200
@@ -118,6 +118,21 @@
 // lastupdate, last modification date time of this record
 type LastupdateType time.Time
 
+const LastupdateTypeFormat = "2006-01-02T15:04:05.999Z07:00"
+
+func (lut *LastupdateType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+	var value string
+	if err := d.DecodeElement(&value, &start); err != nil {
+		return err
+	}
+	t, err := time.Parse(LastupdateTypeFormat, value)
+	if err != nil {
+		return err
+	}
+	*lut = LastupdateType(t)
+	return nil
+}
+
 // one of the supported languages (basic and additional set), based on ISO 639-1
 type LangType string
 
--- a/pkg/soap/ifbn/service.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/soap/ifbn/service.go	Mon Jun 03 10:19:18 2019 +0200
@@ -77,7 +77,7 @@
 
 	Responsible_country *CountryCode `xml:"responsible_country,omitempty"`
 
-	Revisiting_time string `xml:"revisiting_time,omitempty"`
+	Revisiting_time *string `xml:"revisiting_time,omitempty"`
 
 	SURTYP *SurtypEnum `xml:"SURTYP,omitempty"`
 
--- a/pkg/soap/nts/service.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/soap/nts/service.go	Mon Jun 03 10:19:18 2019 +0200
@@ -1273,13 +1273,13 @@
 	Measure_code *Measure_code_enum `xml:"measure_code,omitempty"`
 
 	// Measured or predicted value
-	Value float32 `xml:"value,omitempty"`
+	Value *float32 `xml:"value,omitempty"`
 
 	// Lowest value of confidence interval
-	Value_min float32 `xml:"value_min,omitempty"`
+	Value_min *float32 `xml:"value_min,omitempty"`
 
 	// Highest value of confidence interval
-	Value_max float32 `xml:"value_max,omitempty"`
+	Value_max *float32 `xml:"value_max,omitempty"`
 
 	// Unit of the water related value
 	Unit *Unit_enum `xml:"unit,omitempty"`
--- a/pkg/soap/soap.go	Wed May 29 10:58:45 2019 +0200
+++ b/pkg/soap/soap.go	Mon Jun 03 10:19:18 2019 +0200
@@ -15,6 +15,7 @@
 
 import (
 	"bytes"
+	"context"
 	"crypto/tls"
 	"encoding/xml"
 	"fmt"
@@ -23,7 +24,10 @@
 	"math/rand"
 	"net"
 	"net/http"
+	"sync"
 	"time"
+
+	"gemma.intevation.de/gemma/pkg/config"
 )
 
 const timeout = time.Duration(30 * time.Second)
@@ -272,6 +276,21 @@
 
 	client := &http.Client{Transport: tr}
 
+	timeout := config.SOAPTimeout()
+
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+
+	var once sync.Once
+
+	defer once.Do(cancel)
+
+	req = req.WithContext(ctx)
+
+	go func() {
+		defer once.Do(cancel)
+		<-ctx.Done()
+	}()
+
 	res, err := client.Do(req)
 	if err != nil {
 		return err
--- a/schema/auth.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/auth.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -4,7 +4,7 @@
 -- SPDX-License-Identifier: AGPL-3.0-or-later
 -- License-Filename: LICENSES/AGPL-3.0.txt
 
--- Copyright (C) 2018 by via donau
+-- Copyright (C) 2018, 2019 by via donau
 --   – Österreichische Wasserstraßen-Gesellschaft mbH
 -- Software engineering by Intevation GmbH
 
@@ -102,6 +102,8 @@
     LOOP
         EXECUTE format('CREATE POLICY hide_staging ON waterway.%I '
             'FOR SELECT TO waterway_user USING (staging_done)', the_table);
+        EXECUTE format('CREATE POLICY sys_admin ON waterway.%I '
+            'FOR ALL TO sys_admin USING (true)', the_table);
         EXECUTE format('ALTER TABLE waterway.%I ENABLE ROW LEVEL SECURITY',
             the_table);
     END LOOP;
@@ -112,10 +114,10 @@
 -- RLS policies for templates
 --
 CREATE POLICY select_templates ON users.templates FOR SELECT TO waterway_user
-    USING (country IS NULL OR country = users.current_user_country());
+    USING (country IS NULL OR country = (SELECT country FROM users.list_users WHERE username = current_user));
 
 CREATE POLICY user_templates ON users.templates FOR ALL TO waterway_admin
-    USING (country = users.current_user_country());
+    USING (country = (SELECT country FROM users.list_users WHERE username = current_user));
 
 CREATE POLICY admin_templates ON users.templates FOR ALL TO sys_admin
     USING (true);
@@ -131,27 +133,25 @@
 
 CREATE POLICY same_country ON waterway.gauge_measurements
     FOR ALL TO waterway_admin
-    USING ((fk_gauge_id).country_code = users.current_user_country());
+    USING ((location).country_code
+        = (SELECT country FROM users.list_users WHERE username = current_user)
+    );
 
 CREATE POLICY same_country ON waterway.waterway_profiles
     FOR ALL TO waterway_admin
-    USING ((location).country_code = users.current_user_country());
+    USING ((location).country_code = (SELECT country FROM users.list_users WHERE username = current_user));
 
 CREATE POLICY responsibility_area ON waterway.bottlenecks
     FOR ALL TO waterway_admin
-    USING (utm_covers(area));
+    USING (users.utm_covers(area));
 
 CREATE POLICY responsibility_area ON waterway.sounding_results
     FOR ALL TO waterway_admin
-    USING (utm_covers(area));
+    USING (users.utm_covers(area));
 
 CREATE POLICY responsibility_area ON waterway.fairway_dimensions
     FOR ALL TO waterway_admin
-    USING (utm_covers(area));
-
-CREATE POLICY sys_admin ON waterway.stretches
-    FOR ALL TO sys_admin
-    USING (true);
+    USING (users.utm_covers(area));
 
 --
 -- RLS policies for imports and import config
@@ -212,7 +212,7 @@
 CREATE POLICY import_configuration_policy ON import.import_configuration
     FOR ALL TO waterway_admin
     USING (
-        users.current_user_country() = (
+        (SELECT country FROM users.list_users WHERE username = current_user) = (
             SELECT country FROM users.list_users lu
             WHERE lu.username = import.import_configuration.username));
 
--- a/schema/auth_tests.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/auth_tests.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -15,6 +15,28 @@
 -- pgTAP test script for privileges and RLS policies
 --
 
+CREATE FUNCTION test_privs() RETURNS SETOF TEXT AS
+$$
+DECLARE the_schema CONSTANT varchar = 'waterway';
+DECLARE the_table varchar;
+BEGIN
+    FOR the_table IN
+        SELECT table_name
+            FROM information_schema.tables
+            WHERE table_schema = the_schema
+    LOOP
+        RETURN NEXT table_privs_are(
+            the_schema,
+            the_table,
+            'waterway_user',
+            ARRAY['SELECT'],
+            format('waterway_user can SELECT from %I.%I',
+                the_schema, the_table));
+    END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_privs();
+
 --
 -- Run tests as unprivileged user
 --
@@ -53,16 +75,18 @@
 
 PREPARE bn_insert (varchar, geometry(MULTIPOLYGON, 4326)) AS
     INSERT INTO waterway.bottlenecks (
-        bottleneck_id, fk_g_fid, stretch, area, rb, lb, responsible_country,
+        gauge_location, gauge_validity,
+        bottleneck_id, stretch, area, rb, lb, responsible_country,
         revisiting_time, limiting, source_organization)
-        VALUES (
+        SELECT
+            location, validity,
             $1,
-            ('AT', 'XXX', '00001', 'G0001', 1)::isrs,
             isrsrange(('AT', 'XXX', '00001', '00000', 0)::isrs,
                 ('AT', 'XXX', '00001', '00000', 2)::isrs),
             $2, 'AT', 'AT', 'AT',
             1, 'depth', 'testorganization'
-        );
+        FROM waterway.gauges
+        WHERE location = ('AT', 'XXX', '00001', 'G0001', 1)::isrs;
 SELECT lives_ok($$
     EXECUTE bn_insert(
         'test1',
--- a/schema/demo-data/published_services.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/demo-data/published_services.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -12,6 +12,7 @@
 --  * Tom Gottfried <tom@intevation.de>
 
 INSERT INTO sys_admin.published_services (name) VALUES
+    ('waterway.sections_geoserver'),
     ('waterway.stretches_geoserver'),
     ('waterway.fairway_dimensions'),
     ('waterway.gauges_geoserver'),
@@ -22,4 +23,5 @@
     ('waterway.bottleneck_overview'),
     ('waterway.waterway_axis'),
     ('waterway.waterway_area'),
-    ('waterway.waterway_profiles')
+    ('waterway.waterway_profiles'),
+    ('waterway.sounding_differences')
--- a/schema/demo-data/responsibility_areas.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/demo-data/responsibility_areas.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -21,6 +21,7 @@
 AT
 HR
 BG
+RS
 \.
 
 COPY users.responsibility_areas (country, area) FROM stdin;
@@ -30,6 +31,7 @@
 AT	0106000020E61000000100000001030000000100000046040000C04A9D52EEF13040EAFA1051554D4840E86633EC4FF430408EFE99D958474840286AB342F5F230403A155330BD45484000FF1F3E68F23040BE56E1BA15454840F8725F4833EE304062C9DE1D9C43484038DAA08CF7E93040CEC6C3B57942484038728DDE0FE830408A033BAD44414840D0B6E56DB6E630403A28B3A4903F48407009A8C604E03040E6296E605B3C484018E343FD6CDD30407E106C43A23A4840A075570E5EDC304012F2820ECE38484050C79F8C17DA304046FBE4D7F831484050CA48376ED93040B6A2B4412A314840E8D9EFA7CBD830401A76D6E3C8304840709B628258D8304086E26EE033304840605F8CDE2FD830405241190BCC2E484040D7BF11FBD830403AB61EC5062E48401083C011F7DA3040864BC652A02D484000C38ADE1FE03040E6FCB4C1712D48401038156794E1304072A37FF1282D4840F0175C4847E4304042883D4A6B2C484090FFB9D7E5E630407EE7FA85712B484038728DDE0FE830400E7FBF986D2A4840E0B823DB7AE7304062A652F8DB2948406082267879E63040428B10511E294840A0829F8CFFE530408E94B85E7828484030BE83071CE730402279FDA2342848401831D022CCE730408EDAC235EF274840B8ECF0A7ABE830404E41E8F44A27484078DA19A17DE9304052A925FF8E264840A852C6E8CEE930400EF0BBFBF9254840204DEDA7A7EA3040FA197FF138254840A037F4E19CEC30403A71AC6ACD24484058689FEFF0EE30406E3107FA7124484018C3C2AEC5F130401A7CE5D79523484000B329155CF330407A2DD44667234840E86633EC4FF430403672C6D2F5224840D8E6F6E18CF43040227A56956722484058AD50D434F43040B21E9282F0204840E86633EC4FF43040A6CF6DE05320484050342AB24EF830402EE59002BB1B48407029F14486F9304096E0E6576A194840C0EAEA0A8DF93040966B71FDA3164840685A156760FB30409EB33473A514484058D39318D50131407A2C973C4712484068EC07F3590531403275D1A98E114840E0BEE5D0730931408EB6077A58114840C09E2CB2260C314002AC5C4FC410484048C8FD11FD0F314092B58AC16D0E48402025146734113140769F3710B00D4840F040B8D789143140E6BCAB6A7E0C484050595A48EB113140FE16828E690B484098AAB09D981031409629D2A91D0A484088352615240F31400A9C3510BC074840A8B727782510314036036EE08507484038920E2D371331409AFC0A97AA064840086171F6D61131409A3FE7F490044840902086A4B61731402E6E7EF17C03484030092FB2FE1F31400AC73CCA7F02484060822678F9253140928F4F5BB20048402072550EEA1531402EC35DCF2DFC474018529CEF9C1831404A8DEC9120FB47408898B9D7811831406215B95E55FA474018E2F87E431731400640087A88F9474008AEB29D8C163140CE0A23626FF84740783836EC0B163140729D9A5938F84740B0D09B52AA13314096B166A6ACF7474048124B37FA1231401A3A46844FF74740885308F33D1331404E1CD446C1F64740A07FBE1183143140565E963C7FF64740A0EF6182DC153140E202BDFB59F64740E071DCF963163140A2F33DCA18F64740E0B8EB0A551531407EBCC96FD1F3474020CE6BBCD91331407AA23B2D24F24740B0DEF67E4F113140FE3D4821D9F04740C05236891A0D3140BACAE8F4BAEF474058FC27783D043140E61ED3C610EF4740685B60E509013140F2F424FF7FEE4740309F4D371E013140AA850DB41AED4740C804AA63BB0231408A4F236287EC4740E04E42601A083140C6AC3C2DB1EB4740787038261D0A3140468E3ECA2EEB4740F01BC011930C31405E1A68A6CAE84740200410CA3D0E314016137937FBE74740E072EFA7670A31406E2DAAEA8AE6474048AC95B5BF0A31405A8B640957E447407862DDF9770C3140FA640040BFE14740B81660E5F10C31407E74371091DD4740D0421604370E314086E404DD46DC4740F8EBE66D62103140F21DC0184DDB474000D6FB7E4B133140FA45DF9DAFDA474000914A9A070E314056AF34F3DBD94740685A156760FB30409A63CC0C04D9474000FD894115E73040A6A747A14CD74740A06050715FDD30400E9F2BB9E6D7474070D41667A4D930409A1E4C3E41DB474010367F6A41D6304086B5842B49DA4740F870917B3AD13040DE267F7195D74740B8CA69BC65CE3040067FB124A4D64740B0DDAB0026CC30402656A29375D64740708B3989BABD3040D20CDC003AD74740F828FF1BFABA30407EE548A1CBD7474070C1DD961EB830409247852BCCD84740E8D6D65C29B63040FA1B31D61EDA4740E822CD8535B53040F68BD44678DB4740A0F12F4FD5B33040EA1DABEA9EDC474000DB72F69AB03040DE25267F62DD4740E0BE1DA1199C30401657AE8714E047403076F1A75B913040FEF2B85E89E047400052641FF78730409607ADEA19DF4740B0E4F0446E8630409AAE6943DDDD474040237E6AE1863040A6629D762DDC4740C030E76D7A853040B67EF98513DB4740A08716044F83304006A830565EDA4740B075608218793040CE0CC7D28BD84740505C73930D763040161AB7C19ED74740E8B229155C743040F688D02944D74740107F5C482B733040B6557A5459D74740E052A629E67130402AE6D4C6BFD74740981C22DB6A7030408A4D717D61D84740E07508F3896E30403EB4963CBDD747408816F044A66A3040FAACBCFB9BD547402075368966683040D2753373A6D44740507E8ADE076D3040DE6F5C4FBFD34740E82318045F7B304096D6963CC9D14740B0D8F3447682304092908C6552D247404815F4E15093304052ABE557FECF474078857793C19B3040FA6567A67BD04740B042D5BF56A130407A2DC6D29DCF4740C00F5371CFA530404A8251F895CD4740E0B823DBFAA73040EE046FE0FFCA4740200593188DA63040461B6DE084C84740805734ECE3AA30406AAAC435B1C74740D831A28C6FAE30408E063FCA78C6474008929E8C6BB0304052871C28DEC4474038DBEB0A21B0304036BC188BE5C24740103AAB6367AD304002CD3D4A43C1474018D6334FF1A5304086B6CFA932C0474010EC1E3E08A3304042F1A23021BF47405898116710A430400A60973CF9B94740007A715481A03040DE4B5CBF07B94740F0B05B48E396304032081D287BB64740E82318045F7B30408676E9F436B247403849314F4D783040AE453573E8B347402835A48CE37430407A786309B7B44740D048D8F9AF7130405E3E31D66AB44740E86D7830186F3040CE2F53F8CBB24740D0A9D5BFBA6F3040D6044584E3AD4740286661822C6F3040FE8625FF82AD4740103AAB63E76D3040B6E75E6C49AD474078FEC6E8CA6C304042AEB85EF1AC4740D80DF544A26C30409229BD7B2FAC4740F0413B26596D30402A49392DB2AB474090B40E2D836E304056B071FD7BAB4740F065E86DA66F30406680D7E355AB47405824398956703040E6965AB214AB4740E04B61E51D783040668130D688A5474028F8530E267930406E3B505B6EA34740E04E0A9074773040322E8AC8B7A14740A830AF9DD47330404ADA2D3996A04740A80BB7D7DD6B30401A5967261F9F4740A80BB7D7DD6B304072786AC31B9F47402006DE96B66C3040A6406A43E09C4740D06FD65CC5683040A25B3C4A129A474050A1ECA7AB693040BEE31D45F5974740105A6482346B3040CAF0941F82974740D83C540E3E6D304006AB035D9197474050702FB2626F30401E9954957F974740808F6EF61A7130407602AAEAAB964740C81E60482F713040C231CD0C8C954740E86D7830186F3040C2CD01405D934740A0758FDE036F304052D13610A8924740004B16677472304072977DF1E1914740482E5F481B7B3040569013514C93474038BA1F3E507F3040926CB5C128934740E0CBF57E6F82304032C1C7D29A914740C06D37EC378130408EFB87481B904740E82318045F7B304056AA707D788D474008648A41F9753040C675178B538C4740703036894E743040EA003C2D758A4740E0E34D37367630408EE0AE87C4884740E82318045F7B304006A4C5D22D884740007C0B2D637E3040AECACC0CA8874740F0F82578497F30409A44B9DEFD86474020012F4F417E30405ECB6D604A864740E82318045F7B3040FE63D1A9A8854740885BA129EA6F304072C5AB6A118447402006DE96B66C3040FACC281C15834740D03A7DCD0A743040C286BA5EC6824740B8CECD85B1773040FA66A4B05B824740E82318045F7B3040C6514E5B3381474040D5F144827C30406A14759AD07F4740680DD4BFAA773040B6A91C286A7F4740F0D58BDEFF7030406AE82262637F474068C7D75CBD6C3040DA49E8F41D7F474068C7D75CBD6C30404269EB911A7F474058D7F7E1A06B30409AACC5D2C07E4740E8203789626A30400AF3E2BAA57E4740B82689DE0F693040761042E7C57E47400841F0A7AF6730400ECD67A61F7F474060F66582446330406A7E41E742804740C829DAF9D75D30405ECCC6527D80474050595A486B533040421DF5680E8047408044FB1BDE493040A66B8DE5B6804740A8EC800760463040125BF6688D804740E04DF7E1F0433040B2AD6409237F474050121367D44230408252190B327D4740406E2915C440304082EAF02E9C7C474068CAB8D7393E3040EA69A1136B7C4740F8A3540EA23B3040BE10E557A87B474060F201B9F83A30406E79AE87E07A474008B1CBE82E3B304042CE247F2A7A4740C0EAEA0A0D3B30407A0BAF87637947401080A7C65439304082951FC574784740D09270F68E373040F218188BFC7747408855D6BF36323040AE81E1BA34774740105A6482B42B30408692067A92754740103960E5BD2830406682973C857447406065BE74741F3040B2D278B72C704740A88009902C1C3040CE2C4FDB176F474048AE2BB2121830402A04B95E6F6E4740C0DD162F8D0D3040466671B04B6C4740E0E62EB232083040BEC7F905356B474090A4E0E747073040669AEE17216B4740C0C44A53A4F92F401633F3CB3D6A4740B06C572AA0F62F40FAF8C098F1694740F09254C7A6F12F4052CDE7740A694740D092DBB220F22F409222717DC268474050F58E3467F42F405EA339AD79684740A0722294D3F42F407214A6B0916747404098B4F347F12F40AE3925FF97634740802FEAE624F12F407E899DD534634740802081C07CF02F40EE462A1C59614740A0341D20E9F02F4036DDEB111B5F4740305D29CEC8F62F401E4ED346F95B4740F8F095B5D7003040D6BC88C8C65A474048CEAC00BA03304082E548A1CB584740F8E851EE47043040DACCC136D955474000BF55713F043040AAB5465DDB554740907FF1FDEEFE2F403EFEE8F4EC574740A0B5950B53F92F403EB63AAD995847401097893487E42F404ACC953C3C594740C09B8797D0C12F40E66F8D65405C4740D046E58994B32F401ACD9102BC5C4740B05A3A7C58A52F40C2E8C5D2855C4740B022DF63B8912F4072CE725A295B4740605F38DFA1822F407AE533731D5A4740407DE2EC15752F40AE6A5795FB59474040A864D8DF4D2F40FEA6911FFC5A4740B080B5909E452F40C230741AD95B474000914E2A08442F407E0767A6EA59474060AA4AB615442F402A0C50DB45584740C0CF630104412F406EC42FD626574740C0C66A65553E2F408EE38A6FCE564740207EB456B93B2F403A895E6C78564740103257011C352F40A2B334732556474000242B31622D2F40C20B52F885564740100820947B222F403A705A327F56474000012A6B87172F406A32593200564740C0ED1683B20F2F401A0F23E2F8544740B0282994AB0A2F40E273B95E26534740301B09D4650A2F40D2B39B7EE852474060B6CEA107092F40922431D6B1514740E0BF531223072F40D6B7E63DF750474090D119BDBF052F402630F6686E5047409032B05639FC2E4022A0C335244F4740605625CEE0EC2E404ED27AD4AC4E4740C0F5DE154AE12E406637FA05EC4F4740C044EE3DEADE2E406EEF66EF47504740606C6E75CED52E406631F985A8514740B01CA5A8B9C62E40FAF73D4AA2524740B0572CA162AA2E406E3F9CD160524740C0AA336BE7682E404A23198BC9514740B07FC45B59582E4026A88A11185247407041DC1410362E403A3EE6B2BA524740406CEED1E32B2E40D662B8F3EA524740C038EB60B81F2E40AEF039AD24534740A00CA5E23E022E40AA84DE1D8451474080CED11532EF2D40FAF8963CD54C474080B7BB675AE52D407EBD2262444F474020A42F6BFFDD2D40623DE657814F4740C09C5A97C9D62D40922A72DEB84E4740D06FB1B9A2CB2D407685A9CD824D47406020CF96C2C92D40B20342B3774D474040635B640DC12D4016A86826444D474060A9980BDBB92D40FA60BF186B4D4740207C1E5A66B32D40725BFB05F24C474080DCC2CAD3AA2D40D64D2262CD4A4740700899A801A52D404A854E5BA5484740503BC4AC08A12D405A3DF6399246474060FA6CD8479D2D400E16616C9C4447407042D125E4972D403E70F0BA7B42474070C4FA71C1932D405ECE6AC3D9404740E0F3E1ECE5902D40F6449BD96A404740F0F14BF092852D402E4B0040863F4740E0F5805D73782D404ACDE0BA253F4740A0301A5AE6732D40CE9911D1B43F47409013B256616B2D40BA13FEA20A3F4740F038CBA14F682D402A999AD96E3E4740B0E05E64C5652D40F2B13A2D903D4740E01B639ECA5F2D408E72BDFB503C4740903C0EE6235C2D402EE0D346BC3A4740F0B8A7ED57582D400A24516ADA3A4740707DEB6050552D40C29E9D76F23A474020984453FC522D407EBFB85ED73A4740D09AA345B8482D403EB4963CFD3847400049F49A6D432D40521A30D664384740609334083E3E2D40EAFE741A213847408004643B21332D4076BC9F13F5374740D0C169D8272E2D40CEFF79549B374740500D07AC96262D405AAAA193B9354740903D59644D222D403A95606C3F334740B0724EA5CC1F2D409E8E546D24324740006E24318A1D2D4076297EF12431474050561C5AA6142D40CAC26D6077304740B019ED60E00D2D4016C4707DB1314740405FBFCA1B082D40C27F7CD4E233474090AFCAA11F012D40F2DE0FB48C35474040EE980B73EF2C4076A4CA6FD2344740A04E3D7CE0E62C4022A10EB40D354740A0EADA6FD1DF2C403206CAFE9D354740D00934080ED72C409A9549215136474040A2128332D42C40C609AE87E9364740F02FB8908ED22C40DEE6751AA2374740B0452A6B1FD02C403ADE59323C384740609AB11CACCA2C401A77216272384740205613E66BB92C40F6224CBECA374740801A13CA117C2C400A96472A18384740D0079E0BBB4C2C40E6DC41E753384740A08D9BE2BD4B2C4026A4C6D55F38474060431B5A46462C40369AF3CBA1384740408A21F77C292C40A61860ECEB3C47405058B256F9212C40D2E378B7923D474000C324F79C192C40BE9887C8003E4740200F2D089E102C40626080F10A3E474060511DCEA3072C4032D47C90C33D474010CB8ED15DFF2B4072A68AC8813D474010B30DE6F3F62B409620DB80AF3D4740F03D42191FC82B40FEF6004082414740D0F3D878ABB82B404EB20EB4F341474030E3562A70972B402A304367024147408033620174902B40924F4604FF404740401E72AFA36E2B406AD1983C6A424740606C3F19E3662B40220EFB058742474090D1518DE55E2B40CE49BE9841424740809514BD2B572B40926D1C2865424740604E8C9734192B4052871C28DE45474080C77AE974032B40628578B701464740405B64758AFF2A40AAB03ECA7A464740104DBF904AF82A407ABB4DDBE6474740B0DB48F0BEF42A40A68C52F8224847402038C16786D52A40627F7737BE474740D0294E2A24BF2A400EE687C86B4847405077E826F78A2A40B6EEB8DE7F46474060E0B6F353762A409A8E0B97AD464740D0609B6E886B2A407AE1C8EF6C47474000C3653BFD4A2A406A0E9002E04A4740708BADB906212A40C2795F6C8C4C4740B0C27C862BA92940BE5EA293084E47407091D6DB108C294016C614EE4D5147405081B5533B7F2940FE7A84280C524740404D38A5D07A29406279929F4D52474040734319CB76294082A0ACEA3552474050CE875D976E29406631F985A8514740507C5F9EC6692940EA9501C0A151474060C626F710652940F6AFA430FD514740609A82C0C05B29409ED0AAEA3353474080AF2880BD5729402AEC84FF83534740404373E9D4562940B2D1492196534740E078FC9A893D29404267559507544740A0A6F8D4BE1F294046AF03DD5A534740E00B92343B182940AECC62097B53474030DA14BDC30F2940AE6BB0872E54474060950B49F1FF2840DE76D2A908564740206662A173F02840AEECC3938056474050C70A4929E428402AEE79B7DF564740D0A8FE715DCF2840D29A7FF155584740F064D94FA3BD28408EF74621075B4740406CA645F4B328402E4DA4B0225F474060922ACE74AF2840B65D11D1EF6147406075C2CAEFA628408E05DF1DE162474060E678E94C9C284046409D7621634740C0D43B7C9C912840B63C68A6D6634740B01435A5B08C2840EEB0CC0C6F644740802A2094C7892840EE714DBEEF6447408035590190882840B684EC91CD65474080D14819AB882840EAB141E77467474040B7E14F0B8A28407AC2C335F0684740501FF5FDF28B28400E39DF1DD3694740708781C0608D2840D22BEF2EC06A4740F096AF1C388D28400E3B5932536C4740E0B2657582882840D29C077A1F6F4740201391D1698028404E25D2291470474040FF4453E06A284062F1EF2E35704740804F502AE4632840B6ACFDA2A670474070F275E9C46028401EE25BCF45714740800D48F0F65D2840F28BCD8C1372474070D026CE24582840627DC552187347406098ECC36D5228407A0C087A9673474000A116205D4628402A07A84DF5734740404A805DF74028404A5FC5D2557447403013F8FD7A4828406A34FDA29C7547409095EB898848284076044E5BC8764740402475E9FC442840329C973CFE7747401054D578A74128407E7B67266B794740F05FF237883E2840A26A2D395F7C4740A0163B424F3C284056173373D57D4740E02C9F45EC3828404269EB911A7F4740D0EC2C08523E2840FAD2299C58814740201DEF60545D2840D670AE874D84474000A2F1FD3A6828401EAB6589D38647402004A72F6568284032734CD99388474020B1A6458C6828403E1DFC22338A4740C022C8A17B5C2840EEF3C452E88A4740406DF0C02B442840AED47027178A4740E09E070F843B284096B2A3B0CC89474080644E53A8072840FA894CBE2E85474040D80E2025E32740EA4F1A8BE2844740E0EDDEB28CCC2740D29A717D8C834740D066CD04CDB62740C62D77B789814740800E936E20A52740B2AD6409237F4740B09C0172E58D2740D671077A807E47403050B2F33B8827406E3CA94DE17D4740D0C5D6152E7E27402E9B5378797C4740D083CEDB4078274006CA4E5B3D7C474050F979E9AC6E27403A25AF87DC7C4740102828CE1C5E2740326B8F02F77E4740D015F93760542740B2AD6409237F474060C3DE4F8350274046897A540B7F4740C070C467A64C27404269EB911A7F474060806BD8034C2740B2AD6409237F4740D0F3A91CC04B2740B2AD6409237F4740F0243EDF65312740421DF5680E804740A02881233A252740B613E974DC7F4740104C7D865B162740DA49E8F41D7F4740B0C989FA4D162740DA49E8F41D7F474080C4A2E2321627404269EB911A7F47402042AF56251627404269EB911A7F474000878F9754112740B2D3DF1DA97E4740D03467D8830C27404ACF842B827E4740A06A19BDDB0727407E50606CA77E4740C09A6B75920327404269EB911A7F4740C0BA2494DF0027406AC44BBEB97F4740404E1CF7E8FD2640264D8F8234804740107E7C8693FA26406E7AF9058A80474050618D9794F1264026ECDC00E88047401097B89072EC2640BA394F5BB4804740A03A498DE5E726403A74A9CD1C804740A0FC4319FBE32640DA49E8F41D7F4740107A508DEDE326404269EB911A7F47408045E0B284D22640B23A931F397C4740005AA0E2DAC22640FA8930D65B7C474030A7B856EDB22640E2BB5932B07D474010E118BDABA0264046D1289C5E7E474010734319CB7C26407282B324587D4740608201D55659264046B7AF875F7B474020DE37422F5026402ABEA0136F7A4740B0D29C0B0F2F26400221D2A9CA74474080EED94FD32A2640CA1168A63773474090C5548D6D252640AAD43156B76E47407021040FCC1B2640DE696943C56A474030A470AF5F1B264046AFE7F4476A47400012D6B2F41B26405E7DBE98F3684740102CC4670E1B2640DE09E6574F684740F0B4FB71D518264082BE9082E567474070FFB4F32B132640EAB141E7746747405024DC150E112640C65299D91C674740C09A6B7592052640E2BA0040BD63474080639CA86DFE2540267C0F3472624740B03BFB3720F72540FA8A7B54456247402097B89072EE2540C251392DC562474040E5B4569DDC254016862E3912634740803B8B97D4D32540B2A16209DC624740E07EC704BDC225408E716409DE6147406037E689A8BD25406A0E741ACD61474020682A6B6BB8254096DDD4C62C62474000D8FC37B0AF2540D2BE482176634740F0F899A815AB2540B235DD1DDF634740C0C0E689D8A525404A16DA80E263474080AEB7F31B9C2540563C55956863474060E9C90415972540C680CE0C71634740A0FD970B5F882540F291DC80E0644740A05298D171822540EAF6C1B537654740D03B744CA6772540AEB49BD9A1644740E076FF71257225409AAD1051AA644740409C18BD136F25405AF53056C96547402004B35675712540BA5CCD0C6B66474070F133DFD57C2540D69D759A00684740805EB590527F25406A149182E3684740D01BBB2D597A25401A53828E2E6A474030C930A5305325407E07751A346E4740609C21314E4B25404A471A8B8F6E4740209447B641422540D6044584636E4740C04FC0CA2F0E2540CA2D7E71EE6B474020B42094F7F824402E4795BC556C474060F7E3EC59E82440CEC2828EA56E474020021D5A22E7244032E742E760714740602AB0F37BED2440E61F1E45BA754740105F7E86BBEA2440E2BCB224E3774740A0020F832EE62440CE3242E7D1784740E0CD9EA8C5D42440EEB9DF1D307B474040BC613B15CA2440665165A6217E4740706AB290CAC424404269EB911A7F4740706AB290CAC424400ECD67A61F7F4740E07EC704BDC424400ECD67A61F7F4740109F80230AC22440E2B558B26C7F474090375F9E2EBF24404663FF3F857F4740A0E939A560BC2440E2B558B26C7F4740400D360882AD2440E6B71151F77D4740601B39A598A02440429720C56E7B474040EA3442A7972440723716EE7E78474080F47B8663972440CA9D3DCA1A76474050F350C7A28A2440326D178B00764740206E5C01B08024402ED7E3D772764740405BC22D6178244092E12A1C2F764740909C91D1997024401EC92D39F073474050B203AC2A6E24408EBA4F5B517247400035704CBE6D24406EC3E4573D714740404FD7155E6C24402A4EE1BA42704740101724F720672440CA2EF34BF46E474050F49B45CC50244032762A9C016D4740508AF29A914324402E2E91825C6C4740E074B993184024409E332258626C4740C014A545FC382440521A3E4A6E6C4740F0FBEAC3DD222440760F05DDA56D474000F086975417244006445ECFCA6E474090537C238A03244006AC5C4F0472474010C6B656C5CC2340BA5B90020B75474080477F2312C02340A269E2BAB5764740A00009E68FB923404ED6C96F4A784740209DEA26B7B823405216D3C67D794740700F45195BBA2340FA1B31D6DE7A474000F0F637A0BB23403AB9299CDF7D47403062D815BEBD2340CE0B6EE0187F47403062D815BEBD23409607ADEAD97F47409077D1DBC8BB2340C29D44843F8047402098030F9CB82340264D8F823480474060A32EA570B623401AEB3DCA85804740C0345E6449B723400AE9842BFB814740D0D8D7158E56234052D144843187474030397C86FB4D2340D6C14C3E6A874740B06A895D2733234066C3DD9DD8864740F0482C6B932923408E72AF87478747402030F060B437234002415AB2568A4740700A5E01403B23408ED97E71AA8D4740A0BD956E1036234072E3731AEE90474040C1D8B2E42923401EC789C8D393474040EA3442A71A23406A34FDA25C954740B06741B6991A23403A4ADF1D7996474050AF2231AE1F234056F06CE06598474090319DA8352023402A28828E4F994740E05C0849D11B23401EC47737169B4740601FF5FDF2162340A688188B339C4740A025397CAC1423401E7FF768539D4740D091C8041D18234072786AC31B9F4740D06F79E97C0F2340521A4CBE77A04740600E23CED40A23406AEA9C76A3A14740807196D1491B2340D67B579561A64740D028FA37C02C23402A4F3AADF5A9474040130172B52E23404E68D8E3D6AA4740A07054C75A312340EA8C115118AD474080D8EE60BC3323403270291C3EAE474050F7EC6094472340FECF1EC57FB2474010D1EFC38D4C23409EFF2A9C71B44740504E8323FA4C2340BA80B224DEB9474020E544B6513E2340F667FDA20EBC4740E03D7AE9442B2340121B024088BD474000D5B490221C23409E6D1C2865C14740003CE27E2A1B2340E625D07E29C24740B0FA99844F18234082A1180A6CC4474000FA54C78A1923407E2B30D64AC44740603D33BEB81B234016751FED30C44740801AF79AA9392340D20E72FDCCC24740E0ADE589785A234036BC188BE5C24740400DCFDB70682340EA8CF56805C44740303A5764D96F23406E77FCA2FAC5474080F9F2FD327623406E82A5B04EC84740F073AE565D81234012C76DE080CA4740B0A036DFC58823409E2142E72BCB4740405A802372902340127D1B2851CB4740A0963608B297234082E70FB4DFCA474040C429CEAC9D2340262FABEAC4C9474020B9774C5E9F2340E6FFB12441C84740403776E95C9E23400A80035DB2C64740B0B8852352A023405261A11358C54740B0A6F8D4BEAA23407E298C656EC44740E085D478DFAE234046867DF17BC44740C0C27C862BB52340AE44CE0C2CC54740A034E54FC3B72340CADEAD874AC5474030FC5A6429C723402E4AA74D53C4474060D3FED466D62340CA0E5DCF1EC44740407833A520DE2340B6CC9AD960C4474000B0F49A51E42340361E40E737C547405018815DBFE523405A1D65091AC34740C0EC239417EB2340128C1A8BE7C14740A097AAB97EF12340BA1130D6D1C0474000F900D526F6234056F82DB918BF474000F900D526F623408648D0A9B5BE474090E9D2784FF62340CA0FB6C151BE4740806106AC1AF723402A97F66892BD4740702B629EB6FC23408ABE89C800BD47405005DE15360124405617337355BD4740C0CA4419C305244016F5862B02BE4740C0705D3B950B2440CA0D125175BE47400064FE71C524244016F6DF1D35B8474000A771E944292440AE81E1BAB4B6474030D6DF4FE326244076C0FC225CB54740105C9D0B3F1B244072A4CA6FD2B34740F08EF0FDDA20244056B4EAF4B5B24740C00B92343B222440AE5CBF9861B14740A00B1920B52224403251246201B0474000D74A8D75252440DED8DD1DC8AE4740801945F06E2A244076C5B224F6AD4740E085D478DF2D2440F260AAEAFCAD4740E05AE2ECC931244016F0EC117BAE474010151E5A823824403EB61EC506AF4740506D8A970C3B2440BAAA6E60F8AE474020BF394257402440D2BE3AAD6CAE4740801C8D97FC4224401A107CD46BAE474040116201284524408E69B1A4B4AE4740D0AC33DF3D4B244066EF4BBED8AF474040C08D34BB6124402E0B1A8B8AB04740305A1083266B24406A813790ADAF474090F678AFC76A2440FA88C96F5FAD4740204DC8048561244076A09002A3A84740C028930BAF6224403629E9F4CBA7474080B554C7F26224408A72AF8707A74740B0D4A2A8AD612440A61A0B172DA64740006821F7305F2440A220F768C2A54740402BF2FD6A5824406EC14E5B6AA54740B03B04AC5A562440D292CC8C2CA54740B085CB04A55124407E75828EBAA3474020FFD4B2944F24405E7ACFA9EDA24740D0F89034DB512440E6000B17B4A24740E0506628935924407E54C033DDA24740D0B88E978C7A244096D52FD68CA3474040334AF0B68524403E1CB1A449A44740C02C2FA5A09C2440F2B589C8ADA6474080CB1FF7A0A62440168779B73BA84740605BD415D6AF24400ECD593256AA4740F0DF5D9E36BB24401A32AF0739AE4740A0514D5348BE24403EB61EC506AF4740700CF4FD92C324401682C3B5A1AF4740F08B0F835ECE2440DE46DD1D45B04740E060A4E2C2D22440DE075ECFC5B04740E07D0CE647DB244042FF2CB9B1B24740402D88FABDE22440DE76D2A948B54740D0F7D51566E72440FED0694329B84740B0F2EEFD4AE72440F6D526FFE7BA4740801716202DE524401E17CF8C7DBC474020B5DBB26CE224408E966A439EBD474040E0465308DE2440262D15EE71BE4740A0BD8CFAD5D62440FAD82A1C1CBF474040734C8D05DC24406A0D458436C4474080FAAD1CA8DC24401ABE929F65C54740E010122045D924409A20E23AD4C6474070B98934D3D424400245B7C13DC84740105B528D15D42440EAB1178B58C94740C07D93D1C1DB2440167FD4C6DBC94740301DEF6054E42440BEEBD0A91ECA474030F2FCD43EE824406258CD8C61CA474040EC316B0BEA24408ED8099724CA4740C0B7DCEC51EC2440B21B80F1F2C8474090C7835DAFEB2440AE5AFF3F72C84740B05A02AC32E924406EA820C5D4C74740B0F2EEFD4AE72440AACC707D04C74740A06F094931E8244072978B65EBC54740904F502AE4EA2440BA80A4B054C5474050B11F5A12EF24407AE525FFD3C44740204C0DE60FF72440428AC5D234C4474090551220DD0C2540CA34D8E3A4C34740C09C0172E5122540AED42A9CD2C34740904C6FAF67192540464B4684B5C4474010E6FFD4C623254066798BE528C74740F09ADB15DE2A2540EE41741AFFC74740700A5E0140372540AE807A54F8C74740801B4219D37A2540CA34D8E3A4C34740400B306BE37C25409AF5BE983DC34740206975E9947E25404E1E6A4394C2474060C1E1261F812540AEA3067AF8C147404096768683852540720440E7BEC1474020FF3BDFA5942540EA44717D0EC2474020966D1249A92540AEEFD246A8C34740503B1BF788B0254046ACF80502C447403087FF37A0B725407ADF4EDBECC347406032788613BD25402E06561567C34740A048AAE2D2C82540128C1A8BE7C14740F078FC9A89C425401E4D7A5406C14740102A9E0B07B425406E36A8CD1DBF474000EB0F2085B72540F2CD6CE019BE4740E050845DDFD12540AEA603DD07BC4740003FC5676EEB254096FBB85E5CB7474060AC3842F7EC25404E424F5BC7B64740E09C0AE61FED2540F68C2D39EBB54740402A37DFF5EB25406216125108B54740F04EE5EC51EA25409EFF2A9C71B44740B0591E5A1AE92540021F2E396EB44740F091D17857EE2540FAADEB91B2B2474060C9A0457CF525404AD72262FDB14740F070CDDBE02A26407E5C4684DBB14740B00FBE2DE13426400E87807160B24740409305AC52562640061789484EB64740301C0B0F3C632640AA5FAE87E7B64740708CF837306E26407206F2CB24B64740309C18BD136D26403E8F741AAAB24740E006B4905A7926405E0FB1246DB24740201878E984842640E64752F84AB347402040195AD28B2640CE797B549FB44740B07C485398922640BEC7EB912BB64740507F498D7D9C2640121C5B32BBB74740701EB3F303A626402A72D4463FB84740303C548D3DBC26402AC2FD2256B84740E04EEE608CC426404268A013F1B84740C02383C0F0C826402ABA583236BA474060397C86FBC62640226E62092ABB4740D0F113206DC12640AE1A045DC8BB474020B56B1221BB26404AF7B8DE12BC4740A0A3175A42D32640CA6F39ADC7C04740F0B4629EE6DE2640F68EC3353EC147409097AAB97EEB26404AB20EB4F3C047401026027215F726401657AE8754C04740E00FC7A11B0F2740D634024001C14740B09E976E381A2740A687BF98C0C14740803333A588232740BA367C5481C34740800E936E202627406AE7C96FB0C4474080E887FA252A27407A08CE0CA7C747407040020FA42D2740AEFBF02E02C9474000AFA0A8ED3B274032E453F81ACB4740708FD9B2AC3D2740E2DEF3CB79CB474000AC4F8D25472740665C00406CCB4740B037CF3E7A5D27405286C335ABCA4740C0E2C5042D8727405A160B97A3CA4740701EB3F303A42740968ECC0CA3C947404070629E4EA9274066EE0040EFC94740503A37A570AE2740FEF44E5B1CCC4740F05D5C3B35B4274042AEB85EB1CC4740701716202DDF2740D2BE3AAD2CCE47404000875DCF542840528699D94ECD4740D0C56675E2582840C258B64173CD4740A0ACB1567D67284042B5045D9ED04740E091D17857692840FE19717D6FD1474050144CF0DE69284052ABBBFBA1D24740D0192531066928404E68CA6F0DD64740A0B1986E986728404290D4C6C1D64740A0D638A500652840A61B48210DD74740E0746975D2602840E294939FC0D74740902736A5105D2840220C3BAD97D8474010B5DBB26C5B2840D2E203DD4CD9474030CAE24F6B5A28408295F56858DA4740F069B7F3836228404AB367A6E6DA4740D0564653D86E28409EFEA74DA2DC474020AECE3E4A7328402E7BAF871ADD4740B05D4A53C07A28407EC460ECA7DD4740403EFC71057C28401A1516EEB2DD474090E59D0B6F7A28404A87EB111DDD474060EBEF601C792840FA878C65BFDB474040F1BACA4F7728400AF41151BCDA4740B0A30EE6077428409E17D6E3F7D9474090AEC0675672284096433D4A13D94740C093EE6024752840EE446AC3A9D7474070DBCFDB387A2840BE62F12EE6D64740C0223842C77F28407A0F0C970AD74740B029B556358B2840F2CEC5D20CD8474020A470AF5F962840926D004052D84740406CA645F4B32840CA7C949F41D74740D079E0ECA1BC2840EAD655957ED74740908E97A8BDD028409247852BCCD84740703D219427D928404271CF0C85D8474020A5BB2D89DB2840DA044C3EC8D7474080E25564E1E02840F2B44CBE0DD5474080C7835DAFE32840CA2D9A5901D44740A0A65F01D0E728404265B1242BD34740A0E6FA710DF72840FE3D482119D1474080A0BDCA3FFE284056F522E27FD04740802A2094C708294002F6AE0774D0474050414419931329400E105278CFD0474010DC31A5901B2940BE5877B768D14740E0FB68AD7220294042FE8F3C25D2474090D02F425F322940C2530040D9D44740B07967D81B3C29406AEAAAEAACD54740D0A8FE715D4E29408A05ED916AD64740E0D7950B9F6029408A05ED916AD6474070A5A4E25A7D2940B6F1AE872AD5474030B8C5A123862940824CFCA25BD5474060FD1EF7D8802940AE8B3F4A1FD34740A0B1080FE4882940D67DED9174D1474030079D452C96294086476943F9CF4740902D683B55A02940D2BC963C50CE4740203776E95C922940C6EC458424CD4740007DC1671E8C2940FE1C830E2DCA4740D05C0849D18E2940AACC707D04C7474000594CF0769B2940CADEAD874AC54740A0EE526459A329402AE269430FC54740C0407B232AA82940422BB7C1C4C44740E0921CF780AC2940BAEECD0C2EC44740408C8897E4C32940AAAFF34BD1BF4740807A42B6F9DC294046B1AE079CBC4740A0449EA895E229403A2753F838BC4740E0963F7CECE629407A36D2297ABC474000E4F93728EB2940326FEC11DEBC47403021946E80F029407E30E6D7E4BC474080E19A456CF5294076C6198B72BC4740E048BCCA47F829400EA81B28F0BB474080288A9774FB2940364F727D9BBB474070EDFE71F5002A40D29F277FA6BB474000C86C1281042A40367920452CBC47409017AFF31B132A40FAD82A1C1CBF47401027DD4FF3122A40B6C996BC2CC04740D02C2FA5A0112A404AB20EB4F3C0474080BAD4B2FC0F2A40322CE6579BC14740E0DE09ACD20E2A4072A531D64EC24740A061764C660E2A405A1D65091AC34740A061764C660E2A40BA80A4B054C5474040D9306BAB0F2A4002AE0E34AAC54740C01644B689142A4002843610BDC74740A01BB2B91E152A401AA7087AECC74740902BD23E02142A4086E69AD9D9C74740401736DF95132A409A272E39C1CA47401094D715F6142A405286C335ABCA474060BB865D371D2A40C62D694380CC4740703333A5881D2A40D6BE4FDBDACC474040660D839E232A40328EFF3F64CF4740E0DDC7A1E3242A4006B0B95EABCF474020CEA71C00262A40F2FD299C37D1474040DD5C6451262A400ACF198BC5D24740E0DDC7A1E3242A40EEF56F7D69D447405018E889D0202A405684115145D647408079F737D0162A40CEE4A74D29D94740C03C4F2A84102A401AC61BA872DA47408014BC902A0A2A409A1E4C3E41DB47401065B9903A022A40F68BD44678DB4740C0F0C8A143F5294016A68CE581DA474050CE875D97ED294086B5842B49DA4740E0733EB611D12940CA83E09D2EDB474060DEB056B5C829407AC02D399DDC474050E611BD3BD2294062842D3918DF4740808201D556D22940463F289C1BDF474020D0ADB99ED5294026773D4A05E04740404DC80485D629408A716409DEE04740A0D4A2A8ADD62940FE848F02B0E14740E0BF9B0BAFD72940C2603F4A80E24740A0A4C9047DDA29408E713AAD81E3474020AB7D2382FB29406AF3BDFB6DEC47407043E38920F92940CA0CD546D5EC47402037064911EE29408230097A9CEF4740F08489FAB5DC29409221347362F64740C0623ADF15D4294026524584CEF74740A01A673BF5CF29409AFBA3302EF84740B003CAA1A3C529402278963CF8F94740600226CE5CB9294052ABD7E334FB474010BA522A3CB529400AEE10D138FC4740B05718BD7BB22940CE5178B78FFD474060CCD1554DB229402AAD8DEBBEFD474050EAA4E2F2B029406680E5571FFF4740E0C7639E46A9294022C204DDFA01484050D2B3563D8529407A9AA4B04D084840B0027F237A802940F28EDF1D91094840503D1820ED7B2940BA59D0A91B0B484070CBAF56557929406ECB9E76CB0C4840904DB1B9567A294082935F6C850E4840A0AA8BFA757D2940964EF4CB700F484070F837DFBD802940A6FA820EA10F4840A05FE04F138429405A3989489A0F4840E0BCBA90328729407A934A3ED70F4840B0E45B01808E294022539E76C111484010D44953909029406AD031D62D1248406075C2CAEFA42940969549219114484020AD0AAC9AAB29405AA2FCA2D915484050C4996EF8B429409A85FF3F51184840F08EE789A0B9294082DDA3B02B19484000341C5A5AC12940924AB3A4DC194840E06F825DB7DD29409E8E04DDC81A484010FD354207DD2940EE4BB6C1161B484050AC3842F7E42940DABFA113291C4840E0638ED179F529409E01D6C6B21D484000A2619E86082A40A6FBBF18C120484080C910E6C7102A40921F828EBC214840F0F89034DB452A40B638F6684125484080DF74E9648C2A409ED0A3304F27484020382894979D2A40D24FC6D2E92848408078ACB9A6CF2A4086E26EE033304840C0AF7B86CBD62A40829AAB6A32324840D0028897B4D92A40C209AE87E9344840F08489FAB5DA2A401AE102DD92354840406A30080ADD2A4036DE59323C364840C0CCE38950DF2A40A6D1F5681D37484070D1D8785FE02A4006D8DF9D72384840F0D1CAA16BDF2A40829EFA05103C484070D1D8785FE02A405AD2C7D2403D4840B0245E9ECEE22A40EE76D9636D3E4840406674AFAFE92A40860927FFD9404840A0CE00721DEB2A40DE48A4301942484060DE20F700EA2A40623F830E39434840000FECC33DE52A40169E03DD74444840601FFE712DE32A403E4530D6C346484010B28A97A4E12A401EA11C2857474840F039DE4F53E12A407A9AA4B0CD474840F01E0C4921E42A40D6BF7E71B1484840C06666D8BBE82A40869647A166494840B0BF929774F92A406E9A7A54714A4840C06741B6990A2B40869B198BD34A48403097C104AD3F2B40B615949F5D48484080C8C5679E482B40A61AE1BA9047484060DB5F3BED502B40C2CB6B438A46484070857B23C2582B405A3F6EE08A44484020A15764BD6D2B40DADEC2B5F8424840609137DFD96E2B4096AFB4C1C6424840A0EDA6E266732B40E2BD045D31464840204048B6BD772B40D2BB4BBEA6474840602081C07C792B40727E5DCF9547484090AFCAA11F842B40E6D7929FDE4748402075AAB932882B4092B447212948484080596D756E8C2B40167FD4C6DB484840C0ACF29ADD8E2B40EE4A79B77649484050561C5AA6972B40C6C6CA6F9E4C4840F09EE126D39A2B4052B5198B4C4E4840E025518D699C2B405E3DBCFB245048409020F160C89C2B406AA812514B5248409040AA7F159A2B40869967A66D564840C0A34F2A689B2B409E835BCFF4574840E0CB69AF3BA22B40CA7AC6D2085948401021E389D49B2B407E73C2358B594840A0C8764C4A992B4006D2DE1DEF5948404086F5FDD6932B406242959FB65B4840E028A2A831912B408AE0BCFB8D5B4840809B75AF5B922B40EAAF818EC55C4840105E04ACA6A12B401E5823621A62484080094B533CB62B403E200EB430614840901B7AE9F8BF2B40DA2C727D4F60484090B0152049C92B405E18A84D1B5F484060ADC40481D42B40964DB7C1905D484010B30DE6F3F62B4012EADD1D6E5A4840E08C895D73FB2B4046B7AF879F594840F0A9F160F8032C40926B5CCF75574840C0F79D4540072C40AE35EB916856484090D2FD0ED8092C404E1CD44601564840E04374AF630D2C402EC21251C4554840901E5B6475102C4056826DE068554840C081000FC8112C4012EB3610A15448404019FB37D4102C40F2EF832BD4534840F033542A800E2C40CA55A4B0B553484020C7D278030C2C40D2738F02CA534840F0595F9E7A0A2C407A76BF989A534840A084D8150A072C4022E5741A68524840709AC30421042C403E4522623A524840209BB52D2D032C408E4A828EDB514840D00250C78E052C40A21E45841C504840007A18BDC7072C4062A5F905694F4840200F2D089E102C40625BD1A9954D484000DFDA4FE7142C40921F741AF34C484090AA532A50262C4066D32E39BD4B484060FE31A5DC6E2C40A63D89C8634A484040CBFE71A9A12C40B2618AC869474840608B054995A62C40AA3A707D8147484060FC02D59AAA2C40CA7B2D39C5474840507C976EECB42C404EFE0B9724494840E040BC678ACF2C409AB7606C0B4B4840A077990BA3D72C408297BC7B6C4C484030010AAC1EE32C40366FE55779514840C0197DC094EA2C401EC789C853524840101B2194DBF62C407AA4F4CBEE4F4840E03E8D97480B2D408291BBFB284E484010EF73E950112D403AB47A54EA4D48409011B52DFD182D407233B224334E4840C0ED3FB655282D40FA85F6686C4F4840601A4F2A38302D404E65DB80874F484000FF030F80332D4092DC97BC0C4F484060EF5C9E22342D402252299C3B4E48400009592A30342D403EFA842B614D484050E4AA1CD4352D409691D029D74C4840F03BAC1CCC392D40F6D06289C44C4840F0E5C704A1412D4026BEAE87384D4840604D7075F6442D40824F0E34194D48402012E54F774A2D404A8C852BE44B484060D3CF787B4D2D40C277BBFBAF4A484060ADC40481512D40FED4D4C6D9494840705C4EF0EA592D408EB0EA11C249484080E5DC28DD612D40E205E293134B4840C059EFFD2E642D409EB1741A764B4840C0344FC7C6662D40EE6F9BD9C94E4840103F960B83662D408670E874B352484080BBBE2D5D682D40F6FA2C3928564840004BFA370C722D40BA7E0EB4C1584840E01ED478FB8C2D40CE531C28AC5C4840B0903C42938F2D40F6F8A4B01E5F4840F0C30849B5942D409A47A1139F60484050B3F69AC5962D40B21891022D624840A00BEAC3C9992D401EE8559564634840B047279483A12D40BAA2D7E3E1634840401133A53CBC2D403628741A46634840207C8EFAB1D62D40E6FCC2357B614840907E162011E12D40C25E9BD9A3614840403E2BCEF0E62D40BAA2D7E3E163484020B74253D4E52D40769F1B281D644840A0EB97D18DE32D400E36C6D2F06448405074CFDB54E12D400ECA40E7F365484060F2CD7853E02D408E22717DC2664840C083FD372CE12D4096F60917A7674840A04E3D7CE0E42D40B6A76A4344694840F0504C8DB9EF2D40324F56954871484060D33F19C7EF2D4062A64484D274484030F8663BA9ED2D40727B52F8BC784840506B2C6BDFED2D40B6D727FF617C484040B22994DBF42D40D6916CE0147F484060B2A2A861F42D40A2AD3AADC67F484060B2A2A861F42D409A47A1135F8048405093CDDB2CF52D403A22B224CD804840A00528CED0F62D40DEE07B54038148408099DFB208022E40FE3A3D4A4081484080DA2CCE801E2E40120D5CCFA47F484020A0037259462E40165366A61B7F484090ED36421B4A2E403A47B85E4D7D4840204B03AC464C2E40B278F805907B484000D83D7C104C2E40FACF3AADD279484060EEA17FAD482E4006861251FF774840C0146D75D6522E40964199D9F6774840306210E6E3792E40DE8CD980B2794840A0BF633B897C2E40A2AE939FF979484080743F7CA0832E405A7B289C607B484060698DFA51852E401A597C540D7C484000E1471997862E40CAC3BF98C57D484090392D6BA7882E40E2C2BA5E4B7E4840900E3BDF918C2E40562038104D7E4840A080A3A8298F2E40CAC3BF98C57D4840C05B7C8647912E40AEAD6409237D484000B4E8C3D1932E4006D1AF87D87C484000B2C267CAAB2E40BAD13BADCC7C48402022377C38B72E40BE174684437C4840C0A772AFD3CF2E40EA4D5A32337A4840B0B085C094E62E4066591FC5EF78484060A3667596F12E403604ABEAE5774840D09BEEC3E10A2F4066EC4E5B49744840D0A0FBA29F162F404E1C01C888734840103257011C352F404E8988C89471484080894F8DD95C2F4006F6C335E26D4840107CB72D55682F408EB736106F6D4840B07DD41522742F408EB736106F6D4840C09F2331C27C2F4006F6C335E26D484070E3CF3EF68E2F4016C0289C786F4840B0A34F2A68932F40FE5F7B54A66F484000F886FA11A32F406EBDF805A86F4840B0CD869708A62F40BEC25832496F484020547DE918A82F4052D9F02EB66D484080ACE926A3AA2F406A5F198B0E6D48408004643B21AE2F401682D8E3CF6C48402099865DEBB72F402EC2F668B16C484000CCD94F87BD2F40220AA5B0046C4840109C00AC56C12F40EA8A6DE0BB6B4840D06BAEF39FC52F404E8AE1BAC76B48409033FBD462C02F40BE5588C8A26A484000617586D7C62F4046B64821E36A484080B2AB1C9CCC2F40223A54F8D86A48409082D2786BD02F4082B865A6456A4840D0044DF0F2D02F40426EC435EC68484060A98F97A0D92F40F26864094B6948406089D67853DC2F405ECE717DBE6848406089D67853DC2F40CA575695DB674840C07E162011DD2F40EA1BF9053967484020DF1704470830402EE437100861484010CE6BBCD915304086B42B39165F484008BD9018812D3040123133738E5F4840C0327D6A4D513040B6A778B7CD5D4840103534EC975130403A23E1BAE35D484028B1D45CE95630405A649E76275E4840584F6C594C5A30407A2C89C83D5D48403803FD1BBA5B3040D24D30D6165D4840D091EDA7BF5F30409A864ABE7A5D4840C8761BA18D623040E6E4ED91585E4840D86001568B6F3040468F828EB365484038B88941FD7330408A04862BAE664840E82318045F7B3040BEC4E0BA5266484050816B59047E3040628CD9E35C664840E8D5C3AEA5823040168331D602674840807C6482008530409A3C14EE1D67484080D700565B8730403A215932DA6648408036A0EFB88B3040822E4921ED654840984433EC039B3040DAD8C1357564484058076A59C09F30404201F4CB45644840B0517B6AA5A430405A871C289E634840300A7A30A8A63040D296299C1362484030BE83079CA730403A6C04DD3C604840C0A3A32976A93040063D0B17B95E4840B8804160D2AC3040F67FD246F15D4840E8912578E5B03040D6251151B45D484078EF580EA2BA304072C6EF2ED65D4840187962828CBE3040220BE2BA645D484000D70E2DCFC73040FE5F6DE05C5B484078D84BD484CC3040AAA603DDC75A484000E456AB70D13040A684D0A9FA5A484040FB6C5948DB3040DA9926FF225C4840B0D2314F7DDF30408ABA41E7075C4840A88F16678CE53040EEF4168B36594840801E800718E930404AD72262BD504840C04A9D52EEF13040EAFA1051554D4840
 HR	0106000020E6100000190000000103000000010000004B05000020027ACDEA8330404641CC0C384047406838FE1B668530401E79F6E8CF3F474040F9D65C7590304062115C4F6E3D4740D858681FDF933040FAB14F5B3E3C47406087D5BF6E953040FAB14F5B3E3C47407077B53A8B9630400E12E874623D474058671CA121983040EA6F7837D23D4740D8881267249A3040F6AF96BCB33D474058F8C3AE719C304082919859313D4740A0AAE86D3EA33040926CD1A9BB3C4740D830570EC6A83040521BA5B06A3B474040FBA4296EAD3040866FA4B06E394740008F7CCD8EB130408E4D9BD9FD3647409095A08C5FB6304076A4CA6FD2344740687EC2AE2DBE304012C2B7DE2633474090DB9CEF4CC13040160EC335E13047407007DAF90BC33040160EC335E1304740786FEDA7F3C430405EA92CB9B332474020DF1704C7C63040025ED7E349324740889623DBAECD304012A4E1BAC0314740E847FD1BD2CE304056870EB49431474040F9D65CF5CF3040464DDC80483147402028BD110BD13040BEEEBF98E4304740B063A18C27D23040A2D6C09865304740180DEB0AD9D330408AD7C5D2DF2F4740E07F372689D430407E03180B4D30474068C3AB6317D530404A9142E7E230474068334FD470D63040160EC335E1304740A0CCFFB8B8D93040E668257FC02F474088E8B15602DC30406A0E1D63B02E474078A924DB8EDD3040926FB224F82D47407009A8C604E03040A6674CBEE22B4740F80E81072CDF3040CA7DD1A9E129474030B34A951AE13040126976A5F127474020DCFEB8A4E330400AD056956525474018083CC363E5304046D4178BA4244740103960E53DE730403EDCA74D16244740100FB9D7D1F03040F66A16EE3020474060005DE51DF73040CA723610171C4740C0EAEA0A8DF930407EC15CCFF31A4740285C324F2D0731408E4F3F4A1A17474050370B2D4B0931406218B6C124164740F0AEC54B900A3140B2899BD9C21447403891C3AE8D0D3140A2F039ADA4134740803F4CD4E8193140C256FDA268104740401C71F6BE1D31404E3AA3B0C20F4740807983078432314092FFE457820F4740C0569A52663531400EC3FBA2EB0E474058DE459AA33F3140C27B2D39450A4740F0D0DC960A4131404667630911094740E071A4293E443140DA94852BF704474068EC07F3D9443140E20453F8AC03474090D93EC39F4631406E7AF9058A014740F0A635899E4A314016A100400401474030BD00B9CC4E314026C33373D100474018E1AD001A513140D64FD446B3FF464030799A52324F314046AFF56891FF4640F0E4D085054C314092B6F9050FFF4640906D8F7B464A3140EED7A013E8FE4640906D8F7B464A3140FE41828E08FE46408806C74B084E3140D222F80592FD4640605B1A347E4E31409E1DDBD26DFD464020AABE740C5131401A30E8F4A4FC4640008A0556BF53314092B623626BFC4640F8A03BC37F563140FE41828E08FE464040D7BF11FB573140BE5A3710D8FB4640581B267895573140A2835BCFB4FA46408054537167583140822B3E4A54FA4640E0112AB2825D31404EA61A2876FA4640705102569F6331401282CA6F46FB4640D07C4D3752663140E242B124DFFA4640E8649DEFFC673140023B4BBEC9F8464038963A265D693140E6B86A432AF94640800B3EC3576A3140124F02DD8FF94640983F84A40E6B31404ECC953CFCF94640C0E8540E3A6D31404EA61A2876FA4640F8FE570E0E833140468C77B71AFA46403801671FE78D3140CE71C13551F94640A84FE56D52973140028741E7D5F746402015B39D70A33140561741E75EF34640E0BBCC8551A5314036227A5427F2464030BE83079CA53140BAF2077A1DF14640E06C2DB2EEA63140F264F2CB35EF4640C823E03339A7314016356DE0BDED46407051836998A73140E2C9A00D12ED4640401E07F311A83140322577B736EC46409883EA0A29AA3140BEED741ABBEB464060FF1167F4AF3140EE72983C99EB4640302DA42926CF314056F377B73EE84640581B267895D6314036465F6C5AE64640B8A10D2DA3DB31401207707D4FE34640E86207F3A9DB314056D0F905C8E24640983DEEA7BBDE3140D249BE9801E34640A8B2783030E03140E60453F8ECE34640485C3BC367E13140A2154E5BEEE4464010C4D55CC9E331400A0FD6E364E54640204417FAFEE731400A0FD6E364E5464098D2C1AEB1F931400A0FD6E364E54640F8E4A1299A14324056D0F905C8E246401806A6C69017324056D0F905C8E24640C029DAF9571A324006CB99D926E346407074641FC31E32407EC178B746E44640586122DB0221324042FC361087E446406875E76D1236324042FC361087E4464000451CA1D53A32407AC5C098FFE3464050B0314FB1423240E2D5D246EFE14640E0A14E7183483240663ADB80E8E14640F037DD96EE4C32400ECF0B173CE04640D00C72F65251324006F09F13A7DF46406057FC1BBE553240260D862B41E0464058ACCD85E55932404E46B32413E14640D07EAB63FF5D32400285B95E0CE14640388D5FE541623240A216A74D21DF464000D14C37566232400A36AAEA1DDF4640687A96B5076432407E9C5695F3DE464060A6D3BFC665324082BCE557E4DE464040D210CA856732407E9C5695F3DE464078C42EB266693240A216A74D21DF4640A04D46FD446E3240BACB4F5B77E0464040654ED4287732409E3D6DE050E44640787B1904577B32401636F02E4DE5464040C051D41481324082DF63095BE4464040AC95B5BF8732407E75828E3AE54640206D6EF6CE92324002D02C3989E84640309359ABE0953240CEC3DB80D8E94640B811B19D7C9B3240CE9302DDA7ED4640F86BE233459F32402A08014028EF4640A8668207A49F3240DA2678B730EF464070D9CE2254A0324052412E39FAEF46402090089018A132401A0C1FC584F14640A08F16670CA232408E2879B72AF24640800B3EC3D7A73240B2ECF1CB2BF44640409C6CBCA1AC32407E2C973C87F44640280EA6294EB932409A1CA11300F34640001B759369C332406A117FF125F1464028784FD488C632403690A3B000F14640C0969CEF34C932404E8F9E7686F146401848763058CA3240566337106BF24640104554717BCB3240623388C896F34640985EBA740CCE32408626ABEAF1F446409804691F5BD13240E273B95EE6F3464038D092208CD23240D6C39A1BEAF3464080E4AF000ED43240929230D6EEF3464018F84A9A6BD8324076C8A113FCF44640B8761BA18DDD324032FF178B83F5464048DE3C2669E03240FE1C8AC811F646402082AD63F3E23240A291014018F74640E090DAF9BBE63240B2ED66A631F74640F005658230E83240F24166A635F54640907CD422CCE33240968EE8F435F4464010C4D55CC9E132409AD6963C89F34640C0DE2E4F75DF324006800A1797F24640606FE433B9E43240C630828E22F14640C00327156CE73240167E900217F04640F005658230E832403A28900219EF4640E0D4406056E63240EEB0E8F441EE4640C0727F071CE332409AD3A74D03EE46406850B7D7F5DA324016356DE0BDED4640085B773038DA32403EDDF2CB3FEA4640482401B9B0D932409E64337380E946407831F1A7C3D832403A4DC098F5E8464000F3638250D83240A6B2CD0C69E8464008A76D5944D932402298178B9FE74640087C7BCD2EDD3240F66D05DD36E64640D0EBA52902DF3240BE3820C51DE54640708E237871E63240663ADB80E8E14640D00DF544A2E73240EE8D4E5BF8DF4640607BE133B1E832404EFDB95E16DC46404063B8D7D5EA32407E2DE2BA70DA4640607C2CB25AEE32403EB8DE1D76D94640F88FC74BB8F23240DAC442E794D84640A867055673F632401A0D6A436ED74640805285A4EEF73240F23FC23599D54640B89EF4E180F632404AAD5F6C7ED44640706C0C2D77EF3240121653F812D1464000D3E233A9EC324086DB0DB458D04640A0EBFDB890E93240F68625FF42CF4640207E11CA81E83240AE83939FDACC4640F005658230E83240DADD7EF173CA4640304A44FD50E73240A2A899D95AC9464058F314677CE932403A733BADBBC84640C8B0EBA797EE32402632A84DD4C546401069715914F13240A637818EFBC44640D82BA8C6D0F53240B21E7D5402C54640588652711FF93240C6309002ECC5464078EBBC74B0FB3240A25F76B701C7464088B83A2629003340FE1B2362D5C74640F82205565B023340D29013EE61C84640B085B8D7A1043340266E5495A0C84640E06A5FE5F5063340E6BB751A03C84640B05AC64B8C083340B2A47B54FEC64640A8D2F97E5709334066CD26FF54C64640E883D3BF7A0A3340F6CEB75EC3C5464008E93DC30B0D3340A637818EFBC44640287AE5D05B1133400261B85E46C44640A0902915101533408E07838EFDC34640C029DAF957183340A68DABEA55C346404891FB7E331B33409A05097A7DC1464020A37930C4133340EAFD1B28EEBE4640D81BD7CE96043340D6A72126B8BF464088DCE76D76023340662F3273D4BF4640C089547113FF32400218E9F4A5BC4640A0E799EFC40033401214CB6F49BA46400863DF50230333400E0E936967B94640381C392619083340BEEF0A178EB746408052BD749409334026A10EB40DB6464010B103B954073340EE8D5CCFC1B4464068BA9852D6F932400AC614EE8DB24640409F4D371EFD3240CE7E1C288BB04640306F7493ED0033404290CD0CDDAE4640107BF87E5F053340E6DEF3CBB9AD4640B8C2D9F9730A334052425DCF50AD4640080E36890215334052425DCF50AD4640E0B1A6C68C1733406EC6E1BACCAC4640A06DC74B6C1833400AF71C2895AB4640402C917BA2183340FA81929F20AA4640C0DDE3D04B19334056D14484F1A84640105432ECEF1F33402E50939F28A64640E06891187D29334012BFC1357CA44640D032B53AF35C3340DA97828EC69F464008C4D55CC9603340AAD26A43639D464090CFE033B5653340DE9F3CAD949C464090AF271568683340225AD546009A464040BFCE85C56733402297DA80FF964640F0EEA18ED8643340260079C8F7954640302697B5036433407AE830D6AC9546400859E133E54D334066CE8D655195464098A220DBA64C334026E9BCFBA0954640A0FE3689964A33406A5C1C28BF964640B0CA69BC654933401659606CFA96464040E5499A8B4733406E7CABEAAF964640403140C3974633403201BCFB1F964640102F0256D345334012CBD1A98C954640F0024C378E443340F6A9A3B039954640F82F1504D7393340EA91A4B0BA944640D0779EEF5C343340D651862BD9944640306C9318712F33402E29DB8082954640D00959ABB02C3340167FDB8080964640480335ECDF273340B281E1BAF4984640609E43FDD4243340063B4BBE89994640B05EF244321F33400261C6D20F994640D84B2915F820334016372D392D97464058A6D3BF46243340662D9C76C194464090AEDC963E2333405689B224B1924640F07372F6B61D3340F66F9BD94992464058CEE4D05F0F33409A68973CCC92464080BAD0227C0B33409EB8D5469191464000F47630D40B334082E1F9056E90464050226BBCDD0D33409A8BF905708F464020C6DBF9671033406234E1BA898E4640282EEFA74F1233405640B95EB48D4640A818F6E14414334002F3D4461C8C4640A090291510153340D6BF939FDF8A4640884433EC0316334002622D39CC874640386B10CA21183340F28A7B5405844640A06DC74B6C18334072785595AD824640F829530EDE173340AA898D65798146405880907BA61633406A7398D9EE7F464068D16DBCCD153340A66A2D395F7E4640C0BCDF33551633403AD4FBA2117D464080E013671C1933408E1F66A6297C46403864CB85591E3340FE1B31D6DE7C4640F0947693AD2033402AE41B28F57B4640384169BCB52133400E57929F017A464090D13E60E21F33405664828E1479464028BD3889F21C3340D225F568A178464000D3AA63831A3340D66BFF3F187846405044BAD77D163340F687707DAC754640D09D190423143340623388C896744640B8772E4F111133400A5C3373ED734640F09FF044560D3340561B89C817744640006026782D0833402E0504DD1876464078C9A529B6043340EA3D1EC57C76464008129A52CEFD3240BA0C818E1C7546408007DAF98BFE3240324A99D98972464058CC4ED40C0433400615DE1DCD6E4640B0A81AA145013340EADAABEA806E46406B34DF1694F93240C8F1C7D36E6E464090AEDC96BEE332405A3C717D3B6E4640280CD85CD5DB32407A3580F12B6F4640F86A1ECC95D732404E6F2B9C287046409804691F5BD13240FEF90B179B714640C099459A8BC83240D2532A9CF574464048370B2DCBC5324006A9741AA378464038682F4FA5C732402297B024E3794640286493B533CD32407E0BAF87637B46405066D122F8CD324036DAE7F4A67C464058AA378992CD32403ABBF768187D4640707183A446CC3240224ED346B97D4640E8C41767B8CB32406EEBF568167E4640403D3DC38FCB3240466915EE767E4640403D3DC38FCB324076A4E657257F464080C260E56DCB3240461D1FC56A7F464098811C3EB0CA32408AB4559532804640684DD65CF9C932408A6E4BBEBB80464020A1E333F1C832402E2B717D15814640F8B3AC632BC732404E8532735281464038C8E19606C032406288751A91814640A024EA6D02BD324022DF7A540982464088FEFEB8F0B93240F2D2299C58834640B8E99F8C63B032406AC8AF87458A464080BDE96D1EAF3240BE81198BDA8A4640F0C56B599CAD32408ADAC2352F8B4640109A2E4FDDAB32406677D2461E8B4640182A8BDE83AA32400E85D5469F8A464000F644FDCCA93240BAE613EEDF89464028BFCE8545A93240AAD25CCF19894640C8FB96527AA83240E6D479547C884640501E07F311A632407EDBF1CB05884640D8B823DBFAA33240AE3A9AD95D884640B0CBEC0A35A23240620FBF9836894640B8566282C0A032402E07B6C13E8A4640C02A2578019F32408672852BEB8B464080794B37DE9D3240CA2BEF2E808C464030503EC3EF9B32408A461EC5CF8C4640485BF0443E9A3240FA8C3BADB48C4640280B8DDEAB9632401E53828EEE8B464078A0499AF3943240FEF8C098B18B4640E8B05B48E39232408694B85EB88B4640381D84A4C28E3240C631DB80158C464028799A52B28C32401A314F5B218C464030E06282F08832401239DF1D938B4640B0C97FD4E888324062B23E13518B4640586586A4CE883240B68705DD6F8A4640C01B47FD0C8A32406233963CE088464000D0C9E8868A3240BAAA67A613874640F80DFEB85C8432404E6BD54626874640809FC64BA47D324022BCEE2E09884640F07E403A517B3240481FFDE09088464060A12478517732408E507C547A89464068B2051FAB73324066A49BEDDF8A4640F052A629E66D3240DE0A5B32158D4640C0BC17047B693240EED8077A648E4640707206F395643240162B39AD2F8F4640A8403FC3835232404668AE87BA8F4640B8701867B44E32403E0215EE5290464000ABD122104332404E23198B4993464010E905F3E53C32400A36B85E2794464040AC5DE5993632407AB9B024AF934640B81234ECCB333240CE28F2CB70924640E00DC6E8363332404A0110B4D8904640205CFA7E073332402E50AF873B8F46402830BD7448313240AE3D89C8E38D4640D0A145FDC82C3240C6E788C8E58C4640585FC4AE55273240F270F4CB7C8C4640C04C0E65C9243240AE250B6F818C4640B8566282C021324016EDE8F4868C46402014681FC71C3240AEA90EB4E08C4640A0C718A11D1732407EDA98D9D28D4640E06A5FE5F5083240E24F0C1759924640B8597BCDE204324032E742E720934640E8408007E40032408E0646841D93464090AF5FE50DFD3140DE2F7D546892464058600F2D7FED31408274299CC78B4640A0C7507143E93140BEA7949F608A4640C8A88A4111E73140822A0140F489464048EC97520EE031405ACDFCA278894640E0CE3D267DD83140CA7F9F765A88464070DFC8E8F2D53140C67AB85E3F884640809FFE1BCAD03140B2127B54FB8846404896AAC628CC31407EE4F6687D8A4640802F23DBCABD31402A9AD7E30E914640603911CAE9B63140FE3FEC9175934640E04DBF114BAF3140D64FE2BAFC94464020F250D4CCA63140FAC932732A954640006CE23345A13140267BBDFB23944640B810661FD39731409E1DD0A996904640680ABB748892314012F00140698F4640F870917BBA8F3140824F2362478F464078F239891E8A3140F28A6DE07B8F4640B8372CB2427D3140929389C8E18E4640B0CF18045B7B3140B213C6D2A48E4640687EC2AEAD7C31400ED0727D788F4640E0246382087F31408A6B40E72292464068E58ADEEB783140FE36E7F4FD904640C00AA429DA75314032DC99D9CC90464020F4E6D09F723140BA3AB6C13091464090D906F3797431408A6DE457FF91464070513A2645753140FA69AF87B4924640184F83A4FA743140B67EF2CB6E934640406403567F7331409AD6963C4994464040F708907C7131408272A113BE944640988435895270314042D722623D94464000D3E233A96F31405A5FEF2E72934640A85068BC216F3140AE84DE1D04934640B83300B99C66314012F0D7E30C924640C0E6BE11E7613140E275872BDF914640680C51715B5E31406AC9D0A992924640E865B09D005C31404E23198B4993464070022BB2965331409E8AA0133D95464030BD00B9CC4E31409A29EE91F095464068EC07F3D9443140A68B15EE42984640E0E0FC1BEE3F3140BE50E7F47694464000B21667D83E3140AA1AFDA2E393464060F45FE5A5323140EAFFCD0C94934640E8912578E52F3140AE84DE1D0493464018E323A6E71F3140266E3CAFA3954640680B06F33119314036D99C76BD96464068C50990440E3140EEFA2C39A899464050370B2D4B0931406E0FCD0C809D4640A8089E8CBB073140AAF641E70C9D4640C8B8B33AAF0331406661AF87219C4640408A469A1F023140FA6727FFAA9B46408804314F35033140C258939FBB9C4640A845EE0A7903314006F0C96F839D4640303E5071130331400AC6067A449E4640408A469A1F0231408EBE89C8409F4640182AC3AEA9FF304056CFAE879E9D4640B8FE77CDF6FC30403E49862B469E464090A19D8CD7F93040D69926FFA29F4640C89270F60EF630404AD1289C1EA0464060005DE51DF73040F21B077A429F4640C8BEAD00CEF730401ECBED915F9E46401004075683F23040EE69A1132B9E4640B8CD820708F130409E406A43E09E4640C00959AB30F130400AF9CE0CFB9F4640100FB9D7D1F03040FA8C4921FEA04640801FCB85C1EE3040860F1351AFA346407044F2A7A3EC304072A7D5466BA44640F087FFB8A0E53040E2BE727D52A2464048D1C54BDCE430409A5C87C8BBA14640006CE233C5E23040DE6B1B28ABA04640504D951819E0304096D82C399C9F4640B85DA7C608DE304062622462279F464078F51A049BDC3040E688919FB99D464050C79F8C17DA3040A6D6B2241C9D464060AB82073CD73040C6782262AC9C464040461804ABD430408A210A17C69B464030D18D7B36D33040763716EE3E9A4640201A62E565D330409621347322994640A012C44B00D33040AEABA4B03398464018BD00B9CCCF3040EE9AA94D32974640C0C18E7B4AC930409EDDF768E4964640D081FC7E479630405A5F0B17459C464080A5F8E1688730404EFF5DCF729C4640E82318045F7B3040063B4BBE89994640B8818CDEFB723040B2A46DE0F4924640E02912CA7D6F3040623DE6570191464068C7D75CBD6C304082E39D764A904640500D9CEF04663040EE6ADE1D8B8F464010F10556A3633040662EF568F48E46402089F2A7BB613040B67DA74DC58D464018C0E133C96130406EC6E1BA0C8D4640F0F657AB5062304056D2B95E378C464018C0E133C9613040FA5DBBFBB68A4640408C1467986030407E2E4921AD894640B8C9560E625B3040D652C33539864640106C14BA94583040009125B08F82464080B38BDE33573040D22F4584C280464040F9D65CF55030407638616C2880464080E95E4883473040E26B1B28EB804640A0CA31ECBF3D3040AE3D973C6D824640A0E67DCD8636304062E8F8050784464078E013671C1F30400ED1A1134F8C4640501396B5A31A304092B904DD678E4640F0D014673015304036E3FA0568934640F0FB06F3451130402E07A84DB595464080A64360120E30401E9DC6D29496464090DCE76D76083040561A5A3201974640D8B023783D0630401A3141E7979746406838FE1B66063040966D2A9C2E98464068EC07F359073040F24037101F9946402867E4D07B073040763716EE3E9A4640D8C52A153C053040C6E8EF2E629B4640A89F3F602A02304032006309ED9B4640700C2CCEB8FE2F404A25D9E3F89B4640308D0272F9F22F401EE6BF98919B46408023623BF9C12F403E40818ECE9B4640405A48534CA62F40EE6AD0A9019B464040CFCB42EA9A2F40C21C2F0A26994640A09D0BACAE952F405AAA8C654B98464060C9E189DC8F2F40BECB5DCF80954640C060639E628F2F4046D3DA8084944640C0C8764C4A912F40DE2503DDEA8C4640A051AE56118F2F405E42872BED8A4640A073D43E0E832F40A219963C678546408069060FD8822F4072547EF103834640409CE0ECED882F40D2C3E9F4217F4640E0B1D9B2F8862F40FE1B31D6DE7C46401033A27F457A2F40526626FFF0784640605DF705DD772F402A8ECCBB9B774640806DC26732762F40B641ED91AF764640D0E40372F1772F40E2E52A9C787346401017EC26FB7E2F404E2343E7E5704640C0249FE22E872F4096D9939F986E4640F076C7A1FF8B2F401215087A296C4640708C397C90892F40426D4F5BE6694640608946199F802F4012520DB42869464060FFE34F17762F408EBEA5B093684640F0B85F3BA16E2F40D62EEC91CF6646406040C1CA436F2F40EE4615EEAA6446404078FBD4FA742F403E1B51F871624640006AF837E4812F400AC8AAEAA05E464080D119BDBF842F40224ED346F95D4640E0C8EF60D0902F40626353F87D5C4640209EFDD4BA942F402610838E505B46405005A64510982F40EEFCD0A9045A464090C52531829C2F407A02AAEA2B59464080F794D105A42F40AE63F66860594640A0B79BA8F1A82F405640ABEAAA594640105C653B19B22F40B25D0A17CB594640C030FA9A7DB62F401279C5D20E5A4640D00021F74CBA2F4046B2F2CBE05A464060635B640DBD2F40BEC1D5E3F95B46401029B490A6C02F40CEBC88C8C65C4640006B43B60DC72F403AFC1A28B45C4640F0C2BDCA8BCA2F40BEC1D5E3F95B4640F02AD17873CC2F4066308B65C75A4640C0A772AFD3CD2F404224640973594640001FB4B992CF2F40620E828E56584640005DB92D7DD32F401A57BCFB9D57464080AF5A01D4D72F404AB8087AD2574640B0931DBD0FDC2F40EA92198B4058464020E637A5ECDF2F40C62F299C2F58464010ABAC7F6DE52F40E61D8F02CC5646407066BE674AE92F405A83C6D29B534640A025E1EC1DF02F408AB5929FD25146407072A97F4DF52F401680347333514640501D30A5B4FB2F40320215EED2504640F8C84CD4180430404A6466A68150464058E720DB3E073040DA4CF3CBF64F464070541BA1410930405E38299CC24E464010CCD5BF860A3040A68DC7D2284D4640E0BEE5D0730B304042FD818E704B4640E8024C370E0B30400E63A213524B464040DC6E59F00830408604862BEE4A464060DF9018CD07304096B8B95E3E494640C0DDDA5C91033040AAAEAF874C4746401025DC968E013040BA9FBE983F454640286A8D7BD2063040CA77C96F39434640787038261D0C3040AA1D087AFC4246406005D45C6D153040760888C877444640F0D15FE5D91A3040DE325ECF2444464010F36382D01D3040C69D4484BF424640709CAD00821F30409AD688C87F404640700C5171DB20304036779102FE3D46404049F97E2727304036B76943B0374640D0B33CC3DF283040D6755DCF423546406836304FED283040AEAA59324A33464010C9BC7464273040DA72448460324640F01FEC0A392530401E7A4821DE3146408876A28C872330402AC052F854314640B0FBC5AE65233040D20CD54655304640B0AFCF85592430403226EC91BC304640B8CBEC0A3527304012AAF768B230464038963A26DD2B3040A2A9E457443046403812DB962E3130402A9CA5B0072F4640B0741504EF3330402601A0130D2E46404805CD0FA53430404E8DB330DE2C4640E0765371B3343040CA516A43C62C464078B8025603343040EE69939FA12A46400887EC0A1D33304002F6A74D8F294640A060884185303040BACE3E4A7D274640406B489AC72F3040A63925FF17264640A89F0790043030407E551D2826244640F08995B5733230403EDFA4B025204640B81AC4AE3D3730404A1A30D6A41A464070E58ADE6B3B3040E6AF65A672184640C82857AB884630400EA23D4A24144640303F9BEF3C4A3040A639178BCE1146407801E033ED4F304052D8A5B0CC0C4640709A1704AF533040F28D6A438B0A464010A622DB9A583040921DB4C1C3084640488339267D5B3040C6EA852BF5074640F065B09D005E3040DE6E0A177107464050C8EA0AC1603040C212A5B057074640D0FF32EC6B673040E68CF56885074640F0649DEFFC693040020FD6E32407464070BF479A4B6D3040424AD1A92F05464018321BA1756E3040522038104D034640E0E202B98C703040864EBCFBCA014640E0DFB19DC47B30409E1E371053004640386E2915448030404AAD7B5411FF4540387A26153C8430405A61AF8761FD454060EBC2BE068730405EBB35CFE2FB4540205006F3498E30406A5C1C28FFF7454040476382D49A304072BD0D3456F3454070B2FF1B2AA330400ECD440428EF4540A8FD5BABB8AD3040F243188B9BEB454078DB641FA7AF3040C606B8DEBEE94540B019793094B03040C212A5B097E74540D8C2499ABFB2304006CFE1BADFE44540081894186DB63040E2D34304C1E245401059E133E5D23040BA9FCC0C89DA4540685A156760FB3040D2EBFA057BCB4540F8978FDECF073140124F099734C64540282494D0F30F31401E5E40378DC3454000319852A6153140D60597BCB1C1454098CE95B58B2431406E37EC91A2BE4540A844A38C4F3D31409EF3136E3CBD454060A8A18C3F453140760F0C974ABB454088B985A452493140328F5FECFBB7454080675DE58144314036B777B739AD45407836A0EF384A31402A50A8CDD6A6454000469FEF245431406659F56853A1454090161FA1915E31401EC48C65C49D454030CCDE33C1633140225245848E9C4540B0F72915746631403A4C6E60A79B454078EC7793256831405EAA8C654B9A454070D44E374A6A314072E830D6AC974540F0A94ED4406D3140368EEA1136954540107F5C482B713140628723E282934540A0FB530D6F73314092FBABA1F6924540A89BDB965E963140EABB751A838A4540A8ACFC3218993140E2F742DD548945408064449ADFA03140FAAA0A17F6854540105A6482B4A83140EAB9DF1D307F4540701B5E48BBA8314072E51E452F7F4540A0DC570EC2A83140EAD2E357297F454068524DD4C8A831407A7566A6277F4540100E6E59A8A93140DAC0FA059C7B45400893B13A6FA2314056D6F3CBA679454060DA6107A694314030B6D03596784540007D0EB280943140985ECF359E784540B0766307B68C3140F035248B477A4540D0636207D688314028D5CE35AE794540007D0EB20084314068557BE0BC7A4540206A0DB2A07F31400017268B6F7C4540007D0EB2007D3140E019CF35C67D4540007D0EB280793140C02CD03526804540805DB75C8B78314060BF248BB78145402096BA5CAB7A3140985ECF35DE824540007D0EB28080314060BF248BF78345406070B85CEB7C314088F7CE357A84454040570CB2C0783140C02CD035E6834540501910B210743140F880CF35EA824540007D0EB2006F314088F7CE353A82454090F30DB2D070314008E8CF358E82454090F30DB2D075314060BF248BF783454090F30DB25073314068557BE0FC83454090F30DB2D0703140D0B2CE35228445402096BA5CAB6E3140D0B2CE35A284454090896407166D314050A3CF35B6854540007D0EB2006F314040D2258B978645406070B85C6B6D3140D893D035CA87454090896407166D314040D2258B57884540501910B29067314040D2258B97864540B0E00CB2F05E314040877AE074894540F0506107F6503140D893D0354A904540206A0DB2202C3140E019CF3546974540D063620756233140C02CD035E6994540501910B2101F314088F7CE35BA9A45402096BA5C2B1D3140D893D0354A9B454090896407961B314088F7CE35FA9C45402096BA5CAB193140D048258BA79D45409089640716173140680AD0351A9E4540007D0EB280143140E019CF35469E454090F30DB2D012314030B6D035969E4540501910B21011314040D2258B579F45406070B85C6B0E3140B0107BE0E4A04540B0E00CB2F00D31400017268B6FA1454090896407160D3140680AD0351AA345402096BA5CAB0C314030B6D03596A34540805DB75C8B0B31401804258BCFA345404083B95C4B083140B0107BE024A44540B076630736073140D0B2CE3562A445409089640716EF3040403CCF35D2AF4540B0E00CB2F0E73040403CCF3592B1454090F30DB250E53040B0C5CF3582B24540F050610776E33040680AD0355AB34540B0E00CB270E1304050A3CF35F6B34540206A0DB2A0C03040204FD035B2B54540206A0DB2A0B13040680AD0351AB84540B076630736A0304088F7CE357AB945406070B85CEB883040D0B2CE35A2BF4540501910B29088304050A3CF35F6BF4540D06362075688304008E8CF358EC0454070060FB2B0873040A87A248B1FC1454090F30DB250863040D0B2CE3562C1454040570CB2C08430407871D0353EC14540B0E00CB270823040C02CD035A6C04540206A0DB220813040B0C5CF3582C04540B0E00CB270773040D0B2CE3562C14540B076630736643040D0B2CE3562C14540B076630736643040B0C5CF3542C2454090F30DB250693040F035248B47C245402096BA5C2B6F3040F8CB7AE0CCC24540B0766307B674304040877AE0B4C3454070060FB230793040D0B2CE35E2C44540B0E00CB2F0743040A87A248BDFC54540501910B2106F3040985ECF355EC64540B076630736563040F8CB7AE08CC6454070060FB23053304050A3CF3536C645406070B85C6B4E304088427AE01CC54540206A0DB2A04C3040D0B2CE35E2C44540F0506107F647304088427AE09CC44540D0636207563D3040C02CD03566C34540206A0DB2201E3040D0B2CE3522C34540206A0DB2201E3040B0C5CF3542C24540908964079626304040877AE074C14540B0766307362A3040E019CF3586C04540206A0DB2202C304028D5CE35AEBE4540E08F0FB2601C304028D5CE35AEBE4540F0506107F61C3040F8CB7AE04CBD4540B076630736193040204FD03572BD4540B0E00CB2F010304028D5CE35AEBE4540206A0DB2200A3040403CCF35D2BE454040570CB240073040680AD0355ABF45409089640796033040B0C5CF3582C04540007D0EB200023040B0C5CF3582C04540007D0EB200023040D0B2CE35A2BF4540C0E070B9D6FD2F4028D5CE356EC0454060EDC60E6CF22F40C02CD03566C04540C01F1F64C1EE2F40D0B2CE3562C14540402C75B956EE2F40F880CF352AC14540E00C1E6461EE2F40D893D035CAC04540C01F1F64C1ED2F40888D258B7FC0454040D41A6441EB2F40B0C5CF3582C045402013C90E2CEC2F4018B979E0ECC1454060C11964E1EE2F40F880CF352AC2454060C11964E1F22F40B0107BE024C2454020E71B64A1F72F40204FD035B2C24540402C75B956F92F407871D0357EC34540E0A1C20EECF72F40680AD0351AC44540E0A1C20EECF62F40E019CF35C6C4454080AE186481F92F40888D258BBFC54540A032206421F12F40403CCF3592C54540A032206421EE2F40985ECF355EC5454040D41A6441EB2F40D0B2CE35E2C44540800673B996E92F40D0B2CE3522C6454060EDC60E6CE52F40D0FD79E0C4C64540C01F1F64C1D92F40888D258B7FC7454040D41A6441E22F4028D5CE35AEC7454060EDC60E6CE32F40D893D0358AC84540E0A1C20EECDE2F4030B6D03556C9454000FA1C6401D62F40607479E054C9454000FA1C6401D62F40A87A248B1FCA4540E00C1E6461D82F40D0B2CE3562CA4540E0A1C20EECDA2F40D893D035CACA454040D41A6441DD2F40607479E014CB4540A032206421D82F40E019CF35C6CB454000FA1C6401D62F40204FD035F2CB4540C01F1F64C1D92F407871D035BECC454000FA1C6401D62F40985ECF359ECD4540C01F1F64C1D92F40403CCF3592CE454080AE186481D62F4088F7CE357AD04540E0A1C20EECDB2F4028D5CE352ED14540C01F1F64C1EE2F40985ECF351ED14540C01F1F64C1EE2F40403CCF3512D24540C01F1F64C1D72F4050A3CF35F6D2454040D41A6441CF2F40204FD035F2D24540E00C1E6461D52F40F880CF35AAD3454060EDC60E6CDA2F4030B6D03596D3454020E71B64A1DE2F4028D5CE352ED3454080AE186481E22F40204FD035F2D24540E0A1C20EECE72F401804258B4FD34540A032206421E72F40C02CD03526D4454020E71B64A1E22F40F035248B07D5454040D41A6441DD2F401804258B8FD54540402C75B956E42F4008E8CF350ED6454020E71B64A1E72F40B0C5CF3502D7454020E71B64A1E62F40D0B2CE3522D84540C01F1F64C1E02F40B0107BE024D94540A032206421E12F4068557BE0BCD64540C01F1F64C1DC2F4040D2258B97D6454040D41A6441D02F40B0C5CF3582D845402013C90E2CC92F40F880CF35AAD9454020E71B64A1C52F40B0107BE0A4D94540E0A1C20EECC02F401804258BCFD94540C0E070B9D6BD2F40B0C5CF3502DA454020E71B64A1BA2F40B0107BE0A4DA4540800673B996B32F40F880CF35AADC4540A032206421B02F4028D5CE356EDD4540E00C1E64619B2F40D893D0358ADF4540800673B996952F40403CCF3592E04540C01F1F64C18A2F40E019CF3546E1454020E71B64A1692F4060BF248BB7E1454020E71B64A1622F40C02CD03526E3454040D41A64415D2F40D893D0350AE64540A032206421512F40F8CB7AE00CE7454000BB6EB916442F4068557BE0BCE74540A0322064213C2F40F880CF35AAE9454060EDC60E6C4B2F4028D5CE356EE8454080AE186481532F401804258B0FE84540800673B9965B2F40F880CF35EAE74540800673B9965B2F40985ECF35DEE84540800673B996582F40985ECF351EE9454080AE186481562F4030B6D03516E94540E00C1E6461552F4040877AE0F4E8454060C11964E1542F40985ECF35DEE8454000BB6EB916472F40E019CF3506EB4540E0A1C20EEC252F40D0FD79E004EE454000FA1C6401182F40306B258B33F04540A032206421172F4030B6D03556F04540C01F1F64C10C2F40B0C5CF3542F3454040D41A6441062F40985ECF359EF44540C0E070B9D6FE2E400017268B2FF5454000FA1C6401F72E40B0107BE064F5454080AE186481F22E40C02CD035A6F5454020E71B64A1EE2E4088F7CE35FAF5454000FA1C6401E92E40D893D035CAF6454040D41A6441E62E40680AD0359AF745402013C90E2CE42E407871D0357EF8454020E71B64A1E02E4008E8CF358EF9454020E71B64A1C42E40F880CF35AAFF4540402C75B956B72E40D893D0354A03464060C11964E1AD2E40D893D0350A054640800673B996A32E40D893D035CA054640A0322064218E2E40D893D0358A0A464080AE1864818E2E4040877AE0F40A464060EDC60E6C772E4088F7CE357A0F4640A0C7C40EAC712E40607479E01410464000FA1C64016D2E40E019CF35C6104640E0A1C20EEC6A2E400017268BAF114640E0A1C20EEC6C2E4088F7CE35FA124640C0E070B9D6642E40F035248B0714464000FA1C64014A2E40D0FD79E004194640A032206421522E40D893D0354A1B4640E0A1C20EEC4D2E40985ECF351E1E4640402C75B956432E4040D2258B9720464080AE186481382E4030B6D035D6214640800673B9963E2E4018B979E02C23464000FA1C6401482E40D048258B27244640A0C7C40EAC502E40D0B2CE35E223464080AE186481542E400017268B6F21464060EDC60E6C5A2E407871D035FE1F4640402C75B956652E400017268BEF204640E00C1E64616A2E40F880CF35EA224640E0A1C20EEC5E2E40F035248B87244640C0E070B9D6622E4060BF248BB724464060EDC60E6C632E40204FD035F224464020E71B64A1632E4060BF248BF7244640E0A1C20EEC652E40F035248B87244640C01F1F64C1692E40F035248B8724464000FA1C6401672E40607479E01426464060EDC60E6C652E4088427AE09C264640C01F1F64C17E2E40607479E09423464060EDC60E6C892E40607479E014214640A0322064218F2E4088F7CE353A2046402013C90E2C972E4030B6D0351620464040D41A6441902E40F035248B87244640C01F1F64C1942E40D893D035CA24464000FA1C6401972E40C02CD0352625464080AE186481982E40D0B2CE35A225464000FA1C64019B2E40D893D0354A264640A032206421942E4028D5CE35AE26464060EDC60E6C8E2E40D048258BA7274640A0C7C40EAC892E40D0FD79E004294640C01F1F64C1852E40403CCF35922A464020E71B64A18F2E4018B979E0EC29464000FA1C64019E2E4030B6D0359627464080AE186481AC2E4088427AE09C2646402013C90E2CD12E40680AD035DA224640402C75B956D92E40F8CB7AE04C224640C0E070B9D6012F4028D5CE352E214640E0A1C20EEC0C2F40985ECF355E214640A0C7C40EAC0E2F40D893D035CA224640800673B996072F40C02CD035A623464000FA1C6401E62E40F035248B8724464060EDC60E6CE32E4018B979E0EC24464000FA1C6401D42E40D0FD79E08428464060C11964E1CF2E40D893D0350A294640A0C7C40EAC9D2E4088427AE05C2D464060EDC60E6C962E40204FD035B22E4640E00C1E64614F2E4068557BE0BC3B4640402C75B956372E40E019CF35C641464000FA1C6401292E40985ECF359E44464020E71B64A11A2E40403CCF35D245464080AE186481182E40204FD03572464640C0E070B9D6072E4040D2258BD7484640C01F1F64C1032E4018B979E0EC484640A0C7C40EAC002E40F8CB7AE00C4A464080AE186481CD2D4040D2258B5758464080AE186481C42D40A87A248B9F5C4640402C75B956CA2D4088F7CE353A5E4640A0C7C40EACC92D40A87A248B1F604640E00C1E6461C62D40680AD0355A62464080AE186481C42D4050A3CF35F664464040D41A6441C32D40D893D0350A6C464080AE186481C42D40D0FD79E0446E464080AE186481CF2D40680AD0355A76464060EDC60E6CD42D401804258B4F78464060C11964E1D72D4088F7CE35BA7A4640A032206421D42D40A87A248B9F7D464080AE186481B62D40D893D035CA89464020E71B64A1B42D4068557BE0FC8B4640E00C1E6461AF2D40B0C5CF35828D464020E71B64A1A72D4030B6D035968E46402013C90E2C882D40607479E094914640C0E070B9D6502D40F880CF352A99464020E71B64A1362D40F035248B479F464080AE1864812F2D4088F7CE35FA9F4640E0A1C20EEC262D40403CCF3592A14640A032206421232D40403CCF3552A3464000BB6EB9162A2D40E019CF3586A4464040D41A6441242D4030B6D03596A54640800673B996112D40E019CF3506A84640C01F1F64C11A2D40680AD0351AA64640E00C1E64611C2D40A87A248BDFA54640800673B9961A2D40A87A248BDFA446402013C90E2C162D40607479E054A44640E0A1C20EEC102D4030B6D03556A4464080AE1864810C2D4050A3CF35F6A44640E00C1E6461052D40985ECF355EA6464000FA1C6401CE2C4040D2258B57AB46402013C90E2CBA2C4088427AE01CAC464000BB6EB916B12C40B0C5CF3502AD4640C0E070B9D6A82C401804258B4FAD464080AE1864819F2C40F035248B07AC464060C11964E1942C40E019CF3506A9464040D41A64418B2C40D0B2CE3522A54640402C75B956842C4068557BE0BCA04640C0E070B9D67E2C40985ECF35DE9746402013C90E2C782C40C8777BE0C8934640C0E070B9D6772C4040D2258B9793464000BB6EB916772C40985ECF351E934640A0322064216B2C4018B979E02C904640C0E070B9D65B2C4060BF248B3791464000FA1C6401642C400017268B6F8F464060C11964E1622C40D0FD79E0848D4640E00C1E64615C2C4040877AE0B48B46402013C90E2C542C4088F7CE353A8A464000FA1C6401512C4028D5CE352E8B464000FA1C64014F2C40680AD0351A8A464060C11964E14E2C4050A3CF353689464000FA1C6401512C401804258BCF86464000FA1C64014F2C40D893D0350A87464080AE1864814B2C40607479E054874640A0C7C40EAC492C40680AD0359A874640A0C7C40EAC492C401804258BCF86464020E71B64A1512C40F880CF352A85464000BB6EB916552C4018B979E06C824640C0E070B9D6532C4040877AE0747F464080AE1864814D2C40985ECF351E7D464020E71B64A1472C40607479E0547C464080AE186481432C4018B979E06C7C4640800673B9963F2C40680AD035DA7C464000FA1C64013A2C40985ECF351E7D464060C11964E1332C4040877AE0B47C4640E0A1C20EEC2D2C4050A3CF35B67B46402013C90E2C292C40F035248B877A4640C01F1F64C1262C40D893D0358A79464040D41A6441232C40D893D0358A794640A0C7C40EAC242C40D893D0350A7C4640C01F1F64C1282C4018B979E02C7D4640E0A1C20EEC2A2C40B0C5CF35427E4640C01F1F64C1262C40985ECF359E804640E0A1C20EEC1B2C4088427AE05C824640E00C1E64611A2C40607479E0548346402013C90E2C1B2C407871D0353E844640800673B9961A2C40985ECF35DE84464020E71B64A1142C4068557BE0FC844640800673B996162C4088F7CE357A83464060EDC60E6C142C4040877AE034814640A0C7C40EAC162C4028D5CE352E80464000FA1C64011C2C4008E8CF358E7F4640C0E070B9D6202C4040877AE0747F464020E71B64A1242C40C02CD035267F4640C01F1F64C1262C40F880CF35EA7D46402013C90E2C212C40204FD035F27C464000FA1C64011A2C4028D5CE356E7A4640C01F1F64C1162C40F035248BC779464020E71B64A1142C40F880CF356A7A4640C01F1F64C1112C40F880CF356A7A4640C0E070B9D6132C40E019CF3586794640800673B996142C4040877AE07478464000FA1C6401142C40E019CF3546774640C01F1F64C1112C4008E8CF350E76464020E71B64A10D2C40F035248B4776464080AE1864810B2C4040877AE034764640800673B9960B2C4030B6D035D675464040D41A64410E2C4018B979E02C75464040D41A64410E2C4008E8CF354E74464020E71B64A10B2C40B0C5CF354274464000FA1C64010B2C40B0107BE024744640A0322064210B2C40680AD035DA73464080AE1864810A2C4040D2258B5773464060EDC60E6C052C40A87A248BDF73464000BB6EB916FA2B40F8CB7AE04C73464000FA1C6401F22B4040D2258B57734640A032206421F62B40B0C5CF35C2724640C01F1F64C1F82B400017268B2F72464060EDC60E6CFC2B4008E8CF35CE704640E0A1C20EECF82B4008E8CF35CE704640E0A1C20EECF82B40B0C5CF350270464060EDC60E6CFC2B40B0C5CF350270464060EDC60E6CFC2B4008E8CF350E6F4640C01F1F64C1F82B4088427AE09C6E464060EDC60E6CF62B4008E8CF350E6E464000FA1C6401F22B400017268B6F6C464000FA1C6401F32B4088F7CE357A6B464060C11964E1F32B40204FD035326B4640C0E070B9D6F32B40985ECF35DE6A464000FA1C6401F22B401804258BCF69464060EDC60E6CF72B4088427AE0DC694640800673B996FA2B4088427AE09C694640E0A1C20EECFC2B40E019CF350669464000FA1C6401002C40403CCF351268464080AE186481ED2B40F880CF35EA67464080AE186481E42B40F880CF352A68464000FA1C6401DD2B40E019CF350669464000FA1C6401DC2B407871D0357E684640E0A1C20EECDB2B40680AD0355A68464080AE186481D92B40403CCF3512684640E00C1E6461D62B40985ECF351E6A4640C01F1F64C1D12B4088427AE05C6A4640800673B996CC2B40985ECF351E6A4640C01F1F64C1C72B400017268BAF6A46402013C90E2CC42B401804258BCF694640A0C7C40EACC92B40B0107BE06469464000FA1C6401CF2B4088427AE09C684640800673B996D12B40F8CB7AE08C67464000BB6EB916CF2B407871D0353E664640A0C7C40EACD32B40C02CD0352664464000FA1C6401D62B40985ECF359E63464060EDC60E6CCE2B40985ECF35DE62464080AE186481CC2B4040D2258B17644640A0C7C40EACCC2B4028D5CE352E66464040D41A6441CB2B40403CCF351268464080AE186481C72B4030B6D03596684640402C75B956B82B400017268BAF694640C01F1F64C1B22B401804258BCF694640C0E070B9D6B32B407871D0353E6A46402013C90E2CB52B40B0107BE0246B464040D41A6441B62B4008E8CF358E6B464020E71B64A1B42B40985ECF359E6B4640E0A1C20EECB02B40D893D0358A6B464040D41A6441AF2B4008E8CF358E6B4640C01F1F64C1B22B400017268B6F6C464040D41A6441AF2B40B0107BE0646D4640A0C7C40EACA92B40C02CD035266D464020E71B64A1A12B40B0107BE0A46D464040D41A6441992B40F880CF35AA6E464000FA1C6401932B40B0C5CF3502704640C0E070B9D6962B4008E8CF35CE70464000BB6EB9169C2B400017268B6F6F464020E71B64A1A32B401804258B0F6F464060C11964E1AA2B40D893D035CA6F464040D41A6441AF2B4028D5CE35AE71464060EDC60E6CA12B4040D2258B1771464000BB6EB9169C2B40607479E054714640C0E070B9D6962B4008E8CF358E724640C0E070B9D6962B4040D2258B57734640E0A1C20EEC982B4040877AE0B4734640E00C1E64619B2B401804258B8F744640C01F1F64C19D2B4018B979E02C754640E0A1C20EEC972B40D0B2CE3562764640402C75B9568C2B40985ECF355E7B46402013C90E2C772B40680AD0355A7F4640C0E070B9D65C2B400017268BEF854640C01F1F64C1582B40D893D0358A864640402C75B956522B4030B6D035D687464020E71B64A1492B40E019CF350689464000BB6EB9163F2B40680AD0355A894640E00C1E6461412B40F880CF352A8A4640A032206421442B40204FD035728B464000BB6EB916462B4088F7CE35FA8B464020E71B64A1392B4030B6D035168F4640E0A1C20EEC462B40B0107BE0A4904640C0E070B9D6732B4060BF248B37914640C0E070B9D6692B401804258B0F92464000FA1C64015E2B40680AD0355A92464000BB6EB916462B4028D5CE352E924640402C75B956392B40B0C5CF358291464080AE186481332B40403CCF3592914640A032206421312B4088427AE09C924640A032206421322B4050A3CF35F693464080AE186481332B40403CCF351295464060C11964E1322B4018B979E0EC95464020E71B64A12D2B4050A3CF357696464000FA1C6401302B40607479E054974640402C75B956302B4088F7CE35FA974640E00C1E64612E2B40F8CB7AE08C984640A0322064212A2B40F880CF352A994640A0322064212A2B4050A3CF35F6994640402C75B9562E2B4028D5CE356E9A4640E00C1E6461302B4060BF248B379B464040D41A6441302B40B0C5CF35429C464020E71B64A12D2B40F035248B879D464000FA1C6401312B40D893D035CA9E4640C01F1F64C1302B40680AD0351AA046402013C90E2C2D2B4088F7CE353AA1464020E71B64A1262B40D048258BE7A14640402C75B956282B400017268BAFA24640A0322064212B2B4030B6D03556A34640A0322064212F2B4088F7CE35BAA34640800673B996342B4088F7CE35BAA34640800673B996342B40E019CF3586A44640E00C1E64612F2B4060BF248BF7A44640402C75B9562B2B40F035248B87A5464060EDC60E6C282B40E019CF3546A6464020E71B64A1262B4088F7CE353AA7464060C11964E12B2B40C02CD03566A74640E00C1E6461302B40403CCF35D2A7464000BB6EB916382B4088F7CE35FAA84640A032206421322B40D0FD79E084A94640E00C1E6461242B40680AD0359AA9464020E71B64A11F2B40E019CF35C6A94640E0A1C20EEC192B40D893D035CAAA4640A032206421152B4008E8CF354EAC4640C01F1F64C1142B40F880CF35AAAD4640A0322064211C2B4060BF248B37AE4640A0322064211C2B40D0FD79E004AF464000BB6EB916162B40C02CD03526B04640C0E070B9D6062B4030B6D035D6B7464060C11964E1072B40607479E014B8464080AE186481092B4088427AE05CB8464020E71B64A10A2B4040877AE0B4B846402013C90E2C082B40E019CF3546B9464000FA1C6401082B4088427AE05CB94640A032206421092B40403CCF3592B9464020E71B64A10A2B40F035248B87BA4640402C75B956022B400017268BEFBD4640C01F1F64C1002B40403CCF35D2BF464020E71B64A1032B40E019CF3586C14640C0E070B9D6062B40E019CF3586C14640C0E070B9D60B2B40F8CB7AE0CCC04640A032206421232B40403CCF35D2BE4640C0E070B9D62D2B40403CCF3592BE4640E0FE5B9E0E422B407A0D53F8BFBA464000918A8FE7512B408E36B88EE0BA464000372C31C2842B406AC7482149BB464020A8CB04F19C2B40EE447FF197B8464040D0E589C4A32B408E75BA5E60B7464070EDC6A1CFAB2B4016372D39EDB64640603E34422BC72B40CABDE1BA39B64640C080B5909ECC2B40BA172A9CF0B64640A05A31081ED12B40262569432DB84640F072A41C94D82B40EADA9D7677B9464020717EC08CF02B402E064F5B42BB4640A03AE8891CF72B40CE723610D7BC464070A1D07843EC2B40B65869431FBF464070A1D07843EC2B40F289067A7FC04640809B050F10EE2B4056649E7667C14640E07573AF9BF12B405A5EB224D2C14640203DBC8496F92B40EACA2B3A86C14640C089BF2D25072C404A63FF3F05C14640F033542A800E2C40DA4D30D656C046408070832346152C40B65869431FBF4640E0DF8CFA21222C40AAAE939F79BD464000BDFBD4922F2C40E6D2E357A9BC46409040CF43C63B2C40F219C2318BBC4640105EDA75113C2C40AE56F3778ABC4640B0262C6B473D2C401658077A87BC4640B090D515824A2C40C232188BF5BC464090894F8DD9622C409E1C939FF6BE464070851A20F96F2C405EFAE657A3BF46407082C904317B2C40B63F650926BF4640C023427C908F2C40FA8D78B794BD4640C08045F052A72C4006164584C9BC464080018C34DFBE2C40F69364092ABD4640F02FB8908ED22C40EADBE8F420BF4640804C2E6B07DC2C407E0A5695B0C0464080DEF19A15F02C40AAAD3AAD46C34640101B2194DBF62C402A760EB46EC54640109270758EF92C4086DF39AD3EC7464090F5B220A6FB2C401A302C1222CA4640E067E9260BFC2C40227D4584ADCA464020CAAA7F45FF2C404E6C20C54FCC46408099DFB208042D40C6795F6C8CCD464060F4D315F2102D405E43D2A916D0464030B461D857162D408E746FE076D14640108DF9FDBE1C2D402A75D1A90ED44640A04DF2FDB6202D4016060917D3D44640103FC66A60232D404213653206D54640D074283172292D40FA7FE0BA7AD54640C0A77B230E2F2D401ECD751AE9D44640D02E649E2A302D405A5FEF2EF2D24640002AF69A952F2D400E3CCE0C99D04640D0297D860F302D403A4E0B17DFCE4640D0DFB52DC5342D401A9DAAEA41CD4640D02B1383623B2D40BACDE5570ACC464020E85D01F44C2D40EA47606C94C94640E0ADB62D8D502D408E2CA5B050C94640C0036E31CD532D4096EE76E3FBC84640401A46B6FD532D40E66F7FF1F6C84640C0EF4553F4562D40AAF6337343C8464040EF532AE8572D40C2A0337345C7464060F51E941B562D40B21E7D5402C54640F07CF93744562D40962126FF58C44640500CBC2D6D602D40B21130D6D1C2464060F4D315F28F2D40B63F650926BF4640F076C7A1FF8F2D40B63F650926BF464060F9BA2D0D902D40EADBE8F420BF4640F07BAEB91A902D40B65869431FBF464020998FD125982D406685862B8BBB4640F039162079AD2D407A0D53F8BFBA464080B1F97161C32D401E9C6DE021BC4640C0C80F2039CD2D40B65869431FBF46403045384213CF2D40A61FACEAD8C1464000DAD37863D82D40125D9AD9E9C14640105416BD07E42D40BACDF3CB93C0464070803308DEEC2D40B65869431FBF46408012270466F42D406E5E40F5F0BE46406063F437FCF82D400E7CB4C1D4BE464040323742B3FE2D403E25AF875CBE4640F07918BDC7032E403E6F01408CBD46404063EBC3C11C2E4052AF1FC56DBD4640D0BE548FDB212E40E66038DCA9BC4640A00809494D472E40FEF60EB40BB746402007D515525E2E405E1EA2137AB64640E0560380A2732E4062E46295DDB74640806B9C0B2B902E401673D980B9B946405028D9B2C8A02E40E6B86A43AAB94640606013BD7FA62E40F6F29C76F6B94640B04EA4A8F1AA2E407E2DE2BAB0BA4640E0C45C3B19B42E40DE771D28F2BC4640001C6C1205B92E40E64D5A32B3BD464000526ED83F9F2E40DE474484C1C046409010D1DBE4972E40AAD11151F0C2464070C8FD37C4932E40BE144921B4C54640C0560E83B2902E40BA0AEB9109C94640E0A40AB679902E4016D8C2B726C94640A0FE1A5AAE8D2E406A7E5DCF95CA4640501FC6A1078A2E40A6AFFA05F6CB4640109D4B2A80892E40CABF694303CD46400076E34FE78F2E40AA1B569596CD4640C036556465932E409AD54BBE1FCE4640C09E68124D952E409A69C6D222CF4640400B71AF43982E405E43D2A916D04640606F4FF04AA72E409EBA79B7EDD04640A097E289A4AD2E408A54E01D86D14640E0CEB156C9B42E40E6666CE0F5D1464080DB07AC5EBF2E4002AC717DF2D1464060F6D9B290BC2E40F6FFCD0C14D3464010614C5334B42E407AC4593283D54640D01D96B737B32E4092FF4514B6D54640B04EA4A8F1AA2E4052B078B760D74640C0C734425BA92E40A68DB95E9FD74640E05F219473A72E40B2ED66A671D74640F027609E42A12E401611B22427D74640E042B990EE9E2E40E2B10917CFD64640E0CF6C753E9E2E40BE5B90024BD64640F0F49397209C2E40C69D52F808D64640502643B675952E40A6FBE2BA78D64640E060DCB2E8902E40368F4ABE0DD74640208195D1358E2E40926D2A9CAED746404039B45621892E4042D86DE066D946405079AD7F35842E402630125141DA4640D09471AF73802E40822B3E4A94DA4640C012704C727F2E407EE533731DDB4640C0336598F47F2E40B626CF8F5BDB4640D06F4A8D91822E40725333739ADC464010C2EB60E8862E40CAEF505B7DDD464010F025948B9B2E408694C6D281DF464060E0ACEE7CA92E404692567251E0464000957A23AEDB2E403E6C04DD3CE3464050CC36C808DC2E40329CB2DA49E34640204A488DD1E12E406285A2131EE4464030691D5A06E12E400E2F65A655E5464030FC229403DF2E401EE40DB4ABE6464070E6B0B972E12E4072755832DEE74640601904AC0EE72E4002F25F6C56E8464000D43142D3EC2E400E327EF137E8464010A85B64A5F22E401A565595E1E746408067701285F82E40F6DB04DDB3E746406018498D99FD2E40DE79B32405E8464050B7B2F31F072F400EAB505B65E94640700EC2CA0B0C2F402AE3DE1DD5E9464030B0889478192F409A06D8C36EE94640301AD615B22C2F401EF15A32DCE8464010D0DC158A402F40B6E8B75EFCE84640D0D7C4678A4A2F40028436107DE9464080DF33A504552F40623E3F4A74EA464090A908AC265A2F40324BF2CBBCEB464070315C64D5592F40E6BA1C2890ED464040EA6C12CD532F406A7D04DD22F04640808CA0A8A1512F40A26CDF1DC5F14640B0F99A6EA4532F40EAD952F84DF34640A056FC9A3D572F402A094CBED1F44640D0B34FF0E2592F40A2D9AF876BF64640F0947A23AE5A2F4022BB87C88CF84640A0D25C83CB592F408604256F7AFC4640404103D532592F40F6F3E7F41FFF4640A02CEE6040592F40CE57640925FF4640A001FCD42A5D2F40FAFA4821BB01474040C6F79A25632F40C2AC35734C03474090A6B7905E652F404E16DA80A204474090D8B525C1622F409EE8475154054740C083FD372C5E2F4086016DE08B06474070C948B6ED572F40A2FC1FC55807474050D8B69096492F40DE51949F62084740001E020F58432F409248EC9108094740E0AC2122493F2F408EE9FAF9C709474070B44A8D29352F400AADCA6FA50B4740401484C0042E2F40468AE1BA870E4740D096774C122E2F404E692362C011474030410C496D352F40F63B7A5460154740C0C67923E63E2F40C6604DBE89184740F04D7BF4CD402F40AAC20DA2FC184740506BBCCA93472F40FEA0BAFB941A474000F5A57F95522F400E3CC0988F1B4740D045D5931F5B2F40CEE92981731B474000FDFD71E17F2F40BA825695FA1A4740A0912094AB892F40329239AD131C4740202BB1B90A942F40D673ABEA1C1F47404076467F5B992F40CE86575AD21F4740606280862F9B2F40DA27B5C11020474000F39FE2F6A22F401AC18F02B5204740601568121DAB2F40665EB224122147405061AEAA36C22F40BEA8F30131214740C040EF9F04C32F403EDC2315322147403090E3EC75C42F40F6FE900234214740F0DCA97E7AD62F40E2733000EC224740208C50C7BEE52F40DAB85CCF60244740305D29CEC8F62F406615D546E8244740A0BF6315C2FC2F40864325D132254740200CB3B932FF2F40428C69435125474020027ACDEA0430402E02F9054026474088886182F8093040EAD7BCFB7A27474090BFDAB6C70C3040DA7786C28A284740E05DE80A690D3040DA992DB9C7284740D08E0C2D430F3040B2F7A84D892A4740205006F3490F304012313A2D332C474050E2A0EFB40E3040F2ACA013092E4740A81179CDD60E30400E1653F85230474010A06C0CB2163040EA4DD944C12F474010F21804271830404E38FF3FA62F474060D41667241B3040220A9082D62F47402073A08C931D30408A8F178B8C304740B85DA7C6881F304066A79D768531474098457E6AAD213040D64A10D14F324740F0635271D32430402E2FDC00863247404885CF2250273040AA60077A1A3247401086A18C732D3040028A537813304740F019F2441A31304026C052F8542F4740F0AA99526A353040F660AAEAFC2E474030546ABC9537304072A52362052F474040373A89B64030403E689959CC2F4740803555718F4630408E4922E2C32F4740F01D1E3EC047304052823CCA2730474048DACEEAA94730404A06BBF977304740F06184A45A47304056870EB494314740182EB7D729463040E601727D303247400866203ECC4130403EB5EF2E303347408037B39D3C4030406E8008FAD6334740187962828C3F3040EE1F3373E8344740582DE56D064030400A3B60ECF7364740687B1904D73F30408EE115EE00384740A0162815CC3C30401658077A873B4740D8EC2878D13B3040BA32035D473D474010FEBCB8F33B3040C699BAF3103E47400865D5BF223C30404ACDE0BA253F4740504A2861CC4230400A86AEDCBC4147403053570E92433040865379B709424740F8BB3C269D4B30408E05DF1D2143474000914A9A874F304052A8C552F743474050124B377A5430407A55F3CB89444740B8BE2E5B1C573040164FA1209C45474000E5A1291A583040D6E2F5680346474088C350D0EB593040BA09DE2D1C454740C059B39D085A30409EAB6CE00D454740A8491A049F5B3040D6E2F56803464740401F1AA1955E304062CFCA6F31454740B042D5BFD6613040BAF0717D0A454740981A8CDE1765304066EF59322245474020B99CEF006830409EAB6CE00D454740D81B5071476A30401236F02E8D444740F0ACF77E976E3040B2A3067AF8424740D094CE22BC703040B6E910516F42474070C09218F57230403E6EA84D5942474058BCF67E837830408E4BE9F497424740C0E74160367B3040B6E910516F42474068CAB8D7B97D3040AAD11151F041474020027ACDEA8330404641CC0C384047400103000000010000009C00000000F78F7BF66F32404A1E6A4394474540D82A5D48277132404A4842678145454098C2D085B96F324096B46AC36041454068749CEFE86F3240B6AEA8CD273F454028E4C64BBC71324092B166A62C3D4540588507F375743240F6B6DB007C3B4540901EB8D7BD7732405E161251083A454060CAB8D7B9793240767A077A9339454038D63CC32B7E3240B2A0FBA29F3845407002F3E1707F32403AB5EF2E3037454090896407167F3240D893D0354A354540F0506107767932407871D0357E384540007D0EB20076324030B6D03556394540F0506107766D3240E019CF35463A454040570CB2C0693240985ECF351E3B4540B0E00CB27069324028D5CE35EE3C45402096BA5C2B3D32401804258B8F474540F050610776383240D0B2CE35224A4540805DB75C8B38324008E8CF354E4B4540206A0DB220393240403CCF35124D4540B07663073639324028D5CE35AE4E454070060FB2B0373240A87A248B5F4F4540007D0EB2802A3240888D258B3F504540F0506107F61F3240B0C5CF358252454090F30DB2D01B3240985ECF35DE524540F05061077617324088F7CE35FA524540F050610776133240985ECF355E534540E08F0FB26010324018B979E02C54454070060FB2B00E32407871D0357E5545404083B95CCB1332407871D0357E55454090F30DB2500E324018B979E06C57454040570CB2400C3240C02CD03566584540501910B2100B324088427AE0DC59454040570CB24007324068557BE07C584540D0636207D60032400017268BAF594540501910B210F6314088427AE05C5D454090F30DB2D0E631407871D0353E604540F050610776E13140C02CD03566624540501910B290E4314088F7CE353A654540F050610776DD314088427AE01C65454040570CB2C0D53140403CCF3552674540007D0EB280CE314018B979E0EC67454090F30DB2D0CC314008E8CF35CE674540D0636207D6CA31407871D0357E674540206A0DB220C931401804258B0F674540B0766307B6C73140B0C5CF35026645409089640716C6314060BF248B37664540805DB75C8BC43140204FD035B2664540B0E00CB2F0C3314060BF248BF766454040570CB240C23140A87A248B5F67454070060FB230BC314050A3CF3536694540501910B290B83140B0C5CF35C2694540D063620756BD3140403CCF35126845402096BA5CABBF314030B6D035D6664540805DB75C0BC3314018B979E0AC634540805DB75C0BC3314088427AE01C63454090F30DB250C1314028D5CE35AE6245402096BA5CABBF3140403CCF35D26245402096BA5CABBF3140680AD0359A634540501910B210C03140403CCF3592644540B0766307B6BF314088F7CE353A654540501910B290BA3140888D258B7F664540B0766307B6AD314088F7CE357A6845402096BA5C2BA2314030B6D03556694540007D0EB28094314028D5CE35AE6B4540F050610776863140A87A248B9F6C454040570CB2C0703140D893D035CA6F4540D063620756713140E019CF350673454090F30DB2D06A3140D0B2CE35A2744540D0636207D659314068557BE0FC754540F0506107764531401804258B4F7B45404083B95CCB3C314030B6D035567C4540B0766307B6383140F880CF356A7D454040570CB2C0363140E019CF35C67D454070060FB2B03331407871D035FE7D45406070B85C6B183140B0C5CF35027E4540007D0EB20010314088F7CE35BA7E454090F30DB2D00B3140985ECF355E7F4540805DB75C0B07314040877AE0748045409089640796043140D0FD79E0C4814540B07663073607314040D2258B17834540B07663073607314060BF248BF783454090F30DB2D004314088427AE05C84454090F30DB250033140D0FD79E004854540501910B210023140F8CB7AE0CC8545406070B85C6B00314040D2258B978645406070B85CEB05314088F7CE353A86454040570CB2400E314040877AE0B483454040570CB2C012314040D2258B17834540206A0DB2A028314060BF248BF7834540007D0EB2803D3140F880CF35EA824540F0506107F6633140C02CD035267D4540007D0EB2006D31407871D035FE7A454040570CB24074314088427AE09C7845402096BA5CAB743140985ECF351E784540B0766307B6743140F880CF356A774540B0E00CB2F0743140D048258BA776454090F30DB2D075314068557BE0FC754540007D0EB200773140680AD035DA754540007D0EB2007A3140403CCF351276454040570CB2407B314068557BE0FC75454090F30DB2D07F314060BF248BB7744540F0506107F681314088427AE05C744540D0636207D68431407871D0353E7445404083B95CCB853140B0C5CF35827445402096BA5CAB85314050A3CF35B675454070060FB2B086314068557BE0FC754540B07663073688314030B6D035D6754540206A0DB2A08A314028D5CE352E7545406070B85CEB8B3140F035248B0775454090896407168F314040D2258BD7744540B0E00CB27091314088427AE05C744540B0E00CB270953140D893D0354A734540F0506107769F314030B6D0351672454040570CB240A4314050A3CF35367145409089640716A73140D893D035CA6F454070060FB2B0A83140D893D035CA6F4540F050610776AC3140B0C5CF35027045404083B95C4BB7314008E8CF350E6C454090F30DB2D0BD314018B979E06C6B4540007D0EB200B63140C02CD035A66D454070060FB2B0B33140F880CF352A6F454070060FB2B0B631407871D035BE7045409089640716B53140D893D0358A71454040570CB2C0AD31401804258B8F70454040570CB240A73140D893D0350A72454050479B527AAA314072E38848DC734540E0DD1BA1F1AD3140B6A46DE034754540E0DE661F9BB331401285F12EB2754540901EB8D73DB931404E65DB8047754540F0A5EA0AF5C33140A668AC6ABA734540D033705968C931400AAC86AB60734540B01660E571CB3140DE221BA8C9734540004554717BCD31409A90B6C16E744540B0DEF67ECFCF31409E006F6076744540C8FFFA1BC6D231403AFC289CFD72454028F53AC383D331409A6B6A43BF714540C8898C4139D331409E01C852A96E45401875FEB8C0D33140262FC018336D4540781CEA0AC5DB3140EAAE289C926845408801180493DE31409ED7D346E9674540085D0D2D8BE731405E10035DFB664540E062C6AEC9ED3140CE03C9EF3865454010A1AB63CBF83140424B2A9CA260454088DB9CEFCCFE314092E28AC8C65E4540980BAE63A30C32402AC65A327D5B4540E8CF9118613932402E124AA164504540B0F410CAD14132406EA07BD4B44E454030DE0456C34F3240D6C57FF1F44C4540680B06F3B15632401685D5461F4D454060F033EC7F593240725EE33A534C4540180C7F11AA5E324092A845B4854945404079D222D85E32400ACC0EB46C4945400088FFB8A0623240E242B8DE83484540482CC94BC86B32403248180B2548454000F78F7BF66F32404A1E6A43944745400103000000010000001600000070060FB2B0DB3040680AD0355A6445404083B95CCBEB304088F7CE357A6345409089640716EF304028D5CE35AE624540B0766307B6EF304040877AE0B4604540B0766307B6EC3040C02CD035E65E45404083B95CCBE73040F880CF35AA5D4540805DB75C8BE2304088427AE05C5D4540D0636207D6E13040B0C5CF35425E454040570CB2C0DF3040F880CF356A5E454070060FB2B0DB3040680AD0351A5F4540007D0EB200D93040C02CD035E65D45409089640796D530401804258B8F5D4540206A0DB220D2304060BF248B375E454090F30DB250CF304088F7CE35FA5F4540007D0EB280D23040D048258BA7604540F0506107F6D23040F8CB7AE0CC614540206A0DB2A0D1304088F7CE357A62454090F30DB250CF304088F7CE35BA614540501910B290CD304088F7CE35BA6145406070B85C6BCF30407871D0353E634540007D0EB200D4304050A3CF35F663454070060FB2B0DB3040680AD0355A6445400103000000010000001E00000070060FB2B0A83140680AD0351A5F45406070B85CEBAD314018B979E06C5D4540007D0EB200BB3140403CCF35525B4540B0766307B6BF3140D048258BE75845404083B95CCB62314028D5CE35AE62454070060FB2B05F3140F035248B47614540501910B2905A3140D0B2CE3522624540206A0DB2A0553140D0B2CE35E2634540E08F0FB2E052314088F7CE353A654540B0E00CB27057314008E8CF358E65454070060FB2B05F314068557BE0BC664540B0E00CB270643140985ECF359E6645402096BA5C2B63314028D5CE356E6645404083B95CCB613140A87A248B1F664540206A0DB2205F314088F7CE353A654540F0506107F6613140607479E05465454070060FB2B0643140D0FD79E0446545404083B95C4B67314050A3CF35F66445409089640796693140680AD0355A644540007D0EB2806B314088F7CE353A654540D0636207D66F314018B979E02C6445406070B85C6B7C3140985ECF351E644540501910B210863140C02CD035E6614540501910B21093314050A3CF35F6604540F0506107F698314088F7CE35FA5F45402096BA5C2B98314050A3CF35B65F4540B0E00CB2F0973140B0107BE0A45F4540B076630736973140680AD0351A5F4540B0E00CB2F0A53140F035248B875E454070060FB2B0A83140680AD0351A5F45400103000000010000002400000040570CB2C0213140680AD0351A7C4540501910B21026314028D5CE35EE794540501910B2102A3140B0C5CF358278454040570CB24033314068557BE0FC754540007D0EB280313140F035248B07754540206A0DB2A02C3140D0FD79E00476454070060FB230273140D048258BE7754540007D0EB2801B3140F035248B077545406070B85CEB03314088427AE0DC764540501910B210F93040985ECF359E764540501910B210E63040204FD035F2734540F0506107F6D93040D893D0354A734540E08F0FB260BD3040C02CD03566754540007D0EB200B130401804258BCF76454040570CB240AC3040A87A248B9F76454070060FB230AC304068557BE0FC7545404083B95CCBA730401804258B8F764540B0E00CB270A33040C02CD035A6774540B0E00CB2F0B43040C02CD035267B4540B0E00CB2F0B43040680AD0351A7C4540501910B210B03040F880CF35AA7C4540F0506107F6A53040D048258B277D4540B0766307B6A13040E019CF35C67D45409089640716A73040E019CF35067F454040570CB2C0AC304040D2258B177F4540501910B290B230407871D035BE7E4540B0E00CB270B8304088F7CE35BA7E45404083B95CCBBD3040D0FD79E0847D454040570CB2C0C43040204FD035727C4540F0506107F6D23040C02CD035267B4540B0E00CB2F0E03040C02CD035267B4540007D0EB280E430401804258B8F7B4540E08F0FB260EA304068557BE0BC7C4540B076630736083140E019CF35C67D4540B0E00CB2701C314028D5CE356E7B454040570CB2C0213140680AD0351A7C45400103000000010000002000000040570CB2C03D304050A3CF3536894540D0636207D63E3040E019CF35C6894540F0506107F63F3040F880CF352A8A4540E08F0FB2E040304060BF248BF789454040570CB240413040E019CF35C6884540805DB75C8B40304068557BE0FC874540D0636207D63E3040B0107BE064874540D0636207D63B304040D2258B9786454090F30DB2D0383040D048258B27854540D0636207D6353040B0C5CF3542844540501910B2101F304050A3CF35B681454090F30DB25016304060BF248B778145402096BA5C2B10304088F7CE353A824540206A0DB2A0153040403CCF35528345404083B95C4B163040F8CB7AE04C85454040570CB240143040403CCF35D286454040570CB2C011304040D2258B978645402096BA5C2B10304040D2258B97864540007D0EB2800C304040D2258B57884540501910B2900D30407871D035BE8845402096BA5CAB0E304068557BE0FC884540B0E00CB2F00F3040A87A248B1F89454040570CB2C011304050A3CF353689454070060FB2B017304040D2258B978A4540D063620756213040403CCF35528B4540501910B2902B304088F7CE353A8B4540206A0DB220333040F880CF352A8A4540D0636207D6313040E019CF3586894540E08F0FB26031304050A3CF353689454040570CB240373040D0B2CE35228A45406070B85C6B3A3040403CCF35128A454040570CB2C03D304050A3CF353689454001030000000100000038000000B0E00CB270B13040D893D0354A904540501910B290AB30401804258B0F90454090F30DB2508F3040F8CB7AE04C92454070060FB2B07930401804258B4F954540F0506107F671304088427AE0DC954540E08F0FB2606D3040E019CF3506974540007D0EB2806A3040E019CF3546974540206A0DB2205F3040E019CF3506994540B07663073660304040D2258B97994540E08F0FB260613040680AD035DA994540B076630736643040C02CD035E69945406070B85C6B67304068557BE03C99454090F30DB2506A3040D048258B679945409089640796703040680AD035DA9A454070060FB230723040C02CD035E6994540F0506107F672304028D5CE356E9A4540D0636207D6753040B0107BE0A49B4540D0636207D6753040680AD035DA9A454090896407167F3040F880CF35EA9B45406070B85CEB863040C02CD035269A4540501910B2108E304008E8CF350E98454090F30DB250953040C02CD0352698454090F30DB2508E3040680AD035DA9A4540007D0EB280873040985ECF355E9C454040570CB2408530401804258B4F9D4540F0506107F6883040C02CD035269D4540501910B2109030401804258B4F9D454090F30DB250953040B0107BE0A49B45404083B95C4B96304028D5CE35AE9B454090F30DB2D0983040D0B2CE35629C4540501910B2909A3040680AD0359A9C45404083B95CCB9D304088427AE09C9C4540E08F0FB260A53040C02CD035269C4540D0636207D6A83040B0107BE0A49B4540501910B290AA304068557BE0FC9A45402096BA5C2BAC304068557BE0FC99454040570CB240AE304028D5CE35EE984540B0E00CB270B13040C02CD03526984540501910B290B1304008E8CF358E974540B076630736B13040C02CD03566974540007D0EB280B03040C02CD0356697454070060FB2B0AF3040E019CF354697454070060FB2B0B4304088427AE0DC9545404083B95C4BBA3040403CCF351295454090F30DB250C0304088F7CE35BA944540206A0DB220D93040F035248BC79445404083B95C4BDD304088427AE09C954540E08F0FB2E0E03040607479E054944540501910B290E530401804258BCF934540B0E00CB270F53040B0C5CF358293454040570CB2C0043140F035248B07924540206A0DB2201C3140D048258B6792454040570CB2C027314050A3CF35F6914540007D0EB280313140D893D0354A904540D8AE0DB278F13040D893D0354A904540B0E00CB270B13040D893D0354A9045400103000000010000002900000070060FB230803040D0B2CE3562A44540007D0EB2006C3040C02CD035A6A84540E08F0FB2E067304030B6D03596AA4540B0E00CB2F069304040D2258B17AA4540E08F0FB2606B3040A87A248B9FA94540206A0DB2206D3040A87A248B9FA9454040570CB2406D304028D5CE356EAA4540007D0EB2006E304008E8CF358EAA454070060FB2306F304050A3CF3576AA4540908964079670304030B6D03596AA45402096BA5C2B6F3040B0C5CF3582AB4540D0636207566F304040D2258B57AC4540501910B21071304040877AE0F4AC4540908964071674304040877AE034AD4540501910B29070304088F7CE357AAF4540206A0DB2206D3040204FD03572B24540B0E00CB270723040D893D0358AB245409089640716743040204FD03572B24540B0E00CB2707E30400017268BEFB04540805DB75C0BB03040B0C5CF3542B0454090F30DB250CF304040877AE034AD454090F30DB250CF304030B6D03556AC454090F30DB2D0CB304030B6D03556AC4540805DB75C8BCE304030B6D035D6AB4540F0506107F6D03040F8CB7AE08CAB4540007D0EB200D33040C02CD035A6AB4540501910B290D4304030B6D03556AC4540E08F0FB2E0D63040D0B2CE35A2AB454070060FB2B0DB3040A87A248B9FA94540F050610776DE3040D893D0354AA9454070060FB2B0E03040C02CD03566A945409089640796E2304050A3CF3536A94540B0E00CB270E4304050A3CF35F6A745402096BA5C2BE13040A87A248B9FA5454090F30DB250DF304088427AE0DCA445404083B95C4BDD304030B6D03556A54540206A0DB2A0D830407871D035FEA245402096BA5C2BD03040E019CF3546A24540D0636207D6A03040B0C5CF35C2A14540E08F0FB260903040403CCF3592A2454070060FB230803040D0B2CE3562A4454001030000000100000015000000B07663073664304030B6D03556AC454040570CB24060304028D5CE35AEAA454040570CB240593040680AD0355AAB4540206A0DB220483040F035248B47AF4540F0506107763B304050A3CF3536B14540007D0EB200353040403CCF3592B14540D0636207D6343040C02CD03526B2454070060FB230353040403CCF3552B24540D0636207D6353040607479E054B245409089640796363040204FD03572B24540501910B210343040204FD03532B3454090F30DB250333040680AD0351AB44540206A0DB2A0343040680AD035DAB44540D063620756383040C02CD03526B545406070B85CEB46304088F7CE35BAB44540D0636207564C3040F880CF35AAB34540007D0EB2804D3040403CCF3592B14540B0E00CB270543040D0B2CE35E2B1454040570CB24059304018B979E06CB04540E08F0FB2E05D304088F7CE353AAE4540B07663073664304030B6D03556AC45400103000000010000000E000000800673B9965B2F40204FD035F2D2454000BB6EB9167B2F40403CCF3552D04540C0E070B9D65C2F4050A3CF35F6D1454000BB6EB916582F4040D2258B97D1454080AE186481532F40C02CD035A6D2454040D41A64413D2F40A87A248B9FD54540A032206421352F4088427AE05CD6454000FA1C6401392F401804258B4FD74540A0322064213B2F40D048258BA7D6454040D41A64413D2F4040877AE074D6454060C11964E14C2F40C02CD03526D64540C01F1F64C1522F4028D5CE352ED5454060C11964E1562F400017268BEFD34540800673B9965B2F40204FD035F2D2454001030000000100000010000000C01F1F64C1852E4068557BE0FCEE4540E00C1E6461882E40204FD035B2ED4540402C75B956AF2E40F880CF35EAE7454060EDC60E6CB52E40204FD03572E6454000BB6EB916BC2E40D0B2CE3522E54540800673B996CA2E40985ECF35DEE24540A032206421C62E400017268B6FE34540E0A1C20EECA72E4028D5CE35EEE545402013C90E2C9E2E40985ECF351EE74540A0C7C40EAC932E40F880CF35AAE94540C01F1F64C1852E407871D0357EEB4540E0A1C20EEC7E2E4018B979E0ACEC454080AE186481772E4088427AE05CEE454020E71B64A1742E4028D5CE35EEEF454040D41A64417B2E4068557BE0BCF04540C01F1F64C1852E4068557BE0FCEE45400103000000010000002400000000FA1C64019B2E40204FD035B2FB4540A032206421982E40204FD03532FD4540402C75B9568A2E40D893D0350A004640C01F1F64C1852E40F880CF356A01464080AE1864818F2E40D0B2CE3562014640C01F1F64C1972E4060BF248BB70046402013C90E2CB32E4060BF248BF7FC4540C0E070B9D6C12E40A87A248B5FFC454020E71B64A1C42E4028D5CE352EFC4540E0A1C20EECC52E40204FD03572FB4540E00C1E6461C72E40F8CB7AE04CF9454080AE186481C82E40680AD0359AF84540800673B996CB2E4050A3CF35F6F7454080AE186481D22E40F880CF356AF7454060EDC60E6CD62E4028D5CE35EEF6454080AE186481DA2E40E019CF3506F64540E0A1C20EECDE2E40B0107BE0A4F44540A0C7C40EACE22E40607479E014F34540E00C1E6461E42E40680AD0359AF1454020E71B64A1E02E40B0C5CF3502F2454060C11964E1DC2E40E019CF3546F24540E0A1C20EECD82E4028D5CE356EF2454080AE186481D42E4068557BE07CF2454060EDC60E6CD02E4040877AE0B4F2454020E71B64A1CE2E4088F7CE353AF34540800673B996CD2E401804258BCFF3454020E71B64A1CB2E4068557BE03CF4454080AE186481BE2E4088F7CE35FAF4454020E71B64A1B72E4028D5CE35AEF545402013C90E2CB32E4028D5CE35EEF6454060EDC60E6CB52E4088F7CE353AF74540A0C7C40EACB62E4088F7CE35BAF7454060C11964E1AF2E40E019CF3546F8454020E71B64A1A62E40C02CD03566F9454080AE1864819E2E4028D5CE35AEFA454000FA1C64019B2E40204FD035B2FB45400103000000010000001900000040D41A6441822E40D893D0354A024640C0E070B9D67B2E4008E8CF358E034640E0A1C20EEC762E4008E8CF354E054640402C75B956722E400017268B6F064640E0A1C20EEC6C2E40D893D035CA054640C01F1F64C1692E40D893D035CA054640A032206421692E40985ECF355E064640A032206421682E4060BF248BB7064640A032206421672E40D893D0350A074640E0A1C20EEC652E40F035248B87074640C0E070B9D6602E40985ECF351E06464060C11964E1562E40B0C5CF35820746402013C90E2C4C2E40E019CF35C6094640E00C1E64613E2E4008E8CF35CE0B4640800673B9962C2E4030B6D035D60F46402013C90E2C232E40C02CD0352611464040D41A6441242E40985ECF359E11464060EDC60E6C242E4088F7CE35BA11464060C11964E1242E40D0FD79E0C4114640A0C7C40EAC262E40E019CF3506124640800673B996242E4068557BE0BC12464000FA1C6401202E4088F7CE35BA144640800673B996732E4088F7CE357A08464080AE1864817E2E4088F7CE35FA05464040D41A6441822E40D893D0354A0246400103000000010000000C00000080AE186481AF2D4088F7CE35FA12464060EDC60E6CB32D4068557BE07C134640E0A1C20EECB62D4050A3CF357613464040D41A6441BA2D4050A3CF35F612464000FA1C6401BD2D4068557BE0FC114640402C75B956B62D40D0FD79E08411464020E71B64A1AB2D40D0B2CE3522124640C01F1F64C1A32D4030B6D0359613464000BB6EB916A52D4040D2258B9715464020E71B64A1A72D4050A3CF3536144640C01F1F64C1AB2D40607479E05413464080AE186481AF2D4088F7CE35FA1246400103000000010000003900000000FA1C64014A2E4008E8CF358EF9454000FA1C64016F2E4030B6D035D6F44540C01F1F64C1702E40680AD0355AF3454000BB6EB916642E4030B6D03596F2454080AE1864814A2E40B0C5CF3542F6454040D41A6441442E40F880CF35EAF5454080AE186481462E4068557BE07CF54540C0E070B9D6472E40985ECF35DEF4454000FA1C64014A2E4068557BE03CF4454060C11964E1562E4008E8CF358EF2454060C11964E1592E40D893D0350AF24540E0A1C20EEC5D2E40F035248B87F14540C0E070B9D6612E40680AD0359AF14540C01F1F64C1642E4040877AE074F14540E0A1C20EEC652E4030B6D03556F04540800673B996632E40204FD035B2EF454080AE1864815E2E40D0FD79E044F04540A0C7C40EAC592E400017268B2FF1454040D41A6441582E40680AD0359AF1454060C11964E1452E4030B6D03596F34540A0322064213E2E401804258BCFF44540800673B996362E4040877AE074F6454040D41A6441222E40680AD0351AFC4540E0A1C20EEC152E40E019CF35C6FE4540800673B996012E40204FD035B2064640402C75B956F72D4068557BE07C08464000FA1C6401E92D4018B979E0AC09464080AE186481BD2D40E019CF3506124640402C75B956BC2D4050A3CF3576134640C0E070B9D6B92D4088427AE09C14464000FA1C6401B32D4088F7CE357A16464060C11964E1B52D4040D2258B97164640A032206421B82D40F035248B8716464000FA1C6401BA2D4088F7CE357A16464000FA1C6401C42D40A87A248B5F144640C0E070B9D6E02D40E019CF354610464060EDC60E6CE32D40B0107BE0640F4640C01F1F64C1E52D4088F7CE353A0E4640A0C7C40EACE82D4050A3CF35360D464060C11964E1EC2D40F035248BC70C464060C11964E1F32D40A87A248B5F0C4640E00C1E6461F82D40B0107BE0A40B4640C0E070B9D6FC2D407871D0353E0B4640C01F1F64C1032E40D048258BE70B464060EDC60E6C012E40B0107BE0A40A464040D41A6441002E40F880CF352A0A4640E00C1E6461072E4088F7CE35FA09464020E71B64A10B2E407871D0353E094640A0C7C40EAC0D2E4088F7CE35FA07464040D41A64410E2E4088F7CE353A064640402C75B956102E40D048258BA70446402013C90E2C152E40D0B2CE35620346402013C90E2C2D2E4018B979E0ACFE4540C01F1F64C1322E4040D2258B17FE454040D41A64413A2E4018B979E0ECFD454060C11964E13B2E40D893D0354AFD454000FA1C64014A2E4008E8CF358EF945400103000000010000005F00000000FA1C6401C12D40C02CD035A6474640402C75B956BB2D407871D0357E48464060C11964E1852D4008E8CF354E544640C01F1F64C17C2D40680AD0351A57464020E71B64A1772D40B0C5CF35025A4640E0A1C20EEC792D40888D258BFF59464080AE1864817B2D4008E8CF35CE594640402C75B9568F2D40204FD035B2554640C01F1F64C1B02D40985ECF35DE4C464080AE186481C42D40204FD035324A464020E71B64A1C22D4088F7CE35FA4D464060C11964E1CE2D40607479E0144E4640E00C1E6461E02D40F8CB7AE00C4C4640C01F1F64C1EE2D40C02CD0356649464060EDC60E6CFA2D4060BF248BF745464000FA1C6401022E40985ECF359E44464000BB6EB916152E40D0FD79E08443464080AE1864811A2E40403CCF3512424640800673B9961F2E4040D2258B17404640A0C7C40EAC262E4050A3CF35F63D464020E71B64A11C2E4068557BE03C3E464060EDC60E6C0E2E40D048258B67404640C01F1F64C1032E40607479E09440464000FA1C6401062E40D048258BE741464040D41A6441072E40C02CD03566424640A032206421F22D40A87A248B9F44464060C11964E1E72D401804258B4F454640402C75B956E22D40403CCF351246464060EDC60E6CDB2D40204FD035B246464080AE186481D22D40204FD035B246464080AE186481D22D40403CCF35D2454640800673B996EE2D4008E8CF350E434640800673B996182E40888D258B3F38464080AE186481312E40B0107BE024354640E00C1E6461292E4040D2258B5736464040D41A6441212E4008E8CF350E384640A0322064211A2E40E019CF35463A464040D41A6441152E40B0C5CF35023D464020E71B64A12A2E40985ECF351E3B464060EDC60E6C5A2E4030B6D03516324640E0A1C20EEC652E40E019CF35C63046402013C90E2C6D2E40B0C5CF35422F4640800673B9967B2E400017268BEF2C464040D41A6441822E40E019CF35862B4640E00C1E6461782E40D893D0350A2B464060EDC60E6C6C2E4008E8CF354E2C4640E0A1C20EEC5F2E4008E8CF350E2E464080AE186481542E40E019CF35062F464080AE186481542E40C02CD035262E464020E71B64A15D2E4008E8CF35CE2D464020E71B64A1662E4068557BE0BC2C464080AE1864816E2E40F035248B472B464040D41A6441742E40E019CF35C6294640C01F1F64C1692E40E019CF35C629464020E71B64A16A2E4008E8CF350E29464000FA1C64016C2E40985ECF359E28464000BB6EB9166E2E401804258B4F284640C01F1F64C1702E40F035248B07284640C0E070B9D6652E40607479E094284640A0322064214F2E40985ECF355E2A464000FA1C6401432E40403CCF35922A464000FA1C6401432E40E019CF35C629464040D41A6441582E40607479E01427464040D41A6441582E40D893D0354A26464060EDC60E6C4C2E4008E8CF35CE264640E0A1C20EEC412E400017268BEF274640E0A1C20EEC382E40F8CB7AE08C29464080AE186481312E40E019CF35862B464080AE186481312E40403CCF35522C464080AE186481382E40403CCF35522C464080AE186481382E40E019CF35462D4640E00C1E6461342E4060BF248BB72D4640C01F1F64C1312E40888D258B3F2E4640A0C7C40EAC2D2E40C02CD035E62F464000BB6EB916332E40403CCF35D22F4640C01F1F64C1362E40E019CF350630464060EDC60E6C392E4030B6D0359630464000FA1C64013C2E401804258B8F314640E0A1C20EEC2E2E40985ECF351E33464080AE1864812A2E40B0107BE064334640A0C7C40EAC252E40B0C5CF3542334640800673B9961D2E4040877AE07432464000BB6EB916192E40D0FD79E08432464020E71B64A1132E40985ECF359E33464020E71B64A10A2E40D893D0350A374640E0A1C20EECEA2D4068557BE0BC3B464040D41A6441E62D40F880CF35EA3B464080AE186481CB2D4030B6D035D63E464080AE186481CB2D40A87A248B9F3F464060EDC60E6CCE2D4008E8CF35CE3F4640E00C1E6461D32D40680AD0355A404640402C75B956D62D40607479E09440464000FA1C6401D02D4018B979E0AC424640402C75B956C12D40403CCF359246464000FA1C6401C12D40C02CD035A647464001030000000100000025000000A032206421E42C40204FD0357248464020E71B64A10D2D4088F7CE357A404640800673B996112D4050A3CF35F63D4640A032206421062D4088F7CE35BA3D4640402C75B956F82C40607479E014404640E00C1E6461E72C40607479E014444640402C75B956E02C40204FD035F2444640402C75B956E02C40403CCF35D2454640A032206421EB2C40D0FD79E04445464020E71B64A1EE2C40204FD035F244464040D41A6441E02C40E019CF3546484640E00C1E6461D82C4040877AE07449464060C11964E1CE2C40204FD035324A464040D41A6441CB2C40680AD035DA484640E0A1C20EECC52C40C02CD03566484640C0E070B9D6BF2C40C02CD035A648464060C11964E1B92C40C02CD0356649464060C11964E1B92C40204FD035324A4640E00C1E6461BD2C40204FD035324A4640E00C1E6461BD2C4060BF248B374A4640C01F1F64C1BC2C40888D258B7F4A4640C01F1F64C1BA2C40F880CF352A4B464060C11964E1C32C4060BF248B374D464080AE186481C02C40888D258BBF4F464060C11964E1B22C40D0B2CE35E253464060EDC60E6CB02C40403CCF3592554640800673B996AC2C4008E8CF358E594640E0A1C20EECAB2C40888D258BBF5B4640A032206421B62C40B0C5CF35825A4640A032206421C32C4018B979E06C59464080AE186481CB2C40B0C5CF35C257464060C11964E1C72C40B0C5CF35C2544640402C75B956CA2C40D0B2CE35A2534640800673B996CC2C40680AD0359A51464060C11964E1CE2C40A87A248B1F4E4640E0A1C20EECD12C4018B979E0EC4C4640A032206421E42C40204FD035724846400103000000010000002D000000A0322064217B2D40985ECF359E634640A0322064217B2D40A87A248B9F624640E00C1E64617C2D40D0FD79E00462464000BB6EB916822D407871D035FE604640402C75B9567B2D4040D2258BD76146402013C90E2C732D40B0C5CF354262464080AE1864816B2D40D893D0354A624640A032206421662D40204FD035F2614640C0E070B9D6502D407871D0353E66464040D41A6441572D4030B6D0355666464020E71B64A15D2D407871D0353E664640C01F1F64C1632D40204FD035F2654640402C75B956692D40204FD03572654640402C75B956692D407871D0353E664640800673B996672D4018B979E0AC664640A032206421672D4040877AE0F466464000BB6EB916672D4030B6D03556674640A032206421662D40403CCF351268464080AE1864816E2D4008E8CF35CE66464020E71B64A1702D407871D0353E664640A032206421742D407871D0353E66464020E71B64A16D2D40B0C5CF35C2684640E00C1E6461692D4030B6D035D6694640402C75B956622D400017268BAF6A46402013C90E2C612D40680AD0351A6B4640A0322064215F2D400017268B6F6C464060C11964E1692D40D048258B276C4640A032206421742D4008E8CF358E6B4640C01F1F64C1722D4030B6D035166C464020E71B64A1702D40B0107BE0646D4640E00C1E64617A2D4068557BE03C6D4640A0C7C40EAC812D40D0FD79E0446C4640A032206421832D4060BF248BF76A4640A0322064217B2D401804258BCF694640A0322064217B2D40E019CF350669464000FA1C64017F2D40F8CB7AE08C684640A032206421932D40E019CF350665464080AE1864819F2D4068557BE0BC63464000BB6EB916A52D407871D035BE624640C0E070B9D6B32D40A87A248B5F5E4640C0E070B9D6B72D40403CCF35D25B464000FA1C6401B32D40B0C5CF35025A4640800673B996A42D407871D0353E5C4640C01F1F64C18B2D40403CCF3552614640A0322064217B2D40985ECF359E63464001030000000100000059000000A032206421072D40B0C5CF35C2544640A032206421042D40A87A248B9F534640E00C1E6461042D40680AD0359A524640A0C7C40EAC072D40204FD0353252464000BB6EB9160E2D4028D5CE35EE524640800673B996112D400017268B2F51464000FA1C64010E2D40E019CF354650464060EDC60E6C0A2D4008E8CF358E4F4640C0E070B9D6062D4050A3CF35364F46402013C90E2C012D40B0107BE0644F46402013C90E2CF22C40B0107BE0644F464000BB6EB916DB2C40D893D0350A54464000FA1C6401CA2C40403CCF35925A4640E00C1E6461CB2C4040877AE03460464000BB6EB916C92C40403CCF355260464020E71B64A1C72C40D0FD79E084604640402C75B956C62C407871D035BE604640E00C1E6461C42C407871D035FE604640C01F1F64C1BF2C407871D0357E60464080AE186481BC2C40F880CF356A61464080AE186481BA2C40A87A248B1F63464060C11964E1B92C4068557BE03C674640A032206421B92C40204FD03572684640800673B996B62C4028D5CE35EE6846402013C90E2CB12C40E019CF350669464060C11964E1A82C407871D035BE6A4640C0E070B9D6A02C40D048258B6772464020E71B64A1962C4008E8CF354E744640A032206421972C40A87A248BDF744640A0C7C40EAC972C4068557BE03C754640E00C1E64619A2C4008E8CF350E764640C01F1F64C1952C4088427AE05C784640E00C1E64619A2C40B0C5CF35C278464080AE186481A62C40A87A248BDF774640A032206421AA2C40403CCF3552774640402C75B956B32C40680AD035DA744640A032206421B82C4008E8CF354E74464000BB6EB916C12C407871D0353E744640402C75B956C62C40680AD0355A744640402C75B956C92C4040D2258B17754640E00C1E6461CB2C4018B979E0EC76464080AE186481C92C40C02CD035A67746402013C90E2CC72C40B0107BE0A4794640C0E070B9D6C72C4028D5CE35EE7A464060C11964E1CE2C40D893D0358A79464020E71B64A1D22C40F880CF356A7A464060EDC60E6CC82C4030B6D035167C464060C11964E1C02C40403CCF35527E464080AE186481B12C4030B6D0351686464080AE186481AE2C40985ECF351E8746402013C90E2C932C400017268BEF8C4640800673B9968E2C40B0C5CF35828E46402013C90E2C8E2C40D0B2CE3562904640C01F1F64C1932C40F8CB7AE00C934640E00C1E6461992C40F035248BC7944640800673B996A02C40403CCF3512964640E0A1C20EECA92C40B0C5CF3582964640E00C1E6461B62C40F880CF35AA95464060EDC60E6CBA2C4018B979E02C944640800673B996B82C4008E8CF350E8F464060C11964E1B92C4088F7CE35FA8B464040D41A6441BD2C40F880CF35AA89464080AE186481C62C40888D258BFF84464000BB6EB916D32C4060BF248B77804640C0E070B9D6D52C407871D035BE7F4640800673B996DC2C40B0C5CF35C27E4640E00C1E6461E82C4050A3CF35767E464020E71B64A1EE2C40F880CF35EA7D464060EDC60E6CF22C4040D2258B577C46402013C90E2CF12C40680AD0351A7A4640C0E070B9D6EC2C4008E8CF35CE774640E00C1E6461E72C4008E8CF350E764640A032206421EB2C4018B979E02C754640E00C1E6461E62C40204FD035F272464020E71B64A1E52C40985ECF351E714640E00C1E6461E72C40F880CF35EA6C464000FA1C6401EE2C40B0107BE02469464060C11964E1EE2C40D0FD79E004674640E00C1E6461E72C407871D0353E66464000FA1C6401EA2C40607479E0D464464040D41A6441F02C4040877AE0F4624640A032206421F22C40204FD035F261464020E71B64A1F22C40607479E09460464060EDC60E6CEF2C407871D035FE5E464020E71B64A1EE2C40F880CF356A5D4640C01F1F64C1F12C40985ECF359E5A464020E71B64A1F82C4030B6D03596584640A0C7C40EAC002D4008E8CF35CE564640A032206421072D40B0C5CF35C25446400103000000010000004A000000E00C1E6461722D40888D258BBF784640A0C7C40EAC692D4028D5CE352E794640C0E070B9D6502D40F880CF352A7C464000FA1C6401422D4040877AE0347D4640C01F1F64C13F2D40B0107BE0247E4640E00C1E64613F2D4028D5CE352E804640A0C7C40EAC402D40D0FD79E0C484464060C11964E13D2D4030B6D035D685464060C11964E1342D4068557BE0FC84464060C11964E1342D40985ECF351E84464060C11964E13B2D40D048258B2783464060EDC60E6C2E2D4088F7CE35FA824640E00C1E6461052D40985ECF351E84464080AE186481F82C40985ECF35DE844640800673B996EB2C4040877AE0B486464020E71B64A1E02C40E019CF350689464020E71B64A1D92C4028D5CE352E8B4640C01F1F64C1DD2C40D0FD79E0448C464000BB6EB916E22C40E019CF35C68C464000FA1C6401E72C4028D5CE35EE8C464060C11964E1EC2C4028D5CE35EE8C4640C0E070B9D6EE2C4088F7CE357A8D464080AE186481EE2C4028D5CE35AE8E4640A032206421EF2C40D0B2CE35E28F464060C11964E1F32C4028D5CE356E9046402013C90E2C072D40D0FD79E08490464060EDC60E6C0F2D40B0C5CF354291464060C11964E10A2D40F8CB7AE00C93464080AE186481112D40B0C5CF35C294464040D41A6441142D401804258BCF954640E00C1E6461152D40C02CD035E69646402013C90E2C142D40F880CF35EA974640E00C1E6461122D40680AD035DA98464060C11964E1122D40403CCF35D2994640800673B996182D40F880CF35EA9A464080AE186481192D4040D2258B579C464000FA1C6401192D4050A3CF35769D464060EDC60E6C162D4018B979E02C9E4640800673B996112D40D048258B679E464000FA1C6401192D4030B6D035D69F464060EDC60E6C202D4060BF248BF79F464000BB6EB916312D40D048258B679E4640C01F1F64C12B2D40204FD035729E464000BB6EB9162A2D40D048258B679E464000FA1C64012B2D4050A3CF35769D464020E71B64A12C2D4060BF248BB79C4640C01F1F64C12E2D40B0107BE0249C464000BB6EB916312D4050A3CF35B69B4640A0C7C40EAC382D40680AD0355A994640E00C1E64613F2D4050A3CF357696464060C11964E1312D4008E8CF354E95464020E71B64A1392D40D0B2CE3562944640800673B996492D40403CCF351294464020E71B64A1542D4050A3CF35B694464080AE186481572D4088427AE05C924640E00C1E6461512D40985ECF35DE8D464020E71B64A1542D4028D5CE352E8B46402013C90E2C5B2D40F880CF35EA894640402C75B956642D40C02CD03566894640A0322064217B2D40680AD0355A89464060C11964E17A2D4018B979E0AC85464080AE186481992D40F880CF35AA80464040D41A6441A12D40985ECF351E7D464000BB6EB9169E2D40985ECF351E7D46402013C90E2C982D40607479E0547C4640C01F1F64C1912D40403CCF35127C4640E00C1E64618B2D40607479E0547C4640800673B996852D40985ECF351E7D4640800673B9967E2D40F880CF352A7C4640800673B9967E2D40985ECF355E7B464080AE186481852D40B0C5CF35427A464080AE186481832D400017268B6F794640E0A1C20EEC7B2D4018B979E0EC784640E00C1E6461722D40888D258BBF78464001030000000100000015000000B0E00CB2704F30407871D035FEC04540805DB75C8B553040204FD03532C04540501910B2106030407871D035BEBE45406070B85CEB5E30407871D035FEBD4540D063620756553040985ECF359EBD454070060FB23048304040877AE074BE4540B0E00CB270443040403CCF3552BE454040570CB240423040B0107BE0E4BD454090896407163F3040A87A248B9FBD4540805DB75C0B3B3040680AD035DABD4540E08F0FB2E0373040888D258B3FBE4540206A0DB22036304088F7CE35BABE4540B07663073636304028D5CE356EBF4540805DB75C0B38304050A3CF35B6BF45402096BA5C2B393040F880CF35AABF4540E08F0FB2E03A3040B0C5CF3582BF4540D0636207563E3040680AD0355ABF4540501910B2904030407871D035FEBF4540007D0EB2803F30401804258B0FC1454070060FB2B0433040C02CD035A6C14540B0E00CB2704F30407871D035FEC045400103000000010000000E00000000FA1C6401852D40D0B2CE356225464000FA1C64018C2D40888D258B7F244640402C75B956952D40D0B2CE35E2224640402C75B956942D40F880CF352A22464000FA1C64018D2D40A87A248B1F2246402013C90E2C882D4018B979E0EC21464000BB6EB916852D40B0C5CF354221464040D41A64417F2D407871D0357E214640E0A1C20EEC792D40607479E0D4224640A0C7C40EAC7A2D4088427AE0DC234640C0E070B9D67F2D40985ECF35DE23464020E71B64A1812D40D893D0354A24464020E71B64A1812D40888D258B3F25464000FA1C6401852D40D0B2CE35622546400103000000010000000800000000BB6EB9163A2D40A87A248B9F2B464080AE186481492D40204FD035B229464020E71B64A1522D40B0C5CF3582274640800673B996502D40403CCF351227464000BB6EB916482D40F880CF35EA27464020E71B64A1322D40C02CD035662B4640A0C7C40EAC302D4028D5CE356E2C464000BB6EB9163A2D40A87A248B9F2B46400103000000010000001300000080AE186481982D4040D2258B5730464080AE1864819C2D40607479E0142F4640402C75B9569F2D4008E8CF35CE2C4640A0322064219A2D40F880CF35EA2B464000FA1C6401922D40C02CD035662C4640E00C1E64618C2D40985ECF351E2D46402013C90E2C832D4040D2258B972E464000FA1C6401842D40C02CD035A62F4640402C75B956892D4028D5CE352E31464060C11964E1862D40F880CF352A354640C01F1F64C1882D40607479E05435464000BB6EB9168B2D4068557BE07C354640C0E070B9D6902D40A87A248B9F354640E0A1C20EEC962D4008E8CF350E35464040D41A64419B2D40D048258BE73346402013C90E2C9C2D40D048258BA732464060EDC60E6C992D40985ECF359E314640E0A1C20EEC972D4008E8CF35CE30464080AE186481982D4040D2258B573046400103000000010000001600000000FA1C64017D2C40F880CF35AA54464000BB6EB916882C4088F7CE35FA564640800673B9968F2C4088F7CE35BA564640E00C1E64618F2C4088F7CE357A554640E00C1E64618C2C40204FD035B2544640800673B996852C40F880CF356A544640C0E070B9D6842C40888D258B3F544640C01F1F64C1832C401804258B0F54464080AE186481832C4040D2258B97534640402C75B956842C4018B979E0EC52464040D41A6441832C4088F7CE353A52464060EDC60E6C832C40D893D0358A51464020E71B64A1842C40F880CF352A51464000BB6EB916882C40F8CB7AE00C504640C0E070B9D68A2C40D048258B674E46402013C90E2C872C40F035248B474E4640A032206421812C4040877AE0F44E4640C01F1F64C17A2C4040D2258B574F464000BB6EB916772C40D0B2CE3522504640800673B996772C40B0C5CF354251464020E71B64A1782C4040877AE03452464000FA1C64017D2C40F880CF35AA544640
 BG	0106000020E6100000010000000103000000010000006F030000486E45FD56553A408EFCD2C644DB444008572F4F7F4B3A4062146EE0EBDA444070589B5220463A40627C96BC81DB4440F0FDF0A7D1423A40D2548A488DDC44403000C8E8043C3A4012A2593277DF444088D9EA0AE7393A40720B9A59F5DF44405884A08C39363A400A3CC0980FE04440E805499A9D303A40D2C289480ADE4440F0236CBC97223A40860334F3DFDD444098A34F71BD1B3A408EDB299C2BDD4440F8F84160DC143A400AF3C6D212DB4440F0902EB2F4123A40BA3515EEC4DA44409019EDA735113A409242DD9DBBDA444068FAAD637D0F3A40568A12D188DA444000836C59BE0D3A40326FD029CBD9444038CFDB96500C3A409AD0AAEA33D84440309305F3270C3A406AC588C859D64440C81250D4DE0C3A404E365BCF89D4444068BC1267160E3A402EBC1F450AD3444028079EB53A0F3A409289F64667D244404821045621113A405E3E15EE57D1444030B5540EC8143A4056F67BD4B2D044402012B63A61183A402AB5BE186FD04440F071C011511B3A40262D077AE8CF4440C0908641831D3A40E6B703DDEDCE444020C70A90FE1E3A4086E69AD9D9CD4440A06DAB6359213A402608E55795CA4440D86A7BCD88213A407E73DE1D9EC94440F8AEE13323213A40C2CE68A699C74440F0A16A5996213A40B6F34484BDC64440B855FB1B04233A40623837104CC64440D02FF0A709273A405E3C7FF104C6444090E3806A77283A40321F99D96AC54440F8D0304FC3293A4042D3CC0CBBC34440780AD75C1B2A3A408E26C7D244C24440804E3DC3B5293A40F2B73BADD3C04440B05B2DB2C8283A406EA95DCF34BF444060792F4FCB253A403A94077A0CBE44406886A62958253A40E2DEE55730BD4440D0E3F97EFD273A408294AAEAEEBA4440305EE43313293A402EDB40E759BA444008ED4DD49E2C3A40760C087A16B94440E06481076A2D3A40A6D24E5B50B844408026F4E1F62C3A40DADA573248B74440003944FDAA2B3A40BEA025FFFBB6444068D3E7D00D2A3A40AA84DE1DC4B6444000184060B4283A40D64A25FFFDB54440F8FB22DBD8253A404EB05CCFCDB2444088651BA1E7213A40BA3523628EAF444088441704F11E3A403629E9F4CBAD444038A0B49D5A1D3A40AAAA3D4A77AD4440F8AD96B5791B3A408E4A9002A5AD4440A860A42998053A40625FFDA2BBAB44402806FA7E49023A402A049D761CAB4440A092DBF975FB3940E2B85CCF60A9444040CBAF9DAAF539408E256EE051A8444080708CDED5F23940C2AA910230A84440409B055665EF39407AE54F5B70A84440A0FF9DEFD2EB39405EA04ABE73A8444070CA0C9072E539404AB20EB433A7444008EBB7D7CBE139401633D7E3EAA64440B869B73AD9DC3940F26C9002B1A74440B037489A55D539402608E557D5AA444030B409909ECF39404E411251A7AB444098CCAB0000CD39404640731A45AB4440C0B74CD472C3394076A1E9F4D5A8444020A044FD8EBA3940FA40299C95A84440D0C22DB2ACB739400AC3091735A8444058A459AB86B439401A2DEB9155A74440B0342F4FB3B2394082949C76A5A644404054F644F4B03940BE7B115132A64440802BDBF911AE3940B23F3BAD09A6444038414DD4A2AB3940DED776B74BA64440F003B39D4AA63940C20B44847CA7444048213C26C7A339404E8AEF2ED1A74440E89F03B92E8D394016A1D6E367A84440D8BD7E6AB7893940C2E5C86FF6A7444008127E6ABB8739409699838EC0A64440F0292EB21086394076C6EF2E56A54440003D70F6D083394092907EF148A44440605AF97E4D813940E295DE1DEAA3444090AEF87E517F3940EAB3C96FFEA344409041FEB84E7D3940CA338D653BA444404829B000A67A3940B60C731A53A4444040AB4A37167439400A326209E5A34440D849931825493940AACDAD87A49E4440780FA28C234339401E345A327A9E444058CD9952363D39402A4EFDA2D59E4440C08A9FEF3C38394012A25932F79F4440F0059D52562D39406EC2A74D9DA5444068FA917B6A2839403674949F2EA7444030D9D08B622739401EDEA04E5DA7444048A23726D51D39408ABD30D60DA9444048B73EC3D31C39409AF5BE987DA94440800B3EC3D71A3940466D79B7C2AA4440D0540456131A394012C6226217AB444068675471C71839408623A01319AB4440E08F8F7B92143940466D79B7C2AA4440901EB8D7BDEA38404E40B95E74B14440F0BFA0EFE8E23840428AE1BA47B34440289F156778DF38401EECB95E70B3444050FBA429EEDC38402E0EED913DB34440687206F395D738403EB4A4B086B24440B8A10D2DA3CD38401E73E7F442B24440C029DAF9D7CC384016E9A0138EB04440A09C261588CD3840F25F5F6C53AE4440D07D60E555CB38404EF7BF9877AC444020367F6A41C638408A982A9C8DAC4440E878213EACC03840463F44846EAE444050D4DE96FEB73840A6D103DDA6B24440F084E66DFEB23840CAA04F5B58B4444038F99E8C4FAE38404AF2F4682FB544401034B19D48A938400E14AF8776B54440B8FEA62962A338400E14AF8776B54440402D14CAF1A438406AC3E457BDB64440C0080E2D079C3840E240F1CBAFB6444000B1931889983840FE611FC502B74440B01A8CDE979438403A93BCFB62B84440381DBC7468943840CA0FC435DBB84440285919040B9538402ABD559545BA4440B8566282C094384006F9B224E8BA444068E15EE5C5933840A2D3C33556BB444028F7D0BF56913840F68C2D39EBBB44407004C1AE69903840DA0A4DBE4BBC4440C01E2878098F38403A008D6509BD444010707ECDB68D38407A9B0B178ABD4440F8B68DDEA78C38407235727D22BE444098C14D37EA8B38409245E1BA2FBF44407088203E188B3840EA1F4F5BBBC24440B0A85271EB873840CE796DE015C64440B02CB2009A823840AA851B28E4C7444080751F3E387B38404AB4B224D0C644408074D4BF8E75384046DA2D3956C644409066126758703840EAD5FCA28BC344408896EB0A896C3840C65D505B3AC34440A8D65D4823673840021477B790C344405840560E326338401E5B20C569C3444088DF00B9985A384072C6198B72C24440D07C4D375258384012A22FD65AC2444078FC30EC77513840565FE1BAA8C24440401C71F63E4F3840FA41828E88C244402834213E944D3840AEA45F6C2BC24440F846EA6DCE4B3840A688188BF3C1444030DF4FD46C493840F645CA6F41C24440B8A0C2AEF9483840A61C939FF6C24440105A2CB28E4938402A717B540CC4444020DA68BC514938407AE4DA802AC5444038D6749351443840FAF8A4B05EC6444090429DEF3040384056825F6C1FC8444068751F3EB83B3840B2C3953CE9C74440B0AD01B9E0363840A2AFDE1D23C74440304CAC549C323840967FD0611BC64440F858A9633F3238405E3C7FF104C64440800B76937D2E384026093E4AC8C44440805285A46E2D384026539002F8C344407062A529522C384016E6953CF5C14440F86C6582942B3840160C11517BC1444070B94DD4AC29384012EC818E8AC1444060C96D5990283840A686741A17C24440081894186D273840167CB4C1D4C24440386C93187125384002161B286DC34440B8804160D21D3840B2EA3FE745C44440E06E8BDE9B13384072EC6A439CC44440A0491A041F0C3840329B3E4A4BC3444070857793C10C38409245E1BA2FBF444058F8C3AE710D3840A2673E4A59BC4440787DE7D04F0D38400E13569543BB4440F04E7A30C00B3840727E4F5B4CBA444068E758ABE4083840EA21D7E3C4B94440A08267BCD905384026C19D76FEB9444020CD203E30003840826E2FD668BB4440D08E0C2D43FF3740665C1C287FBA4440C099459A0BFE3740229F6A4331BA4440B0F2B29DA4FC37402ADF88C812BA4440E83E22DB36FB3740AE6768A6B5B9444080306E59F4F63740369704DD1BB8444040D63CC32BF3374056159D7602B8444028A060E521E737403276468454BB444078B0729311E53740C22F299C6FBB4440608371F622DE374056A9178B05B94440B8EF995202DA374042D3DA8044B8444068E9EEA7B7D43740DAB4DC1DC2B744404068671F4BCF37402EB898D986B74440B018F6E1C4CA37405A5903DD9CB744405887D5BFEEC637405E633710EBB6444018C0A96323C13740B6E75E6C49B34440206BD8F9FBBC3740E270CA6FE0B244403053570E92B43740AA850DB49AB34440F0ABE4D013AC37405EC413EE93B34440A02AE43321A73740FAAE4484E5B24440F8725F48B3A03740922FBEFB72B044400050B9F4F59F37402AE6719C44B0444070524DD4C89C37405AF834737DAF4440E8B2F14436943740B615949F9DAF444000B5F7E154833740928F41E7E8B24440E04B61E51D6A3740A28BF90530B34440D8C2499A3F653740AAF1929F97B24440B04F1D3E785D3740028D3BAD74B0444010C9BC74E4583740E6932C3984AF4440004835EC7753374086B6EB9145AF4440C8DDE3D0CB5037402E4BF2CB3CB0444050005471634E37404AFEF668B6B1444008F21804A74937408E6FB224F8B2444018E1AD001A453740A2AFD0A9D9B244400068B63A1F3F3740FA1ACA6FE2B1444070E4773068393740F26CACEA83B0444008CE33ECB3353740E6B703DD2DAF444050121367D43437409642004033AE4440505679CD6E34374032DAD980DDAB4440F031DA5C1533374082491B289FAA4440A08322DBCE303740AEF80140BCA94440C09EF4E1002E374092DB1B2822A94440B098C24B3C283740424241E77DA84440709B9A527E1D3740B2C539AD05A84440B005BD11BFEA36405E89C098FAAA4440404595B5DBF036400612EF2EC7AC44407883A9C6C8F13640CE7989C828AF4440904AF5E17CF036401679D346D8B14440B8137F6AF5EF36407A9D939F93B44440E04CAC63C7F036402E9247215DB544407822AC00BEF33640DE206209BFB64440485E099060F43640FE3D482159B7444030E65C480FF436401E598AC816B8444058FBDCF993F2364006A4B75E64B94440F0C136EC3BF23640FE241A8B03BA4440E03CE9B98FF1364012139241F8C24440B08809F369F13640CEE7963CEFC44440A0F3C54BA8F236409E91F3CB0EC7444040B28F7BDEF236406EE4B024CEC74440F0F8257849F23640EE1C52F8ABC84440285A9C52DAEF3640B2D0E2BA19CA444000216F5908EF36407AE525FFD3CA4440B8A3DBF99BEE3640FE432DB989CC4440A8239FEFD8EE3640421D115161CE44400050CE22A4EF3640C25B894826D04440404595B5DBF036409E8B0E349ED14440E0BC4FD420F2364036985DCF0ED24440784B406026F6364012F6ED917ED24440F03DD75C8DF7364026A0D863D2D24440E8B50A9058F83640EE64F2CB75D3444080E95E4803FA36409ECD919F51D54440101CEE6F45FC3640B29E856AAED644405029B000A6FF36408E4C26FFB7D84440C0C2D9F973023740424E27FFB1DB444068C50990440237404AA61A28B6DE444048C19C52BEFD364042FDA43068E1444080D457AB04FB3640FE85E8F4E2E14440A0177393F5F43640F645CA6F01E24440A832459A27F23640C29CCFA979E24440408EE23391F036409A449DF66AE34440303F631F17EB3640961F828E3CE8444080E926785DE83640BA9CD6639EEC44404838C64BC0E63640FACBCF2922EE44407004F97E8FE53640DE474B3EA6EE4440A86DFF1B92E236403E3F2F5640EF4440D828068AD0E1364052DC083091EF444060F314677CE136407A2C7B54B4EF444030F6BD11D3E0364092954267ACF04440C0AFA029EEE03640466E9AD98FF24440603F0B9088E0364076C5B22476F34440E8DCD022C8DD36409E44C09862F64440E074BD74E0DB3640E201482154F944406836304F6DDB3640FEB0EFAE66FC4440E0BBCC85D1DA36402EE28CE5C6FD4440C84ED2BFCED836404ECE247F2AFF44406856E96DBAD836401A32A1932FFF4440E0971F3E84D836403E01ED11E100454010B6D6A07AD836407EE588BCF20045400861A9C6FCD736405AF370FDD9014540906E12CA95D636409A8C4B3E7E0245405891FB7EB3D33640369552F835034540401C71F63ED23640A26288483F0345404047638254CE3640EE698CE5BC024540C89832EC87CC3640D62B12D1B702454030773C2685CA3640E2933AAD4D03454090BB1BA1A5C93640AAA6D9802B044540F040B8D709C93640EA1D818E02054540908FDE96E6C7364092BFFEA286054540F8A7800748C536401679E1BAA1054540906C44FD9CB93640926FD5C66F05454030C3FA7EEBB73640125952F8B0054540D811213EC8B636405664894839064540D05D1767D4B53640D630BA5EC8064540C09F3F60AAB43640FA88D7E328074540D8F2F3E104AD36409ED1FC22C207454050FD60A68EA036403271F6D1200A45408816F044269E364066A36309960A454020515171F38736402AC668A686104540B813B73A9B823640BA5EA29388124540E09C48B9C681364086F3890C1013454038BCB53AA37E3640B2D0D44610154540F01D1E3E407B36405EFADF9DBE184540408EE233917136400689E557721B4540F063527153583640F6FA1EC51E28454060AE6382385D3640928F25FF1529454028B3A229E26736402650939F28294540A87DF0448A6C3640FA6D05DDB6294540089434893E70364086B12ED6862B4540D0F01CA15174364066F233F339304540A03B20DB4278364092DDC65223324540A8102E4F2D7C3640568251F8D532454098B1F5E1607F3640FAEE4DDB1833454058DA102D43823640AAAC12D1D4334540807B51D4FC8436406AC140E7E035454060D3CBE87A883640167FC652923A45409843E86D5A8936408E91D7E33B3D4540D04BF14452883640B6AEA8CD273F4540D04BF14452883640BEEC22E22C3F45404861EA0A5D8636409AF8D029FB404540488933EC1B833640B6ABD5C674424540F01D1E3E407B3640FACBB3418F444540A0F27ACDFE6D3640D24CD7E32349454090029B52E26C36402A4AA74D534945400034B19DC86D3640EE1F5615E04B4540F0BB153BFA7036405EC0639602514540A8C2A129CE713640C2CA12515752454080F66EF6FE72364056F522E27F554540D886449A2B7136406E2D9C7641574540E009917BD67736405E3C6309F25B4540F01D1E3E407B3640A6CDAD87245D45404020D5BF8A7B3640B2CE4CBE865D4540A018BE119F7B3640EA64E457EC5D45404020D5BF8A7B364076C30040505E4540F01D1E3E407B3640CA9E9D76B25E4540E04DF7E17077364056633ECACF5F4540A078705900743640E2DD8C65BD61454058FA917BEA6D3640169D8E022F674540687D7730046D3640CA9C15EEA8674540503911CA696D36403ED4178B24684540B80470B8316E364096A13EF7956845406062A529D26F36405EF97837826945408042651F0B72364056CFB541436A4540F03DD75C8D783640FACF2C39896B4540F01D1E3E407B3640AAA451F8616C4540C03619043F7F36407E4C0A17A56E4540C05AC64B8C813640BE350E34606F4540E0733A2611853640B6A5C6D2676F4540B884A5299E893640CAE788C8256F45406026D8F9638B36404646A5B0896F4540E822560A778B364022769913946F4540606473F6CA8C364076281E454D704540B070E90A499036400E9F392D30714540006CE2334597364052CCA3B085714540D09270F68EAA36406E3252789B6F4540F8059D5256B23640AAAB9DF64E704540A02DC5AE1DBA364052CCA3B085714540A085B8D721BD3640D2DEC96FDD724540A021390655BD3640627993C30D73454048F0FB1BDABE36401A9C66267D744540F03F6D5960C33640229521E2B47A4540C02FD4BFF6C43640FAF440E7527C454038DE0456C3C63640D29A6AC3677D45402075FEB8C0C936402E72BF18117E4540C829DAF9D7D03640324AA093AE7E454098457E6A2DD43640EAD2E357297F4540E006783034D43640EAD2E357297F454010C871F63AD43640125663092B7F454010C871F63AD43640EAB9DF1D307F4540F01A05F39DD73640568114EEF5804540806331EC5BE236400E5952F8B084454080D3D45CB5E336403E00941FAE8545407004F97E8FE536405649B74107884540307CB39DD4E63640C27ACD8CED8845405025840700E93640725196BCA2894540A07725DB56ED36408E9E16EE628A4540789104F36DEF3640B2A2C2B5F38A4540689FC64BA4F4364042B816EEDB8D454068EC07F359F93640AEA154951292454060CC4ED40CFC3640CE39872B5A964540300012769FFB3640B2079808FA974540A8DE25DB3AFB36406EC2A09378994540A0846D59F8F636403ADE6E602A9A4540000B1D3E60EA36404E40ABEA2A9B454010FAB13AD3E536400EC15EEC339C4540F0A94ED440E236406689CE0C849D4540E0B823DB7ADB36403A2167A6E3A04540288162E549D53640D277DE9D27A345405891FB7EB3D33640D6DBBE9804A445406019C84BE8D23640D26F39AD07A54540005690181DD236403AD85F6C5DA745403063800730D13640AAC9573262A84540904CC3AEF5CD364042DD1C281CAA4540E09A3889A6BB36404E20156ED5B04540C8052F8D6EB93640AA771A6B6AB1454080F7B97428B83640DA06E9F4BFB14540906BF97EF3B33640729F371070B24540384A7CCD76B13640C67E1C288BB2454030C1648298AC36401A80347373B24540E8D6D65C29AA3640A6FEDF1DC8B24540D0F3EF60AFA83640E689BBA25DB345409071F34412A836404E958AC89BB34540000AD2BF36A53640BA56D346CCB54540502CC94B48A3364016654D3E93B6454000792AB2E6A03640EE6DCD0CD1B64540603EC0115F9B364022BA271CB5B64540309D7F6AA59836401E7FF0AEEEB64540E8B2F144369636405E1140679BB7454080A194189D923640EA94BDFB5CB9454040B45D48D7903640FE1AD12907BA45409858EF0A598836409EF914EE7FBB4540F0F37630D48436401E0EDF1DB4BC4540B009E90A65823640CADEA6CD25BF454070C4BE119B7D3640C68287AB3BC54540C8B2E8D07B7A36405A80A6CD94C74540A0A81AA1457A3640720D458436C84540C0E9D75C897A3640B6A61FC5DAC84540F01D1E3E407B3640160EBC7B7CC9454088E1CE85917B3640A28C6726D1C94540189BB19DAC7B36406EE510D125CA454088E1CE85917B36409ED4004076CA4540F01D1E3E407B36409A4071FDC4CA45407823F77E677A3640324DC09835CB454040E239C3237A364032274584AFCB45407823F77E677A36409E20CD0C26CC4540F01D1E3E407B3640BA3F57959CCC4540B8610B90547B3640769C4821AACC454030E4FE1B627B3640E2D93610BBCC4540B8610B90547B3640123D27FFCBCC4540F01D1E3E407B36408AB88F02E2CC4540503317044B793640EA4A79B776CE454000FF570E0E793640BEAA838E66D14540180A0A905C773640AEF64F5B16D34540B8ACB63AB7743640A66CDF1D05D4454078B94DD42C6D36405EC7170B88D5454008648A41F9693640366D41E79CD64540D87EE333A56736407A2E267FF5D745407050EFA79B653640CA0B52F885D94540A82999B5F76336401EEE648931DB4540C82CBB74D4623640FA84731ADDDC4540B8F5CBE8C6623640769BE1BAEDDD4540D0A9D5BFBA633640EA253BAD10E0454098681804776336408A2AFA850FE1454048F2C9E8D25C36404EDCD1A9F2E3454050152CB276593640DA7252F869E745404885CF22D05A364072C8C4B533EA4540006A8407185E36402AE3C9EF26ED4540C8080E2D87603640FEA4105117F14540507E8ADE076136400A5C3373EDF445408080C84BCC61364082788D6593F545405824398956643640CE53156E47F74540F0D58BDEFF6436402ADB40E7D9F74540408FF5E1946536409E673E4AD9F94540000AD2BFB66536404E0102408FFD454058C33BC34B6636405ECB66A625FF4540802016046B693640DA9926FFE20046409026489A2F6F364012F13E4AC90146401081964144773640CE75008241024640F01D1E3E407B36408E4488C87C024640A02B6782F08036408A24F9058C02464058BD79CDD283364072562262E0034640F8A78007C88536403E04C7D2B805464010C984A4BE8836408251ABEA50074640509840C3FB8D36407609FDA2FD07464050DDF1A73F933640E6681EC5DB07464040EE5CABCC973640028A4CBE2E084640B8FA4BD4D09A36409AB2CD0C290A46401085560ECA9A36405E83AAEA480B4640705102561F99364072E6707DFD0D4640B85B499ADB98364046FAAE873D0F464048D1C54B5C993640F2683AADAE104640B02E19A1019C36409A20E9F478144640003479CDA29B36400E7E66A67A14464070BEFC1B229B36403ABD7FF1E114464020BC459AD79A36409ED24E5B901546400841F0A72F9B3640E269852B5816464090BB5371CB9B36403229DB808216464000C520DBF29F3640469A1D283E18464058243989D6A33640FA3A4BBE891A4640C0C5F24416A63640EA92EF2E641B4640F0A0EE570FB136401224CD573D1D464050717A300CE83640EEB430D6BA0F4640A08322DB4EF1364076E2289C440E4640D0FD641FF3FC3640CA09BCFBB20D4640287E736320023740FA6A956DDB0C464050C6540EEE073740BA618AC8E90B464098ABFB1B420A3740DE280040FA07464008986082E40537402673F5680C044640D0FD641FF3FC364016CBC33543024640B04007F35DF73640822C89C8FD01464010F04A372EED3640BA3DC198C9004640C0E99F8CE3E73640F61B077A82004640C8E0C44BC8E2364006AABF984CFF4540C0C4A7C6ECDF3640322A26FF6BFC4540008DE6D0BBD936401AE4146ED0F24540E007C3AEDDD936403A2601C0EAEF454068E7907B0ADD3640E26DB1247EED4540A82109F385E33640C639807175EB4540C802146768EB36404AAD6DE0C7EA454060CA15FF730D374046F86285E1EB4540F0A94ED4C0213740260BE97489EC4540D08D89DE73293740364BF2CBBCED454050E97E076C323740FE66C0986EEE454008CCD5BF063C37401EA420454BF04540F03510143D53374002E419DB7BF1454028C445FD147C374096FBB1A4B7F0454078176A1FBB973740EE4904DD30EB4540B0D38A41F09E3740D6F15ED6C0EA4540D05A36ECD7A23740EE91B22484EA454080E95E4883B83740926CAE0744EC4540F89EA5C62CBE3740DE015695DDEB4540B085F0A7C7CC3740CEE2F568C3E8454040BB5B914C2638400A4CCDA99AE045405049314FCD283840A2406A4360E04540608D015332563840A22A62222FE14540301BE533B55B38404EF5FF3F48E14540A8656F592060384062844267C6E1454078C70F2D636E38404A66188BA7E54540C098FA1B62773840E64A48A1B5E64540107C43FD088038407EA1E9F455E64540A0C68C5069A938408A1CF95BB9E0454040F74060A2B43840DAC15AB233DF4540E878213EACC038400A86FD2291DE4540605CAB63B3F63840AE14180BF3DF454010083CC3E3143940BA0DCC0C06DC45406057C44B183639408A2DF7E81EDB4540A06B314F994039400215E5D731DA454060BF8E491049394016AF1BBE5ED845408800CD85E9493940727CABEA2FD84540B05EF244B2523940EA476726B9D5454010768107105C394006CEA4B0BFD34540B87A479A33673940A6B631D634D34540583A5C48136D3940EA889F13C3D34540E046B29DA87739406EE7A6CD78D54540904772D8817B394052E48836B9D54540B88F324F1F7D39403A274C3ED4D54540C0048E7BA88839405AA7884897D54540508DB39D7A8E3940B6141151CED545401050EA0A37933940E26B299CB4D6454020A212CA07983940623AFE2220D7454080693E26D39D39404EC9911F08D84540A021B53A4DA33940EE92198B40D94540F0EEAB004CA739400E7FB124A4DA454010C110CADFAB394006814067D2DB45401016DB79A2BB39408E9D230401DC4540D0C3783056BD3940BA0DCC0C06DC45406807BE11F9C73940CA9E9D76B2DD4540D00AC011EDCD3940A61403DD44E14540900A47FD66CE394022C348A1BFE1454008A5BB74DED639405ACE5595EBE44540201301B98ADE3940A21B3AAD83E64540780AD75C9BEA39403A6FDE9D14EC454010FAC4AEABEC3940760A4821E7ED4540D0984ED41AEF394056AA85AB66EF4540900A47FDE60D3A403E1D03DD97F7454050B160E5C70F3A40D645842B92F9454070B5CD224E143A40DA4E89C809FC4540B0104A37C01D3A400EA22FD6DAFF4540D83C678296263A4056ED6F7D96014640F094927B403B3A405A3C7FF184034640F06F2A157E4F3A402E9841E7BB064640D8356FF013553A40DACC2ED3070746406084EE41716A3A406A0A303B2A0846407010D1223A9D3A401606178BDC0A464040712678D3A53A40DEDD7EF1F30B4640E8EFF67EF5AA3A40F2F9C5D22B0C464030DA84A464AD3A406E5A862B6C0C4640C0DC60827CB23A40966FDC80940D464010886F596CB53A40CE07188BD60D46404842D022F2C03A40CE07188BD60D4640089B95B519CA3A405A3E15EED70E4640F08E281556E23A40BAC2583209144640B8BFDC9664003B40B61D404A2215464088812FB208073B40DA073473A916464050AB661FA9193B4002F867A67E12464048403A269F343B40E26ADE1D8B10464060C1C1AE0B3A3B40EAB878B7730F4640B07B76304A403B408E7742E7A90F4640A009A27CAE403B40A2A821EE8D0F4640D04DDBF9DD443B4062A20A17630E4640105F13CAA9433B4082E4F6687D0B46401064A6290C493B40063CB22446094640E8011DA177573B402A78B224CB064640481B0A90825A3B40D2C43473CB05464020E80E2D755F3B40DE03DE1DA70246409081382643623B40864C188BEE01464090A204F313933B40222B717D15024640A03DD2BF28A23B4006193473CF034640E03BEDA701A83B40FE42F7680E03464010CFD22216AD3B409E8C606C2CFF454058F32C4DB9AE3B4062DDA9675EFE4540F0BE0A2DC1B83B400ACBA74D70F945401810203E8EC93B403EBC26FFEEFA45404047479A41DB3B4002D4828E8BFE4540B0E7EDA77DE93B405ECB66A625FF4540B0E7EDA77DE93B40BAEA694322FF4540F065CC8593EF3B40E2B4226271FB4540C8E7257823FB3B400AAB3B2DB7EC4540907F6A59CA033C405AF377B73EEA454010812A15A4383C40D655C09888E14540F01D1E3E406F3C409A1532731BDE4540501910B210943C40D2B2CE35E2DE4540007D0EB280933C40A87A248B1FDD4540B0766307B6923C4030B6D03596CD4540D063620756933C40204FD035F2CB4540B0E00CB2F0953C4040877AE0B4C94540805DB75C8B983C40D048258B27C845404083B95C4B9A3C40888D258BBFC64540007D0EB2809A3C40D0B2CE35E2C44540B076630736983C40680AD0359AC145409089640716943C40D893D0358ABD4540B0E00CB2708F3C4040D2258B17BA4540501910B2908B3C40B0107BE0A4B84540F0506107F6883C40B0107BE024B8454090896407167D3C40E019CF3506B4454070060FB2B07A3C40D893D035CAB2454090F30DB250793C400017268B2FB14540206A0DB220793C4040877AE0F4AE4540206A0DB2A0753C4088F7CE35BAB04540206A0DB2206A3C4008E8CF350EB345402096BA5CAB673C40F8CB7AE0CCB3454090F30DB2D0643C40F035248B07B54540E08F0FB2605E3C4068557BE0FCB54540D063620756523C40403CCF35D2B6454090896407964C3C40D048258B67B64540F0506107F6423C40680AD0359AB4454030570CB2403E3C40204FD03532B44540501910B2902D3C4088F7CE357AB4454030570CB240283C40204FD03532B445406070B85C6B1E3C40985ECF351EB24540B0E00CB270173C4030B6D03596AE45400013650746163C4078DB79E078AD4540F0506107F6073C40985ECF35DE9F4540007D0EB280043C40D893D035CA9D4540805DB75C0B003C40403CCF35929C45409089640796EE3B40680AD035DA9A45402096BA5CABEB3B40985ECF351E9A4540B0E00CB2F0E93B40C02CD035E69945406070B85C6BE73B40C02CD035E69945406070B85C6BE73B40E019CF350699454090F30DB250F03B4040D2258B97964540501910B210F23B4088427AE09C95454030570CB2C0F13B4088427AE01C944540D0636207D6EC3B40D893D0358A8E454090F30DB250E93B4040D2258B57884540007D0EB280E73B40C02CD0352687454030570CB240E53B4008E8CF354E86454030570CB240E33B40B0107BE06485454070060FB230E23B4060BF248BF78345402096BA5CABE23B40680AD0351A814540007D0EB280E63B40888D258B3F7B45406070B85C6BE73B4088427AE09C784540007D0EB200E63B4040877AE0F46F45406070B85C6BE73B40D893D0350A6E45404083B95C4BE23B4040D2258B976C454070060FB230E23B4068557BE07C6A454030570CB240E43B4088F7CE35BA6745402096BA5CABE53B40680AD0355A6445402096BA5CABE53B4068557BE03C5E4540805DB75C0BE53B4030B6D035D65B45406070B85C6BE43B40204FD035F25A454090F30DB250D73B40F880CF35AA5A4540501910B290C93B40D893D0358A5B454030570CB240BE3B40D048258BA75B4540501910B290BB3B4040877AE0745B454030570CB2C0B93B400017268BAF5A4540501910B210B83B40D048258BE758454030570CB240B73B40D0FD79E084574540805DB75C8BB73B4030B6D03556564540E08F0FB260B93B40A87A248B5F55454090F30DB250BD3B40985ECF359E544540F050610776B63B4028D5CE352E54454090F30DB250AB3B40B0107BE064524540501910B290A53B407871D035FE514540E08F0FB2E0A03B40B0C5CF3582504540E08F0FB260A13B40C02CD035264D4540B076630736A43B40403CCF3592494540B0E00CB270A63B4028D5CE356E47454090F30DB250A23B40D048258B27484540F0506107768A3B40D0B2CE3562484540B0E00CB2F0823B40D893D035CA464540206A0DB2A07F3B40D048258B2744454090F30DB2D07D3B407871D035FE4045406070B85CEB7A3B401804258BCF3D4540D063620756763B40F880CF35AA3E4540B0E00CB2F0733B4040877AE0743D4540501910B210743B40F8CB7AE08C3B4540B076630736773B40403CCF35523A45404083B95C4B763B400017268B6F394540805DB75C0B763B40E019CF35C6384540B076630736773B40403CCF35D23645406070B85CEB773B40F035248BC73745404083B95C4B783B40204FD035F23745402096BA5C2B783B4050A3CF35F6374540B076630736773B407871D0357E384540007D0EB200793B40985ECF351E3B4540007D0EB200813B4088F7CE35FA374540206A0DB2A0833B40204FD035B237454030570CB2C0863B40D893D035CA38454090F30DB2D0883B40E019CF35463A4540E08F0FB2608B3B40D0B2CE35E23A4540E08F0FB2E08F3B40985ECF355E39454070060FB230923B400017268BAF3A4540E08F0FB260943B40E019CF35863A4540F050610776963B40D893D035CA394540F050610776983B40985ECF355E394540501910B2109C3B40888D258BBF39454090896407969E3B40680AD0355A3A4540206A0DB220A13B4040877AE0B43A454070060FB2B0A43B40403CCF35523A4540501910B290A43B40F880CF35AA3945404083B95C4BA43B40D893D0358A3945404083B95CCBA33B40403CCF3592394540B0E00CB2F0A23B40985ECF355E394540F050610776A43B40D048258B27374540F0506107F6A63B40403CCF3592354540B0E00CB2F0AA3B40B0C5CF3542354540501910B210B13B40403CCF35D2364540805DB75C8BB13B40985ECF359E354540F0506107F6B23B4088F7CE35FA344540B076630736B53B40607479E0D4344540501910B210B83B40607479E014354540501910B210B83B40A87A248B1F3445409089640796B63B4050A3CF35F633454070060FB2B0B23B40888D258B3F3345409089640796B43B40888D258BFF3145402096BA5CABB53B4028D5CE35AE314540501910B210B83B40888D258B7F314540206A0DB2A0B53B4088F7CE353A304540D063620756B53B4088F7CE357A2E4540E08F0FB2E0B63B40204FD035B22C454090F30DB2D0B93B40D0B2CE35622B4540805DB75C8BBD3B40D893D035CA2A4540805DB75C8BC03B4028D5CE35EE2A4540B0766307B6C33B4008E8CF354E2B45404083B95CCBC73B40D0B2CE35622B45404083B95CCBC73B4030B6D035962A45404083B95C4BC63B40D0B2CE35E229454030570CB240C63B4028D5CE352E294540F050610776C73B4068557BE07C28454070060FB2B0C93B4008E8CF35CE27454090F30DB250C33B4030B6D035D62545404083B95C4BC03B4050A3CF3576234540007D0EB280C13B4030B6D035162145404083B95CCBC73B40C02CD035261F45402096BA5CABC63B4008E8CF35CE1E4540501910B290C63B4018B979E0AC1E4540501910B210C63B40204FD035321E45404083B95C4BCF3B4040877AE0F41B4540B0766307B6D03B40D0B2CE35221B45406070B85CEBD13B40204FD035321A4540D0636207D6D43B4050A3CF35F61845404083B95C4BD83B40403CCF35D217454070060FB230DB3B4040877AE0341745402096BA5CABDA3B40E019CF35C6164540F0506107F6D93B40E019CF35C6154540B0E00CB270D93B40D0B2CE3562154540805DB75C0BE13B407871D035BE134540007D0EB200E33B4088427AE0DC1245406070B85C6BE73B4030B6D035560F454070060FB230F53B40403CCF35120C4540E08F0FB2E0F63B40607479E0D40A4540007D0EB200F93B40C02CD035A6094540501910B290FC3B4088F7CE353A094540007D0EB280FB3B40403CCF35D207454030570CB2C0FC3B40A87A248B9F064540B076630736FF3B40D0B2CE35A20545404083B95CCB013C40680AD035DA044540B0E00CB2F0013C4050A3CF3536044540501910B290013C40D893D0350A044540E08F0FB2E0003C40F035248B07044540805DB75C0B003C40C02CD035E6034540F0506107F6023C4068557BE0BC024540E08F0FB2E0043C40607479E014014540B076630736053C4008E8CF354EFF4440805DB75C8B033C40F035248BC7FD44404083B95C4B043C4068557BE07CFC4440C8E7257823FB3B401E5AD54640FD444040950B9046F73B4086535DCFB6FD4440685FA8C6C2EA3B405616E8F42BFD444090072EB244E73B40B62FFF3F93FD4440F8CAFEB87EE03B40B28842E7CFFE4440681861E52BDA3B40C62EE5D76AFF4440E871C011D1D73B40AAD0DB0075FF4440E0E8A8C6F2D23B404ECE247F2AFF4440A8EAB23AE7D13B4062813E4A52FF4440B044BF74E2D03B40A6B2F0AE60FF44406001C44BDACF3B4062813E4A52FF44406816CBE8D8CE3B404ECE247F2AFF44405859921811CE3B4002EDB024E1FD4440F05293B50DCE3B40BEA1707D25FC444080A627787FCD3B40B281F6E8E2FA4440E8B823DB7AD13B406AEC6389F7F9444020BDE4D0B9D03B40D603EC9130F94440B067213E86CD3B40D22967A6B6F84440004C7DCD30CA3B40D27B34F3A9F8444010F402B9B2C63B409A62818E1AF94440C8C3783056B93B405A16FD22DAFB44409868FC1BE4AF3B406EE21A28FBFB4440380C102DFB9B3B40720A4FDB0BFA4440308BAD005C9B3B405235170BC5F84440C05997B5759A3B403251FA0525F84440C8E9F3441C993B407AA23B2D24F8444090C0E6D02D973B400E3D2EB9B0F84440A80FFFB816953B4076C8B641AAF74440582EA08C7B923B40BA5A1B2805F74440E86F2A15FE8C3B4066A355954CF6444050A9D022568D3B4016E6A3B0FEF5444080DD16040D8E3B408A655495CDF54440909A4FD4D48E3B4086A0C118A4F54440C0139B52888F3B40A2006F6076F5444098DA6D59B68E3B40DADEB4412FF54440A8EA06F31F8D3B40D2724484E0F444402870A329848C3B405E190FB497F44440804A11CA0F903B40EE1AA01306F444405834469AE18B3B40FA624E5B59F3444090E1B29D7E883B40DA2AAB6A3BF44440301ECF226C823B40AE866D6072F74440C010BAD78B7E3B40DAB9AE07AFF84440209EAE00BC6B3B40565CF2CBA2FC4440589DDC9698653B40CAE8EF2EA2FE4440A85D8BDEF55F3B40A6ACEF2E1D014540705B7CCD1C553B40860927FF5907454080217D6A274E3B407E298C65EE0945407889BF74FA453B409AB8B95EBE0B45403021CC85FB3C3B400A5C2CB9880C454010646E5966373B40A6D0B85E3D0C4540B801AD002C343B407A992FD6470B4540883F68BC7B2E3B409E894EDB6E084540F08D1567D22D3B4032E2939FEB07454050CF4B379C2D3B40AEA3067A7807454040DF6BBC7F2C3B402E0AB3244E0745409855A7634B263B40C28416EEE907454038176EF690203B402632BD7B0208454030FB5071B51D3B40C28416EEE9074540E0644937C4193B402AE422E2190945409012A8636D153B40CE08717D090A454010485A48C5103B4066A36309960A45407079AA5F430C3B4056536E1C9D0A454070C54160EA0B3B406A131CA89D0A454018CB6E59CA053B4036FEC55275094540D0082A151A003B407A9A88C87A054540F83B257827FB3A40CA9911D134044540C0A6BC7498F73A4016F3E2BAA5034540A83D0A904EF53A40CE2A739A55024540C08C227837F33A404AB1C335CA004540B87005F35BF03A40A6B04C3E84FF4440989045FD22EE3A40DE6E035D4CFF4440F0441CA155E93A407A4BB8DE96FF444028E1C9E8ACE63A404ECE247F2AFF4440B07E8F7BECE33A400622325622FE444030D8EEA791E13A40DEDC2CB925FE4440B8FA5E4829DF3A40220A973C7BFE444040CA2C4F5BDC3A402A9ADE9D73FE444048B3F6E19AD93A4026524584CEFD444098BD8B8F80D63A4042E71FFED3FC4440C82FB8D7E3D33A4002F5551501FC44402009DBF9C5D13A408E64FBA29AFB44404869B29DF4CE3A4086093573E3FB4440005B773038CA3A40FA82E4D76EFD44407038E233D3C73A4002EDB024E1FD4440C05DFB7EC1C43A400E88CBEF89FD4440A0D78CDE39BF3A406A5A638974FB4440803B334F9BBC3A40BE839A59BFFA4440C8EA22DBB2B73A401AC5D02989FA444070396582A29F3A407239CF8C09FC4440486700B90E9B3A408A29C435D4FB444058122F4FE7963A4032E8A293B8FA444030F2A48C858F3A4042D3DA80C4F7444070B482A4A48D3A403E4B23E23DF744402010203E0E8C3A40CADCFBA2A4F6444008572F4FFF8A3A40063A156ECEF544408095BC74728B3A40C69E96BC4DF5444060319DEF8A8E3A403E64517893F4444020E8D65C4F8F3A402E2CC3B523F4444080290D2D198F3A409223BCFB6BF34440A0FA2678038E3A40DA52DF1D4CF24440D04374F6B88D3A4096494C3EA0F1444008C35F848A8D3A4056CE3FB0D9F04440503CD65C538D3A40B6CB41E7EDEF4440780DF0A73D8C3A403E9559B29AED4440C8E612CA1F8A3A405ACFB54143EB444008C68741AF863A40D2C27BD480E944409892DBF9757A3A404AD0E4D719E844407039658222603A403EFC361087E84440D02C0F2D8D553A40E23FB4C10FE54440B00EB43AED513A40B684D0A9FAE14440B05AAA63F9503A40F2609C7633DF4440F047E133BF523A40E672759AA1DC4440486E45FD56553A408EFCD2C644DB4440
+RS	0106000020E6100000010000000103000000010000000F00000066098E03C3023440A8EF44B543154740CD4B3B420C5A3440D0A9B4AE4C1647409D8444F618F33440E60CFD90BBCE46407933B169D1403540AD28A009C16E4640EB45198303423540D28393EE07624640DF755EA81A983540A88429DF7F2B464038BB9C46687B354036E4C1E293814540E03702C163C33340156514AA91884540BED5C61278063340AE47B39774624640DBB0F6DF1304334080D17F3C087B46408E3B731CAFCA32402121D92DFF9046408DE81EAA4DC5324026F1490FF1F0464037AAE3B4DBD13240F8C6C8B1AFF446408A347073D3DA324010CC6BE4B9F5464066098E03C3023440A8EF44B543154740
 \.
 
 UPDATE users.responsibility_areas
--- a/schema/gemma.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/gemma.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -200,6 +200,11 @@
     measure_type varchar PRIMARY KEY
 );
 
+CREATE TYPE template_types AS ENUM (
+    'map',
+    'diagram',
+    'report'
+);
 
 -- Namespace for user management related data
 CREATE SCHEMA users
@@ -211,10 +216,11 @@
 
     CREATE TABLE templates (
         template_name varchar NOT NULL,
+        template_type template_types NOT NULL DEFAULT 'map'::template_types,
         country char(2) REFERENCES countries,
         template_data bytea NOT NULL,
         date_info timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
-        UNIQUE (template_name, country)
+        UNIQUE (template_name, template_type, country)
     )
     CREATE TRIGGER templates_date_info BEFORE UPDATE ON templates
         FOR EACH ROW EXECUTE PROCEDURE update_date_info()
@@ -267,80 +273,82 @@
 
 
     CREATE TABLE gauges (
-        location isrs PRIMARY KEY CHECK(
+        location isrs CHECK(
             (location).orc SIMILAR TO 'G[[:digit:]]{4}'
             AND CAST(substring((location).orc from 2 for 4) AS int) < 2048),
         objname varchar NOT NULL,
         geom geography(POINT, 4326) NOT NULL,
         applicability_from_km int8,
         applicability_to_km int8,
-        validity tstzrange,
-        -- pasted text from a more general specification is given
-        -- (a gauge is not a berth!)
-        -- TODO: Ranges need a joint exclusion constaint to prevent overlaps?
+        validity tstzrange NOT NULL,
         zero_point double precision NOT NULL,
         geodref varchar,
-        date_info timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
-        source_organization varchar
+        date_info timestamp with time zone NOT NULL,
+        source_organization varchar,
+        lastupdate timestamp with time zone NOT NULL,
+        -- entry removed from external data source (RIS-Index)/historicised:
+        erased boolean NOT NULL DEFAULT false,
+        CHECK (erased OR NOT isempty(validity)),
+        PRIMARY KEY (location, validity),
+        EXCLUDE USING GiST (isrs_astext(location) WITH =, validity WITH &&)
+            DEFERRABLE INITIALLY DEFERRED
     )
-    CREATE TRIGGER gauges_date_info BEFORE UPDATE ON gauges
-        FOR EACH ROW EXECUTE PROCEDURE update_date_info()
+    -- Allow only one non-erased entry per location
+    CREATE UNIQUE INDEX gauges_erased_unique_constraint
+        ON gauges (location)
+        WHERE NOT erased
 
     CREATE TABLE gauges_reference_water_levels (
-        gauge_id isrs NOT NULL REFERENCES gauges,
+        location isrs NOT NULL,
+        validity tstzrange NOT NULL,
+        FOREIGN KEY (location, validity) REFERENCES gauges ON UPDATE CASCADE,
         -- Omit foreign key constraint to be able to store not NtS-compliant
         -- names, too:
         depth_reference varchar NOT NULL, -- REFERENCES depth_references,
-        PRIMARY KEY (gauge_id, depth_reference),
+        PRIMARY KEY (location, validity, depth_reference),
         value int NOT NULL
     )
 
-    CREATE VIEW gauges_geoserver AS
-    SELECT
-        g.location,
-        isrs_asText(g.location) AS isrs_code,
-        g.objname,
-        g.geom,
-        g.applicability_from_km,
-        g.applicability_to_km,
-        g.validity,
-        g.zero_point,
-        g.geodref,
-        g.date_info,
-        g.source_organization,
-        json_strip_nulls(json_object_agg(coalesce(r.depth_reference,'empty'),
-                                                  r.value))
-            AS reference_water_levels
-    FROM gauges g LEFT JOIN LATERAL (
-            SELECT gauge_id, depth_reference, value
-            FROM gauges_reference_water_levels
-            ) r ON r.gauge_id = g.location
-    GROUP BY g.location
-
     CREATE TABLE gauge_measurements (
         id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
-        fk_gauge_id isrs NOT NULL REFERENCES gauges,
+        location isrs NOT NULL,
+        validity tstzrange NOT NULL,
+        CONSTRAINT gauge_key
+            FOREIGN KEY (location, validity) REFERENCES gauges
+            ON UPDATE CASCADE,
         measure_date timestamp with time zone NOT NULL,
+        CHECK (measure_date <@ validity),
         country_code char(2) NOT NULL REFERENCES countries,
-        -- TODO: add relations to stuff provided as enumerations
-        sender varchar NOT NULL, -- "from" attribute from DRC
+        sender varchar NOT NULL, -- "from" element from NtS response
         language_code varchar NOT NULL REFERENCES language_codes,
         date_issue timestamp with time zone NOT NULL,
         reference_code varchar(4) NOT NULL REFERENCES depth_references,
         water_level double precision NOT NULL,
-        predicted boolean NOT NULL,
-        is_waterlevel boolean NOT NULL,
-        -- XXX: "measure_code" if really only W or Q
-        -- XXX: Do we need "unit" attribute or can we normalise on import?
-        value_min double precision, -- XXX: NOT NULL if predicted?
-        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"
+        date_info timestamp with time zone NOT NULL,
+        source_organization varchar NOT NULL, -- "originator" from NtS response
         staging_done boolean NOT NULL DEFAULT false,
-        -- So we can have a staged and
-        -- a non-staged fk_gauge_id/measure_date pair.
-        UNIQUE (fk_gauge_id, measure_date, staging_done)
+        UNIQUE (location, measure_date, staging_done)
+    )
+
+    CREATE TABLE gauge_predictions (
+        location isrs NOT NULL,
+        validity tstzrange NOT NULL,
+        CONSTRAINT gauge_key
+            FOREIGN KEY (location, validity) REFERENCES gauges
+            ON UPDATE CASCADE,
+        measure_date timestamp with time zone NOT NULL,
+        CHECK (measure_date >= lower(validity)),
+        country_code char(2) NOT NULL REFERENCES countries,
+        sender varchar NOT NULL, -- "from" element from NtS response
+        language_code varchar NOT NULL REFERENCES language_codes,
+        date_issue timestamp with time zone NOT NULL,
+        reference_code varchar(4) NOT NULL REFERENCES depth_references,
+        water_level double precision NOT NULL,
+        conf_interval numrange
+            CHECK (conf_interval @> CAST(water_level AS numeric)),
+        date_info timestamp with time zone NOT NULL,
+        source_organization varchar NOT NULL, -- "originator" from NtS response
+        PRIMARY KEY (location, measure_date, date_issue)
     )
 
     CREATE TABLE waterway_axis (
@@ -375,27 +383,6 @@
         related_enc varchar(12)
     )
 
-    -- A table to help geoserver serve the distance marks as WFS 1.1.0.
-    -- At least geoserver-2.13.2 does not serve type geography correctly
-    -- and does not serve the location_code as isrs type
-    CREATE VIEW distance_marks_geoserver AS
-        SELECT location_code,
-               isrs_asText(location_code) AS location,
-               geom::Geometry(POINT, 4326),
-               related_enc,
-               (location_code).hectometre
-            FROM distance_marks_virtual
-
-    CREATE VIEW distance_marks_ashore_geoserver AS
-        SELECT id,
-               country,
-               geom::Geometry(POINT, 4326),
-               related_enc,
-               hectom,
-               catdis,
-               position_code
-            FROM distance_marks
-
     -- We need to configure primary keys for the views used by
     -- geoserver for wfs, otherwise it will generate ids on the fly,
     -- which will change for the same feature...
@@ -426,6 +413,9 @@
         staging_done boolean NOT NULL DEFAULT false,
         UNIQUE(name, staging_done)
     )
+    CREATE TRIGGER stretches_date_info
+        BEFORE UPDATE ON stretches
+        FOR EACH ROW EXECUTE PROCEDURE update_date_info()
 
     CREATE TABLE stretch_countries (
         stretches_id int NOT NULL REFERENCES stretches(id)
@@ -434,25 +424,21 @@
         UNIQUE(stretches_id, country_code)
     )
 
-    -- Published view for GeoServer
-    CREATE VIEW stretches_geoserver AS SELECT
-        id,
-        name,
-        (stretch).lower::varchar as lower,
-        (stretch).upper::varchar as upper,
-        area::Geometry(MULTIPOLYGON, 4326),
-        objnam,
-        nobjnam,
-        date_info,
-        source_organization,
-        (SELECT string_agg(country_code, ', ')
-            FROM stretch_countries
-            WHERE stretches_id = id) AS countries,
-        staging_done
-    FROM stretches
-
-
-    CREATE TRIGGER sections_stretches_date_info
+    -- Like stretches without the countries
+    CREATE TABLE sections (
+        id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
+        name varchar NOT NULL,
+        section isrsrange NOT NULL,
+        area geography(MULTIPOLYGON, 4326) NOT NULL
+            CHECK(ST_IsValid(CAST(area AS geometry))),
+        objnam varchar NOT NULL,
+        nobjnam varchar,
+        date_info timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
+        source_organization varchar NOT NULL,
+        staging_done boolean NOT NULL DEFAULT false,
+        UNIQUE(name, staging_done)
+    )
+    CREATE TRIGGER sections_date_info
         BEFORE UPDATE ON stretches
         FOR EACH ROW EXECUTE PROCEDURE update_date_info()
 
@@ -500,14 +486,14 @@
     --
     -- Bottlenecks
     --
-    -- XXX: Nullability differs between DRC (attributes marked "O") and WSDL
-    -- (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 (
         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.
+        gauge_location isrs NOT NULL,
+        gauge_validity tstzrange NOT NULL,
+        CONSTRAINT gauge_key
+            FOREIGN KEY (gauge_location, gauge_validity) REFERENCES gauges
+                ON UPDATE CASCADE,
         objnam varchar,
         nobjnm varchar,
         stretch isrsrange NOT NULL,
@@ -516,7 +502,7 @@
         rb char(2) REFERENCES countries, -- from rb_lb in interface
         lb char(2) REFERENCES countries, -- from rb_lb in interface
         responsible_country char(2) NOT NULL REFERENCES countries,
-        revisiting_time smallint NOT NULL,
+        revisiting_time smallint,
         limiting varchar NOT NULL REFERENCES limiting_factors,
         -- surtyp varchar NOT NULL REFERENCES survey_types,
         -- XXX: Also an attribut of sounding result?
@@ -535,7 +521,8 @@
         FOR EACH ROW EXECUTE PROCEDURE update_date_info()
 
     CREATE TABLE bottlenecks_riverbed_materials (
-        bottleneck_id int NOT NULL REFERENCES bottlenecks(id),
+        bottleneck_id int NOT NULL REFERENCES bottlenecks(id)
+            ON DELETE CASCADE,
         riverbed varchar NOT NULL REFERENCES riverbed_materials,
         -- XXX: should be 'natsur' according to IENC Encoding Guide M.4.3
         PRIMARY KEY (bottleneck_id, riverbed)
@@ -565,17 +552,6 @@
             -- CHECK(ST_IsSimple(CAST(lines AS geometry))),
         PRIMARY KEY (sounding_result_id, height)
     )
-    -- A view to help geoserver serve contour lines.
-    -- At least geoserver-2.13.2 does not serve type geography correctly
-    CREATE VIEW sounding_results_contour_lines_geoserver AS
-        SELECT bottleneck_id,
-                date_info,
-                height,
-                CAST(lines AS geometry(multilinestring, 4326)) AS lines
-            FROM sounding_results_contour_lines cl
-                JOIN sounding_results sr
-                    ON sr.id = cl.sounding_result_id
-
     --
     -- Fairway availability
     --
@@ -634,66 +610,6 @@
         CHECK(measure_type = 'minimum guaranteed'
             OR value_lifetime IS NOT NULL)
     )
-
-    CREATE VIEW bottleneck_overview AS
-    SELECT
-      objnam AS name,
-      ST_Centroid(area)::Geometry(POINT, 4326) AS point,
-      (lower(stretch)).hectometre AS from,
-      (upper(stretch)).hectometre AS to,
-      sr.current::text
-    FROM bottlenecks bn LEFT JOIN (
-      SELECT bottleneck_id, max(date_info) AS current FROM sounding_results
-      GROUP BY bottleneck_id) sr ON sr.bottleneck_id = bn.id
-    ORDER BY objnam
-
-    -- Published view for GeoServer
-    CREATE VIEW bottlenecks_geoserver AS
-    WITH fairway_availability_latest AS (
-        SELECT DISTINCT ON (bottleneck_id) bottleneck_id,date_info,critical
-        FROM fairway_availability
-        ORDER BY bottleneck_id, date_info DESC NULLS LAST),
-    gauge_measurements_waterlevel AS (
-        SELECT DISTINCT ON (fk_gauge_id)
-            fk_gauge_id, measure_date, predicted, water_level
-        FROM gauge_measurements WHERE predicted ='false'
-        ORDER BY fk_gauge_id, measure_date DESC NULLS LAST)
-    SELECT
-        b.id,
-        b.bottleneck_id,
-        b.objnam,
-        b.nobjnm,
-        b.stretch,
-        b.area,
-        b.rb,
-        b.lb,
-        b.responsible_country,
-        b.revisiting_time,
-        b.limiting,
-        b.date_info,
-        b.source_organization,
-        g.location AS gauge_isrs_code,
-        g.objname AS gauge_objname,
-        json_strip_nulls(json_object_agg(coalesce(r.depth_reference,'empty'),
-                                                  r.value))
-            AS reference_water_levels,
-        fal.date_info AS fa_date_info,
-        fal.critical AS fa_critical,
-        gmw.water_level as gm_waterlevel
-    FROM bottlenecks b LEFT JOIN gauges g ON b.fk_g_fid = g.location
-        LEFT JOIN LATERAL (
-            SELECT gauge_id,depth_reference,value
-            FROM gauges_reference_water_levels
-            ) r ON r.gauge_id = b.fk_g_fid
-        LEFT JOIN LATERAL (
-            SELECT bottleneck_id,date_info,critical
-            FROM  fairway_availability_latest
-            WHERE b.id=bottleneck_id) fal ON TRUE
-        LEFT JOIN LATERAL (
-            SELECT water_level
-            FROM  gauge_measurements_waterlevel
-            WHERE b.fk_g_fid=fk_gauge_id) gmw ON TRUE
-    GROUP BY b.id, g.location, fal.date_info, fal.critical, gmw.water_level;
 ;
 
 -- Configure primary keys for geoserver views
@@ -702,7 +618,8 @@
   ('waterway', 'distance_marks_geoserver', 'location_code'),
   ('waterway', 'distance_marks_ashore_geoserver', 'id'),
   ('waterway', 'bottlenecks_geoserver', 'id'),
-  ('waterway', 'stretches_geoserver', 'id');
+  ('waterway', 'stretches_geoserver', 'id'),
+  ('waterway', 'sections_geoserver', 'id');
 
 --
 -- Import queue and respective logging
@@ -825,18 +742,4 @@
     )
 ;
 
-CREATE VIEW waterway.sounding_differences AS SELECT
-    sd.id           AS id,
-    bn.objnam       AS objnam,
-    srm.date_info   AS minuend,
-    srs.date_info   AS subtrahend,
-    sdcl.height     AS height,
-    CAST(sdcl.lines AS geometry(multilinestring, 4326)) AS lines
-FROM
-    caching.sounding_differences sd JOIN
-    caching.sounding_differences_contour_lines sdcl ON sd.id = sdcl.sounding_differences_id JOIN
-    waterway.sounding_results srm ON sd.minuend    = srm.id JOIN
-    waterway.sounding_results srs ON sd.subtrahend = srs.id JOIN
-    waterway.bottlenecks bn ON srm.bottleneck_id = bn.id;
-
 COMMIT;
--- a/schema/geo_functions.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/geo_functions.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -4,19 +4,25 @@
 -- SPDX-License-Identifier: AGPL-3.0-or-later
 -- License-Filename: LICENSES/AGPL-3.0.txt
 
--- Copyright (C) 2018 by via donau
+-- Copyright (C) 2018, 2019 by via donau
 --   – Österreichische Wasserstraßen-Gesellschaft mbH
 -- Software engineering by Intevation GmbH
 
 -- Author(s):
 --  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+--  * Tom Gottfried <tom@intevation.de>
 
-CREATE OR REPLACE FUNCTION best_utm(g geometry) RETURNS integer AS
+CREATE OR REPLACE FUNCTION best_utm(g geography) RETURNS integer AS
 $$
 DECLARE
   center geometry;
 BEGIN
-  SELECT ST_Centroid(g) INTO center;
+  -- Centroid should be calculated on geography to get accurate results
+  -- from lon/lat coordinates, but the respective PostGIS function returns
+  -- POINT(-NaN NaN) for some invalid polygons, while the calculation on
+  -- geometry seems to give reasonable approximations in this context.
+  SELECT ST_Centroid(CAST(g AS geometry)) INTO center;
+
   RETURN
     CASE WHEN ST_Y(center) > 0 THEN
     32600
@@ -27,20 +33,3 @@
 $$
 LANGUAGE plpgsql
 IMMUTABLE;
-
-CREATE OR REPLACE FUNCTION utm_covers(g geography) RETURNS boolean AS
-$$
-DECLARE
-  user_area geometry;
-  utm integer;
-BEGIN
-    SELECT area::geometry FROM users.responsibility_areas INTO user_area
-    WHERE country = users.current_user_country();
-    SELECT best_utm(user_area) INTO utm;
-    RETURN ST_Covers(
-      ST_Transform(user_area, utm),
-      ST_Transform(g::geometry, utm));
-END;
-$$
-LANGUAGE plpgsql
-STABLE;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/schema/geoserver_views.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -0,0 +1,187 @@
+CREATE OR REPLACE VIEW waterway.gauges_geoserver AS
+    SELECT
+        g.location,
+        isrs_asText(g.location) AS isrs_code,
+        g.objname,
+        g.geom,
+        g.applicability_from_km,
+        g.applicability_to_km,
+        g.validity,
+        g.zero_point,
+        g.geodref,
+        g.date_info,
+        g.source_organization,
+        r.rwls AS reference_water_levels,
+        wl.measure_date AS gm_measuredate,
+        wl.water_level AS gm_waterlevel,
+        wl_14d.n AS gm_n_14d,
+        fca.forecast_accuracy_3d,
+        fca.forecast_accuracy_1d
+    FROM waterway.gauges g
+        LEFT JOIN (SELECT location, validity,
+                    json_strip_nulls(json_object_agg(
+                        coalesce(depth_reference, 'empty'), value)) AS rwls
+                FROM waterway.gauges_reference_water_levels
+                GROUP BY location, validity) AS r
+            USING (location, validity)
+        LEFT JOIN (SELECT DISTINCT ON (location)
+                    location,
+                    measure_date,
+                    water_level
+                FROM waterway.gauge_measurements
+                ORDER BY location, measure_date DESC) AS wl
+            USING (location)
+        LEFT JOIN (SELECT location, count(water_level) AS n
+                FROM waterway.gauge_measurements
+                -- consider all measurements within 14 days plus a tolerance
+                WHERE measure_date
+                    >= current_timestamp - '14 days 00:15'::interval
+                GROUP BY location) AS wl_14d
+            USING (location)
+        LEFT JOIN (SELECT location,
+                    max(acc) FILTER (WHERE
+                        measure_date <= current_timestamp + '1 day'::interval)
+                        AS forecast_accuracy_1d,
+                    max(acc) AS forecast_accuracy_3d
+                FROM waterway.gauge_predictions,
+                    GREATEST(water_level - lower(conf_interval),
+                        upper(conf_interval) - water_level) AS acc (acc)
+                WHERE measure_date
+                    BETWEEN current_timestamp
+                        AND current_timestamp + '3 days'::interval
+                GROUP BY location) AS fca
+            USING (location)
+    WHERE NOT g.erased;
+
+CREATE OR REPLACE VIEW waterway.distance_marks_geoserver AS
+    SELECT location_code,
+       isrs_asText(location_code) AS location,
+       geom::Geometry(POINT, 4326),
+       related_enc,
+       (location_code).hectometre
+    FROM waterway.distance_marks_virtual;
+
+CREATE OR REPLACE VIEW waterway.distance_marks_ashore_geoserver AS
+    SELECT id,
+       country,
+       geom::Geometry(POINT, 4326),
+       related_enc,
+       hectom,
+       catdis,
+       position_code
+    FROM waterway.distance_marks;
+
+CREATE OR REPLACE VIEW waterway.bottlenecks_geoserver AS
+    WITH
+    fairway_availability_latest AS (
+        SELECT DISTINCT ON (bottleneck_id) bottleneck_id, date_info, critical
+            FROM waterway.fairway_availability
+            ORDER BY bottleneck_id, date_info DESC),
+    sounding_result_latest AS (
+        SELECT DISTINCT ON (bottleneck_id) bottleneck_id, max(date_info) AS date_max
+            FROM waterway.sounding_results
+            GROUP BY bottleneck_id
+            ORDER BY bottleneck_id DESC)
+    SELECT
+        b.id,
+        b.bottleneck_id,
+        b.objnam,
+        b.nobjnm,
+        b.stretch,
+        b.area,
+        b.rb,
+        b.lb,
+        b.responsible_country,
+        b.revisiting_time,
+        b.limiting,
+        b.date_info,
+        b.source_organization,
+        g.location AS gauge_isrs_code,
+        g.objname AS gauge_objname,
+        g.reference_water_levels,
+        fal.date_info AS fa_date_info,
+        fal.critical AS fa_critical,
+        g.gm_measuredate,
+        g.gm_waterlevel,
+        g.gm_n_14d,
+        srl.date_max,
+        g.forecast_accuracy_3d,
+        g.forecast_accuracy_1d
+    FROM waterway.bottlenecks b
+        LEFT JOIN waterway.gauges_geoserver g
+            ON b.gauge_location = g.location AND b.gauge_validity = g.validity
+        LEFT JOIN fairway_availability_latest fal
+            ON b.id = fal.bottleneck_id
+        LEFT JOIN sounding_result_latest srl
+            ON b.id = srl.bottleneck_id;
+
+CREATE OR REPLACE VIEW waterway.stretches_geoserver AS
+    SELECT
+        id,
+        name,
+        (stretch).lower::varchar as lower,
+        (stretch).upper::varchar as upper,
+        area::Geometry(MULTIPOLYGON, 4326),
+        objnam,
+        nobjnam,
+        date_info,
+        source_organization,
+        (SELECT string_agg(country_code, ', ')
+            FROM waterway.stretch_countries
+            WHERE stretches_id = id) AS countries,
+        staging_done
+    FROM waterway.stretches;
+
+CREATE OR REPLACE VIEW waterway.sections_geoserver AS
+    SELECT
+        id,
+        name,
+        (section).lower::varchar as lower,
+        (section).upper::varchar as upper,
+        area::Geometry(MULTIPOLYGON, 4326),
+        objnam,
+        nobjnam,
+        date_info,
+        source_organization,
+        staging_done
+    FROM waterway.sections;
+
+CREATE OR REPLACE VIEW waterway.sounding_results_contour_lines_geoserver AS
+    SELECT bottleneck_id,
+        date_info,
+        height,
+        CAST(lines AS geometry(multilinestring, 4326)) AS lines
+    FROM waterway.sounding_results_contour_lines cl
+        JOIN waterway.sounding_results sr ON sr.id = cl.sounding_result_id;
+
+CREATE OR REPLACE VIEW waterway.bottleneck_overview AS
+    SELECT
+        objnam AS name,
+        ST_Centroid(area)::Geometry(POINT, 4326) AS point,
+        (lower(stretch)).hectometre AS from,
+        (upper(stretch)).hectometre AS to,
+        sr.current::text,
+        responsible_country
+    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.id
+    ORDER BY objnam;
+
+CREATE OR REPLACE VIEW waterway.sounding_differences AS
+    SELECT
+        sd.id           AS id,
+        bn.objnam       AS objnam,
+        srm.date_info   AS minuend,
+        srs.date_info   AS subtrahend,
+        sdcl.height     AS height,
+        CAST(sdcl.lines AS geometry(multilinestring, 4326)) AS lines
+    FROM caching.sounding_differences sd
+        JOIN caching.sounding_differences_contour_lines sdcl
+            ON sd.id = sdcl.sounding_differences_id
+        JOIN waterway.sounding_results srm
+            ON sd.minuend = srm.id
+        JOIN waterway.sounding_results srs
+            ON sd.subtrahend = srs.id
+        JOIN waterway.bottlenecks bn
+            ON srm.bottleneck_id = bn.id;
--- a/schema/install-db.sh	Wed May 29 10:58:45 2019 +0200
+++ b/schema/install-db.sh	Mon Jun 03 10:19:18 2019 +0200
@@ -130,6 +130,7 @@
        -f "$BASEDIR/search_functions.sql" \
        -f "$BASEDIR/geonames.sql" \
        -f "$BASEDIR/manage_users.sql" \
+       -f "$BASEDIR/geoserver_views.sql" \
        -f "$BASEDIR/auth.sql" \
        -f "$BASEDIR/isrs_functions.sql" \
        -f "$BASEDIR/default_sysconfig.sql"
--- a/schema/isrs.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/isrs.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -47,10 +47,101 @@
     CHECK (is_country((VALUE).country_code))
     CHECK ((VALUE).hectometre BETWEEN 0 AND 99999);
 
+CREATE FUNCTION isrs_cmp(a isrs, b isrs) RETURNS int
+AS $$
+    /* TODO: handle non-matching combinations of country_codes and
+       fairway_sections. Otherwise, this will give unexpected results if
+       both hectometre values do not refer to the same river. */
+    SELECT (a).hectometre - (b).hectometre
+$$ LANGUAGE sql
+    IMMUTABLE PARALLEL SAFE;
+
+CREATE FUNCTION isrslt(a isrs, b isrs) RETURNS boolean
+AS $$
+    SELECT isrs_cmp(a, b) < 0
+$$ LANGUAGE sql
+    IMMUTABLE PARALLEL SAFE;
+
+CREATE FUNCTION isrsle(a isrs, b isrs) RETURNS boolean
+AS $$
+    SELECT isrs_cmp(a, b) <= 0
+$$ LANGUAGE sql
+    IMMUTABLE PARALLEL SAFE;
+
+CREATE FUNCTION isrseq(a isrs, b isrs) RETURNS boolean
+AS $$
+    SELECT isrs_cmp(a, b) = 0
+$$ LANGUAGE sql
+    IMMUTABLE PARALLEL SAFE;
+
+CREATE FUNCTION isrsge(a isrs, b isrs) RETURNS boolean
+AS $$
+    SELECT isrs_cmp(a, b) >= 0
+$$ LANGUAGE sql
+    IMMUTABLE PARALLEL SAFE;
+
+CREATE FUNCTION isrsgt(a isrs, b isrs) RETURNS boolean
+AS $$
+    SELECT isrs_cmp(a, b) > 0
+$$ LANGUAGE sql
+    IMMUTABLE PARALLEL SAFE;
+
+CREATE OPERATOR <~ (
+    leftarg = isrs,
+    rightarg = isrs,
+    function = isrslt
+);
+
+CREATE OPERATOR <~= (
+    leftarg = isrs,
+    rightarg = isrs,
+    function = isrsle
+);
+
+CREATE OPERATOR ~= (
+    leftarg = isrs,
+    rightarg = isrs,
+    function = isrseq,
+    commutator = ~=
+);
+
+CREATE OPERATOR >~= (
+    leftarg = isrs,
+    rightarg = isrs,
+    function = isrsge,
+    commutator = <~=,
+    negator = <~
+);
+
+CREATE OPERATOR >~ (
+    leftarg = isrs,
+    rightarg = isrs,
+    function = isrsgt,
+    commutator = <~,
+    negator = <~=
+);
+
+CREATE OPERATOR CLASS isrs_ops FOR TYPE isrs USING btree AS
+    OPERATOR 1 <~,
+    OPERATOR 2 <~=,
+    OPERATOR 3 ~=,
+    OPERATOR 4 >~=,
+    OPERATOR 5 >~,
+    FUNCTION 1 isrs_cmp;
+
+CREATE FUNCTION isrs_diff(a isrs, b isrs) RETURNS double precision
+AS $$
+    SELECT CAST(isrs_cmp(a, b) AS double precision)
+$$ LANGUAGE sql
+    IMMUTABLE PARALLEL SAFE;
+
 CREATE TYPE isrsrange AS RANGE (
-    subtype = isrs
+    subtype = isrs,
+    subtype_opclass = isrs_ops,
+    subtype_diff = isrs_diff
 );
 
+
 --
 -- Functions
 --
--- a/schema/isrs_functions.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/isrs_functions.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -54,24 +54,34 @@
     -- in m, up to which linestrings will be connected at their boundary
 ) RETURNS geometry
 AS $$
+DECLARE z int;
 DECLARE result_geom geometry;
 BEGIN
+    -- Find best matchting UTM zone
+    z = best_utm(stretch);
+
+    CREATE TEMP TABLE axis AS
+        SELECT id, wtwaxs, ST_Boundary(wtwaxs) AS bdr
+            FROM (SELECT id, ST_Transform(wtwaxs::geometry, z) AS wtwaxs
+                FROM waterway.waterway_axis) AS axs;
+    CREATE INDEX axs_bdr ON axis USING GiST (bdr);
+    ANALYZE axis;
+
     WITH RECURSIVE
-        utm_zone AS (
-            -- Find best matchting UTM zone
-            SELECT best_utm(stretch) AS z),
-        axis AS (
-            SELECT id, 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
-        points AS (
+        points0 AS (
             SELECT ST_ClosestPoint(
                     wtwaxs,
                     ST_Transform(geom, z)) AS geom
-                FROM ST_Dump(ISRSrange_points(stretch)), utm_zone, (
+                FROM ST_Dump(ISRSrange_points(stretch)), (
                     SELECT ST_Collect(wtwaxs) AS wtwaxs
                         FROM axis) AS ax),
+        -- Ensure two distinct points on axis have been found
+        points AS (
+            SELECT geom
+                FROM points0
+                WHERE 2 = (SELECT count(DISTINCT geom) FROM points0)),
         axis_snapped AS (
             -- Iteratively connect non-contiguous axis chunks
             -- to find the contiguous axis on which given distance marks lie
@@ -91,19 +101,19 @@
                         UNION
                         -- Fill eventual gap
                         SELECT ST_ShortestLine(
-                                ST_Boundary(refgeom), ST_Boundary(geom))
+                                ST_Boundary(refgeom), bdr)
                         UNION
                         -- Linestring to be added
                         SELECT geom)))
                 FROM axis_snapped AS axis_snapped (refids, refgeom),
-                    axis AS axis (id, geom),
+                    axis AS axis (id, geom, bdr),
                     (SELECT ST_Collect(points.geom) AS pts
                         FROM points) AS points
                 WHERE id <> ALL(refids)
                     AND ST_DWithin(
-                        ST_Boundary(refgeom), ST_Boundary(geom), tolerance)
+                        ST_Boundary(refgeom), bdr, tolerance)
                     AND NOT ST_Covers(ST_Buffer(refgeom, 0.0001), points.pts)
-                ORDER BY ST_Distance(ST_Boundary(refgeom), ST_Boundary(geom))
+                ORDER BY ST_Boundary(refgeom) <-> bdr
                 FETCH FIRST ROW ONLY)),
         axis_segment AS (
             -- Fetch end result from snapping
@@ -119,8 +129,8 @@
         -- end of the resulting linestring, that significantly differ from
         -- the direction of the input linestring due to finite precision
         -- of the calculation. The generated small segment of the
-        -- resulting line leads to unexpected results of the buffer with
-        -- endcap=flat in the CTE below.
+        -- resulting line would lead e.g. to unexpected results in an area
+        -- generated by ISRSrange_area().
         SELECT ST_SimplifyPreserveTopology(ST_LineSubstring(
                     axis_segment.line, min(fractions.f), max(fractions.f)),
                 0.0001) AS line
@@ -130,11 +140,17 @@
                 FROM points) AS fractions
         GROUP BY axis_segment.line;
 
+    -- Drop temporary table to avoid side effects on PostgreSQL's MVCC,
+    -- because otherwise subsequent invocations of the function will not see
+    -- changes on the underlying waterway.waterway_axis that might have
+    -- occured.
+    DROP TABLE axis;
+
     RETURN result_geom;
 END;
     $$
     LANGUAGE plpgsql
-    STABLE PARALLEL SAFE;
+    PARALLEL RESTRICTED;
 
 -- Clip an area to a stretch given by a geometry representing an axis (e.g.
 -- the output of ISRSrange_axis()).
@@ -149,16 +165,21 @@
     area geometry
 ) RETURNS geometry
 AS $$
+DECLARE
+    area_subset geometry;
+    result_geom geometry;
+BEGIN
+    -- In case area is a multipolygon, process the union of those
+    -- polygons, which intersect with the axis. The union is to avoid
+    -- problems with invalid/self-intersecting multipolygons
+    SELECT ST_Union(a_dmp.geom)
+        INTO STRICT area_subset
+        FROM (SELECT ST_MakeValid(ST_Transform(geom, ST_SRID(axis)))
+                FROM ST_Dump(area)) AS a_dmp (geom)
+        WHERE ST_Intersects(a_dmp.geom, axis)
+        HAVING ST_Union(a_dmp.geom) IS NOT NULL;
+
     WITH
-        area_subset AS (
-            -- In case area is a multipolygon, process the union of those
-            -- polygons, which intersect with the axis. The union is to avoid
-            -- problems with invalid/self-intersecting multipolygons
-            SELECT ST_Union(a_dmp.geom) AS area
-                FROM (SELECT ST_MakeValid(ST_Transform(geom, ST_SRID(axis)))
-                        FROM ST_Dump(area)) AS a_dmp (geom)
-                WHERE ST_Intersects(a_dmp.geom, axis)
-            ),
         rotated_ends AS (
             SELECT ST_Collect(ST_Scale(
                     ST_Translate(e,
@@ -169,18 +190,23 @@
                         FROM (VALUES (1), (-1)) AS idx (i)) AS ep,
                     ST_Rotate(ST_PointN(axis, i*2), pi()/2, p1) AS ep2 (p2),
                     ST_Makeline(p1, p2) AS e (e),
-                    area_subset,
-                    LATERAL (SELECT (ST_MaxDistance(p1, area) / ST_Length(e))
+                    LATERAL (
+                        SELECT (ST_MaxDistance(p1, area_subset) / ST_Length(e))
                             * 2) AS d (d)),
         range_area AS (
             -- Split area by orthogonal lines at the ends of the clipped axis
             SELECT (ST_Dump(ST_CollectionExtract(
-                    ST_Split(area, blade), 3))).geom
-                FROM area_subset, rotated_ends)
+                    ST_Split(area_subset, blade), 3))).geom
+                FROM rotated_ends)
         -- From the polygons returned by the last CTE, select only those
         -- around the clipped axis
         SELECT ST_Multi(ST_Transform(ST_Union(range_area.geom), ST_SRID(area)))
+            INTO result_geom
             FROM range_area
-            WHERE ST_Intersects(ST_Buffer(range_area.geom, -0.0001), axis)
+            WHERE ST_Intersects(ST_Buffer(range_area.geom, -0.0001), axis);
+
+    RETURN result_geom;
+END;
     $$
-    LANGUAGE sql;
+    LANGUAGE plpgsql
+    STABLE PARALLEL SAFE;
--- a/schema/isrs_tests.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/isrs_tests.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -15,6 +15,9 @@
 -- pgTAP test script for ISRS location code types and functions
 --
 
+--
+-- Conversion from/to text
+--
 SELECT results_eq($$
     SELECT isrs_fromText('DEBON03901G007906548')
     $$,
@@ -34,6 +37,53 @@
     ,
     'isrs_asText() is the inverse of isrs_fromText()');
 
+--
+-- Comparison operators
+--
+SELECT ok(
+    isrs_fromText('DEBON03901G007906548')
+        <> isrs_fromText('DEXXX039010000006548'),
+    'Different codes at equal hectometre do not equal by default');
+
+SELECT ok(
+    isrs_fromText('DEBON03901G007906548')
+        ~= isrs_fromText('DEXXX039010000006548')
+    AND isrs_fromText('DEBON03901G007906548')
+        >~= isrs_fromText('DEXXX039010000006548')
+    AND isrs_fromText('DEBON03901G007906548')
+        <~= isrs_fromText('DEXXX039010000006548'),
+    'isrs_ops: Different codes at equal hectometre compare as equal');
+
+SELECT ok(
+    isrs_fromText('DEBON03901G007906549')
+        >~ isrs_fromText('DEXXX039010000006548')
+    AND isrs_fromText('DEBON03901G007906549')
+        >~= isrs_fromText('DEXXX039010000006548')
+    AND isrs_fromText('DEXXX039010000006547')
+        <~= isrs_fromText('DEBON03901G007906548')
+    AND isrs_fromText('DEXXX039010000006547')
+        <~ isrs_fromText('DEBON03901G007906548'),
+    'isrs_ops: Ordering depends on hectometre');
+
+SELECT ok(
+    isrsrange(isrs_fromText('DEXXX039010000006540'),
+        isrs_fromText('DEXXX039010000006560'))
+    @> isrs_fromText('ATXXX000000000006550')
+    AND isrs_fromText('DEXXX039010000006560')
+    <@ isrsrange(isrs_fromText('ATXXX000000000006550'),
+        isrs_fromText('ATXXX000000000006570')),
+    'isrsrange: ''Contains'' depends on hectometre');
+
+SELECT ok(
+    isrsrange(isrs_fromText('DEXXX039010000006540'),
+        isrs_fromText('DEXXX039010000006560'))
+    && isrsrange(isrs_fromText('ATXXX000000000006550'),
+        isrs_fromText('ATXXX000000000006570')),
+    'isrsrange: Overlap depends on hectometre');
+
+--
+-- Geometry processing
+--
 SELECT throws_ok($$
     SELECT ISRSrange_points(isrsrange(
             ('AT', 'XXX', '00001', '00000', 0)::isrs,
@@ -58,14 +108,21 @@
         5)),
     'ISRSrange_axis returns a valid simple feature');
 
-SELECT ok(
-    ISRSrange_area(ISRSrange_axis(isrsrange(
+SELECT throws_ok($$
+    SELECT ISRSrange_area('LINESTRING(0 0, 1 1)', NULL)
+    $$,
+    'P0002', NULL,
+    'ISRSrange_area fails if no input area is given');
+
+SELECT throws_ok($$
+    SELECT ISRSrange_area(ISRSrange_axis(isrsrange(
                 ('AT', 'XXX', '00001', '00000', 0)::isrs,
                 ('AT', 'XXX', '00001', '00000', 1)::isrs),
             5),
-        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');
+        ST_SetSRID('POLYGON((0 1, 0 2, 1 2, 1 1, 0 1))'::geometry, 4326))
+    $$,
+    'P0002', NULL,
+    'ISRSrange_area fails, if given area does not intersect with axis');
 
 SELECT results_eq($$
     SELECT every(ST_DWithin(
--- a/schema/manage_users.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/manage_users.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -4,7 +4,7 @@
 -- SPDX-License-Identifier: AGPL-3.0-or-later
 -- License-Filename: LICENSES/AGPL-3.0.txt
 
--- Copyright (C) 2018 by via donau
+-- Copyright (C) 2018, 2019 by via donau
 --   – Österreichische Wasserstraßen-Gesellschaft mbH
 -- Software engineering by Intevation GmbH
 
@@ -68,6 +68,31 @@
     STABLE PARALLEL SAFE;
 
 
+CREATE OR REPLACE FUNCTION users.current_user_area_utm()
+    RETURNS geometry
+    AS $$
+        DECLARE utm_area geometry;
+        BEGIN
+            SELECT ST_Transform(area::geometry, best_utm(area))
+                INTO STRICT utm_area
+                FROM users.responsibility_areas
+                WHERE country = users.current_user_country();
+            RETURN utm_area;
+        END;
+    $$
+    LANGUAGE plpgsql
+    STABLE PARALLEL SAFE;
+
+
+CREATE OR REPLACE FUNCTION users.utm_covers(g geography) RETURNS boolean AS
+    $$
+        SELECT ST_Covers(a, ST_Transform(g::geometry, ST_SRID(a)))
+            FROM users.current_user_area_utm() AS a (a)
+    $$
+    LANGUAGE SQL
+    STABLE PARALLEL SAFE;
+
+
 CREATE OR REPLACE FUNCTION internal.create_user() RETURNS trigger
 AS $$
 BEGIN
--- a/schema/manage_users_tests.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/manage_users_tests.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -4,7 +4,7 @@
 -- SPDX-License-Identifier: AGPL-3.0-or-later
 -- License-Filename: LICENSES/AGPL-3.0.txt
 
--- Copyright (C) 2018 by via donau
+-- Copyright (C) 2018, 2019 by via donau
 --   – Österreichische Wasserstraßen-Gesellschaft mbH
 -- Software engineering by Intevation GmbH
 
@@ -17,10 +17,23 @@
 
 SET search_path TO public, gemma, gemma_waterway, gemma_fairway;
 
+SET SESSION AUTHORIZATION test_user_at;
+--
+-- Utility functions
+--
+SELECT results_eq($$
+    SELECT ST_SRID(users.current_user_area_utm())
+    $$,
+    $$
+    SELECT best_utm(area)
+        FROM users.responsibility_areas
+        WHERE country = users.current_user_country()
+    $$,
+    'Geometry has SRID corresponding to best_utm()');
+
 --
 -- Role listing
 --
-SET SESSION AUTHORIZATION test_user_at;
 SELECT results_eq($$
     SELECT username FROM users.list_users
     $$,
--- a/schema/run_tests.sh	Wed May 29 10:58:45 2019 +0200
+++ b/schema/run_tests.sh	Mon Jun 03 10:19:18 2019 +0200
@@ -28,7 +28,10 @@
     -c 'SET client_min_messages TO WARNING' \
     -c "DROP ROLE IF EXISTS $TEST_ROLES" \
     -f tap_tests_data.sql \
-    -c 'SELECT plan(63)' \
+    -c "SELECT plan(70 + (
+            SELECT count(*)::int
+                FROM information_schema.tables
+                WHERE table_schema = 'waterway'))" \
     -f gemma_tests.sql \
     -f isrs_tests.sql \
     -f auth_tests.sql \
--- a/schema/search_functions.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/search_functions.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -69,12 +69,32 @@
 END;
 $$;
 
+CREATE OR REPLACE FUNCTION search_sections(search_string text) RETURNS jsonb
+  LANGUAGE plpgsql
+  AS $$
+DECLARE
+  _result jsonb;
+BEGIN
+  SELECT COALESCE(json_agg(r),'[]')
+    INTO _result
+    FROM (SELECT objnam AS name,
+                 ST_AsGeoJSON(ST_Centroid(area))::json AS geom,
+                 'section' AS type
+            FROM waterway.sections
+            WHERE objnam ILIKE '%' || search_string || '%'
+               OR nobjnam ILIKE '%' || search_string || '%'
+          ORDER BY name) r;
+  RETURN _result;
+END;
+$$;
+
 CREATE OR REPLACE FUNCTION search_most(search_string text) RETURNS jsonb
   LANGUAGE plpgsql
   AS $$
 BEGIN
   RETURN search_bottlenecks(search_string)
          || search_gauges(search_string)
+         || search_sections(search_string)
          || search_cities(search_string);
 END;
 $$;
--- a/schema/tap_tests_data.sql	Wed May 29 10:58:45 2019 +0200
+++ b/schema/tap_tests_data.sql	Mon Jun 03 10:19:18 2019 +0200
@@ -37,22 +37,30 @@
 
 INSERT INTO limiting_factors VALUES ('depth'), ('width');
 
-INSERT INTO waterway.gauges (
-    location, objname, geom, zero_point, source_organization)
+WITH
+gs AS (
+    INSERT INTO waterway.gauges (
+        location,
+        validity,
+        objname,
+        geom,
+        zero_point,
+        date_info,
+        source_organization,
+        lastupdate)
     VALUES (
         ('AT', 'XXX', '00001', 'G0001', 1)::isrs,
+        tstzrange(current_timestamp - '1 day'::interval, current_timestamp),
         'testgauge',
         ST_geomfromtext('POINT(0 0)', 4326),
         0,
-        'testorganization'
-    );
-
-INSERT INTO waterway.bottlenecks (
-    bottleneck_id, fk_g_fid, stretch, area, rb, lb, responsible_country,
-    revisiting_time, limiting, source_organization, staging_done)
+        current_timestamp,
+        'testorganization',
+        current_timestamp)
+    RETURNING location, validity),
+bns AS (
     VALUES (
         'testbottleneck1',
-        ('AT', 'XXX', '00001', 'G0001', 1)::isrs,
         isrsrange(('AT', 'XXX', '00001', '00000', 0)::isrs,
             ('AT', 'XXX', '00001', '00000', 2)::isrs),
         ST_geomfromtext('MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 4326),
@@ -60,13 +68,17 @@
         1, 'depth', 'testorganization', false
     ), (
         'testbottleneck2',
-        ('AT', 'XXX', '00001', 'G0001', 1)::isrs,
         isrsrange(('AT', 'XXX', '00001', '00000', 0)::isrs,
             ('AT', 'XXX', '00001', '00000', 2)::isrs),
         ST_geomfromtext('MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 4326),
         'AT', 'AT', 'AT',
         1, 'depth', 'testorganization', true
-    );
+    ))
+INSERT INTO waterway.bottlenecks (
+    gauge_location, gauge_validity,
+    bottleneck_id, stretch, area, rb, lb, responsible_country,
+    revisiting_time, limiting, source_organization, staging_done)
+    SELECT * FROM gs, bns;
 
 INSERT INTO waterway.distance_marks_virtual VALUES (
     ('AT', 'XXX', '00001', '00000', 0)::isrs,