changeset 3538:5be5b54336ea password

merge with default
author Thomas Junk <thomas.junk@intevation.de>
date Fri, 31 May 2019 09:33:38 +0200
parents c64c47ff2ab1 (current diff) cbf883596e4e (diff)
children 60f48bc1498a
files
diffstat 13 files changed, 172 insertions(+), 152 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/components/fairway/Fairwayprofile.vue	Wed May 29 09:56:42 2019 +0200
+++ b/client/src/components/fairway/Fairwayprofile.vue	Fri May 31 09:33:38 2019 +0200
@@ -17,9 +17,15 @@
         </div>
         <div class="legend">
           <span
-            style="background-color: #4a2f06; width: 20px; height: 20px;"
+            style="width: 14px; height: 14px; background-color: #4a2f06; border: solid 3px black; background-clip: padding-box; box-sizing: content-box;"
           ></span>
-          Ground
+          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
@@ -96,9 +102,9 @@
       height: null,
       margin: {
         top: 20,
-        right: 40,
-        bottom: 30,
-        left: 40
+        right: 80,
+        bottom: 60,
+        left: 80
       },
       form: {
         template: null
@@ -141,6 +147,7 @@
     };
   },
   computed: {
+    ...mapGetters("map", ["openLayersMap"]),
     ...mapGetters("fairwayprofile", ["totalLength"]),
     ...mapState("fairwayprofile", [
       "additionalSurvey",
@@ -149,32 +156,21 @@
       "endPoint",
       "fairwayData",
       "maxAlt",
-      "referenceWaterLevel",
-      "selectedWaterLevel",
-      "waterLevels"
+      "selectedWaterLevel"
     ]),
+    ...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(", ")})`;
-    },
-    selectedSurvey: {
-      get() {
-        return this.$store.state.bottlenecks.selectedSurvey;
-      }
-    },
-    selectedBottleneck: {
-      get() {
-        return this.$store.state.bottlenecks.selectedBottleneck;
-      }
-    },
-    ...mapState("bottlenecks", ["selectedSurvey"]),
-    relativeWaterLevelDelta() {
-      return this.selectedWaterLevel.value - this.referenceWaterLevel;
+      } (${dates.join(", ")}) WL: ${waterlevelLabel} (${this.waterlevel} cm)`;
     },
     currentData() {
       if (
@@ -192,15 +188,20 @@
         return [];
       return this.currentProfile[this.additionalSurvey.date_info].points;
     },
-    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: {
@@ -371,7 +372,7 @@
           }
         });
       }
-      this.pdf.doc.save("Fairwayprofile diagram");
+      this.pdf.doc.save(this.title.replace(/\s/g, "_").replace(/[():,]/g, ""));
     },
     addDiagram(position, offset, width, height) {
       let x = offset.x,
@@ -429,36 +430,17 @@
       this.pdf.doc.circle(x, y + 10, 2, "FD");
       this.pdf.doc.text(x + 3, y + 11, "Ground");
     },
-    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;
-    },
     drawDiagram() {
       d3.select(".diagram-container svg").remove();
       this.scaleFairwayProfile();
       let svg = d3.select(".diagram-container").append("svg");
       svg.attr("width", "100%");
       svg.attr("height", "100%");
-      const width = this.width - this.margin.right - 1.5 * this.margin.left;
-      const height = this.height - this.margin.top - 2 * this.margin.bottom;
+      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
@@ -498,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) {
@@ -513,7 +494,7 @@
           .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", "transparent")
           .attr("d", fairwayArea);
       }
     },
@@ -521,14 +502,21 @@
       graph
         .append("text")
         .attr("transform", ["rotate(-90)"])
-        .attr("y", this.width - 70)
+        .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("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")
@@ -540,21 +528,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(
@@ -564,7 +566,7 @@
       graph
         .append("g")
         .attr("transform", "translate(0," + height + ")")
-        .call(xAxis.ticks(5))
+        .call(xAxis)
         .selectAll(".tick text")
         .attr("fill", "black")
         .select(function() {
@@ -575,7 +577,7 @@
       graph
         .append("g")
         .attr("transform", "translate(" + width + ",0)")
-        .call(yAxis2)
+        .call(yAxisRight)
         .selectAll(".tick text")
         .attr("fill", "black")
         .select(function() {
@@ -583,6 +585,18 @@
         })
         .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 };
     },
@@ -601,7 +615,7 @@
         .datum([{ x: 0, y: 0 }, { x: this.totalLength, y: 0 }])
         .attr("fill-opacity", 0.65)
         .attr("fill", WATER_COLOR)
-        .attr("stroke", WATER_COLOR)
+        .attr("stroke", "transparent")
         .attr("d", waterArea);
     },
     drawProfile({
@@ -620,25 +634,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
--- a/client/src/components/fairway/Profiles.vue	Wed May 29 09:56:42 2019 +0200
+++ b/client/src/components/fairway/Profiles.vue	Fri May 31 09:33:38 2019 +0200
@@ -37,7 +37,7 @@
         </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>
@@ -45,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>
@@ -327,7 +323,7 @@
     },
     selectedWaterLevel: {
       get() {
-        return this.$store.state.fairwayprofile.selectedWaterLevel.date || "";
+        return this.$store.state.fairwayprofile.selectedWaterLevel;
       },
       set(value) {
         this.$store.commit("fairwayprofile/setSelectedWaterLevel", value);
--- a/client/src/components/map/styles.js	Wed May 29 09:56:42 2019 +0200
+++ b/client/src/components/map/styles.js	Fri May 31 09:33:38 2019 +0200
@@ -247,20 +247,31 @@
         geom = new Point(getCenter(feature.getGeometry().getExtent()));
       }
       maps.forEach(m => {
-        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 (
+          (m.getLayer("BOTTLENECKS").getVisible() &&
+            feature.getId().indexOf("bottlenecks") > -1) ||
+          (m.getLayer("SECTIONS").getVisible() &&
+            feature.getId().indexOf("sections") > -1) ||
+          (m.getLayer("STRETCHES").getVisible() &&
+            feature.getId().indexOf("stretches") > -1) ||
+          (m.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 (
           m.getLayer("BOTTLENECKS").getVisible() &&
--- a/client/src/store/fairwayprofile.js	Wed May 29 09:56:42 2019 +0200
+++ b/client/src/store/fairwayprofile.js	Fri May 31 09:33:38 2019 +0200
@@ -28,9 +28,7 @@
     minAlt: 0,
     maxAlt: 0,
     currentProfile: {},
-    referenceWaterLevel: null,
-    waterLevels: {},
-    selectedWaterLevel: "",
+    selectedWaterLevel: "ref",
     fairwayData: [],
     startPoint: null,
     endPoint: null,
@@ -61,7 +59,7 @@
       state.additionalSurvey = additionalSurvey;
     },
     setSelectedWaterLevel: (state, level) => {
-      state.selectedWaterLevel = state.waterLevels[level];
+      state.selectedWaterLevel = level;
     },
     setDifferencesLoading: (state, value) => {
       state.differencesLoading = value;
@@ -69,8 +67,6 @@
     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;
@@ -79,12 +75,6 @@
       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
@@ -116,9 +106,7 @@
       state.fairwayData = [];
       state.startPoint = null;
       state.endPoint = null;
-      state.referenceWaterLevel = null;
-      state.waterLevels = {};
-      state.selectedWaterLevel = "";
+      state.selectedWaterLevel = "ref";
     },
     previousCuts: (state, previousCuts) => {
       state.previousCuts = previousCuts;
--- a/pkg/controllers/cross.go	Wed May 29 09:56:42 2019 +0200
+++ b/pkg/controllers/cross.go	Fri May 31 09:33:38 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/stretches.go	Wed May 29 09:56:42 2019 +0200
+++ b/pkg/controllers/stretches.go	Fri May 31 09:33:38 2019 +0200
@@ -418,7 +418,7 @@
 		return
 	}
 
-	from, ok := parseFormTime(rw, req, "form", time.Now().AddDate(-1, 0, 0))
+	from, ok := parseFormTime(rw, req, "from", time.Now().AddDate(-1, 0, 0))
 	if !ok {
 		return
 	}
--- a/pkg/imports/agm.go	Wed May 29 09:56:42 2019 +0200
+++ b/pkg/imports/agm.go	Fri May 31 09:33:38 2019 +0200
@@ -182,10 +182,12 @@
   staging_done
 ) VALUES (
   ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int),
-  (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)),
+  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,
--- a/pkg/imports/bn.go	Wed May 29 09:56:42 2019 +0200
+++ b/pkg/imports/bn.go	Fri May 31 09:33:38 2019 +0200
@@ -20,6 +20,7 @@
 	"errors"
 	"regexp"
 	"strconv"
+	"strings"
 	"time"
 
 	"gemma.intevation.de/gemma/pkg/soap/ifbn"
@@ -63,8 +64,10 @@
 ) VALUES (
   $1,
   isrs_fromText($2),
-  (SELECT validity FROM waterway.gauges
-     WHERE location = isrs_fromText($2) AND NOT erased),
+  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)),
@@ -268,10 +271,12 @@
 	rb, lb := splitRBLB(bn.Rb_lb)
 
 	var revisitingTime *int
-	if bn.Revisiting_time != nil {
+	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 '%s' to number of months",
+			feedback.Warn(
+				"Cannot convert given revisiting time '%s' to number of months",
 				*bn.Revisiting_time)
 		} else {
 			revisitingTime = &i
--- a/pkg/imports/errors.go	Wed May 29 09:56:42 2019 +0200
+++ b/pkg/imports/errors.go	Fri May 31 09:33:38 2019 +0200
@@ -57,10 +57,10 @@
 		switch err.SchemaName {
 		case "waterway":
 			switch err.TableName {
-			case "gauge_measurements":
+			case "gauge_measurements", "gauge_predictions", "bottlenecks":
 				switch err.ConstraintName {
 				case "gauge_key":
-					return "Referenced gauge is not in database"
+					return "Referenced gauge with matching temporal validity not available"
 				}
 			}
 		}
--- a/pkg/imports/gm.go	Wed May 29 09:56:42 2019 +0200
+++ b/pkg/imports/gm.go	Fri May 31 09:33:38 2019 +0200
@@ -73,10 +73,12 @@
   staging_done
 ) VALUES (
   ($1, $2, $3, $4, $5),
-  (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)),
+  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,
@@ -108,10 +110,12 @@
   source_organization
 ) VALUES(
   ($1, $2, $3, $4, $5),
-  (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)),
+  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,
--- a/pkg/imports/sr.go	Wed May 29 09:56:42 2019 +0200
+++ b/pkg/imports/sr.go	Fri May 31 09:33:38 2019 +0200
@@ -164,7 +164,8 @@
 
 	selectGaugeLDCSQL = `
 SELECT
-  grwl.value
+  grwl.value,
+  grwl.depth_reference
 FROM waterway.gauges_reference_water_levels grwl
   JOIN waterway.bottlenecks bns
     ON grwl.location = bns.gauge_location
@@ -208,7 +209,11 @@
 	if m.DepthReference == "ZPG" {
 		feedback.Info("Found ZPG as reference system -> translating Z values to LDC")
 		var ldc float64
-		err := conn.QueryRowContext(ctx, selectGaugeLDCSQL, m.Bottleneck).Scan(&ldc)
+		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")
@@ -218,7 +223,7 @@
 		xform = func(v octree.Vertex) octree.Vertex {
 			return octree.Vertex{X: v.X, Y: v.Y, Z: ldc - v.Z}
 		}
-		m.DepthReference = "LDC"
+		m.DepthReference = depthReference
 	}
 
 	if err := m.Validate(ctx, conn); err != nil {
--- a/pkg/imports/wg.go	Wed May 29 09:56:42 2019 +0200
+++ b/pkg/imports/wg.go	Fri May 31 09:33:38 2019 +0200
@@ -235,10 +235,10 @@
 			gauges = append(gauges, isrs)
 			feedback.Info("Processing %s", code)
 
-			// We need a valid time range to identify gauge versions in DB
+			// We need a valid, non-empty time range to identify gauge versions
 			if dr.Enddate != nil && dr.Startdate != nil &&
-				time.Time(*dr.Enddate).Before(time.Time(*dr.Startdate)) {
-				feedback.Warn("End date before start date")
+				!time.Time(*dr.Enddate).After(time.Time(*dr.Startdate)) {
+				feedback.Warn("End date not after start date")
 				unchanged++
 				continue
 			}
--- a/schema/gemma.sql	Wed May 29 09:56:42 2019 +0200
+++ b/schema/gemma.sql	Fri May 31 09:33:38 2019 +0200
@@ -280,7 +280,7 @@
         geom geography(POINT, 4326) NOT NULL,
         applicability_from_km int8,
         applicability_to_km int8,
-        validity tstzrange,
+        validity tstzrange NOT NULL,
         zero_point double precision NOT NULL,
         geodref varchar,
         date_info timestamp with time zone NOT NULL,
@@ -288,6 +288,7 @@
         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
@@ -490,9 +491,9 @@
         bottleneck_id varchar UNIQUE NOT NULL,
         gauge_location isrs NOT NULL,
         gauge_validity tstzrange NOT NULL,
-        FOREIGN KEY (gauge_location, gauge_validity) REFERENCES gauges
-            ON UPDATE CASCADE,
-        -- XXX: DRC references "ch. 3.1.1", which does not exist in document.
+        CONSTRAINT gauge_key
+            FOREIGN KEY (gauge_location, gauge_validity) REFERENCES gauges
+                ON UPDATE CASCADE,
         objnam varchar,
         nobjnm varchar,
         stretch isrsrange NOT NULL,