Mercurial > gemma
changeset 2976:ac5ba5a0e963 unified_import
merge with default
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Tue, 09 Apr 2019 08:51:02 +0200 |
parents | 2a29bf8776d0 (current diff) 7c301ff449bc (diff) |
children | ab26fb7a76f6 |
files | |
diffstat | 7 files changed, 505 insertions(+), 155 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/Maplayer.vue Mon Apr 08 17:17:01 2019 +0200 +++ b/client/src/components/Maplayer.vue Tue Apr 09 08:51:02 2019 +0200 @@ -49,7 +49,6 @@ name: "maplayer", data() { return { - projection: "EPSG:3857", splitscreen: false }; }, @@ -77,25 +76,28 @@ methods: { buildVectorLoader( featureRequestOptions, - endpoint, 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: 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); + // 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( - endpoint, - new XMLSerializer().serializeToString(featureRequest), + "/internal/wfs", + new XMLSerializer().serializeToString( + new WFS().writeGetFeature(featureRequestOptions) + ), { headers: { "X-Gemma-Auth": localStorage.getItem("token"), @@ -104,27 +106,18 @@ } ) .then(response => { - var features = new GeoJSON().readFeatures( + const features = new GeoJSON().readFeatures( JSON.stringify(response.data) ); if (featurePostProcessor) { features.map(f => featurePostProcessor(f)); } 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) { const exists = bottleneck_id != "does_not_exist"; @@ -183,7 +176,7 @@ 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 + projection: "EPSG:3857" }) }); map.on("moveend", event => { @@ -228,58 +221,46 @@ // TODO make display of layers more dynamic, e.g. from a list - // load different fairway dimension layers (level of service) - [ - "FAIRWAYDIMENSIONSLOS1", - "FAIRWAYDIMENSIONSLOS2", - "FAIRWAYDIMENSIONSLOS3" - ].forEach((los, i) => { - // loading the full WFS layer without bboxStrategy - var source = this.layers[los].getSource(); - var loader = function() { - var featureRequest = new WFS().writeGetFeature({ - srsName: "EPSG:3857", - featureNS: "gemma", - featurePrefix: "gemma", + this.layers.FAIRWAYDIMENSIONSLOS1.getSource().setLoader( + this.buildVectorLoader( + { featureTypes: ["fairway_dimensions"], - outputFormat: "application/json", - filter: equalTo("level_of_service", i + 1) - }); + filter: equalTo("level_of_service", 1) + }, + this.layers.FAIRWAYDIMENSIONSLOS1.getSource(), + true + ) + ); - 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()); - }); - }; + this.layers.FAIRWAYDIMENSIONSLOS2.getSource().setLoader( + this.buildVectorLoader( + { + featureTypes: ["fairway_dimensions"], + filter: equalTo("level_of_service", 2) + }, + this.layers.FAIRWAYDIMENSIONSLOS2.getSource(), + true + ) + ); - this.layers[los].getSource().setLoader(loader); - }); + this.layers.FAIRWAYDIMENSIONSLOS3.getSource().setLoader( + this.buildVectorLoader( + { + featureTypes: ["fairway_dimensions"], + filter: equalTo("level_of_service", 3) + }, + this.layers.FAIRWAYDIMENSIONSLOS3.getSource(), + true + ) + ); // load following layers with bboxStrategy (using our request builder) this.layers.WATERWAYAREA.getSource().setLoader( this.buildVectorLoader( { - featureNS: "gemma", - featurePrefix: "gemma", featureTypes: ["waterway_area"], geometryName: "area" }, - "/internal/wfs", this.layers.WATERWAYAREA.getSource() ) ); @@ -287,12 +268,9 @@ this.layers.WATERWAYAXIS.getSource().setLoader( this.buildVectorLoader( { - featureNS: "gemma", - featurePrefix: "gemma", featureTypes: ["waterway_axis"], geometryName: "wtwaxs" }, - "/internal/wfs", this.layers.WATERWAYAXIS.getSource() ) ); @@ -300,12 +278,9 @@ this.layers.WATERWAYPROFILES.getSource().setLoader( this.buildVectorLoader( { - featureNS: "gemma", - featurePrefix: "gemma", featureTypes: ["waterway_profiles"], geometryName: "geom" }, - "/internal/wfs", this.layers.WATERWAYPROFILES.getSource() ) ); @@ -313,12 +288,9 @@ this.layers.DISTANCEMARKS.getSource().setLoader( this.buildVectorLoader( { - featureNS: "gemma", - featurePrefix: "gemma", featureTypes: ["distance_marks_ashore_geoserver"], geometryName: "geom" }, - "/internal/wfs", this.layers.DISTANCEMARKS.getSource() ) ); @@ -326,12 +298,9 @@ this.layers.DISTANCEMARKSAXIS.getSource().setLoader( this.buildVectorLoader( { - featureNS: "gemma", - featurePrefix: "gemma", featureTypes: ["distance_marks_geoserver"], geometryName: "geom" }, - "/internal/wfs", this.layers.DISTANCEMARKSAXIS.getSource() ) ); @@ -339,12 +308,9 @@ this.layers.GAUGES.getSource().setLoader( this.buildVectorLoader( { - featureNS: "gemma", - featurePrefix: "gemma", featureTypes: ["gauges_geoserver"], geometryName: "geom" }, - "/internal/wfs", this.layers.GAUGES.getSource() ) ); @@ -352,12 +318,9 @@ this.layers.STRETCHES.getSource().setLoader( this.buildVectorLoader( { - featureNS: "gemma", - featurePrefix: "gemma", featureTypes: ["stretches_geoserver"], geometryName: "area" }, - "/internal/wfs", this.layers.STRETCHES.getSource(), f => { if (f.getId() === this.selectedStretchId) { @@ -371,12 +334,9 @@ this.layers.BOTTLENECKSTATUS.getSource().setLoader( this.buildVectorLoader( { - featureNS: "gemma", - featurePrefix: "gemma", featureTypes: ["bottlenecks_geoserver"], geometryName: "area" }, - "/internal/wfs", this.layers.BOTTLENECKSTATUS.getSource() ) ); @@ -384,12 +344,9 @@ this.layers.BOTTLENECKS.getSource().setLoader( this.buildVectorLoader( { - featureNS: "gemma", - featurePrefix: "gemma", featureTypes: ["bottlenecks_geoserver"], geometryName: "area" }, - "/internal/wfs", this.layers.BOTTLENECKS.getSource() ) ); @@ -422,8 +379,6 @@ 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"); }
--- a/client/src/components/Pdftool.vue Mon Apr 08 17:17:01 2019 +0200 +++ b/client/src/components/Pdftool.vue Tue Apr 09 08:51:02 2019 +0200 @@ -791,7 +791,7 @@ if ( this.selectedBottleneck && this.selectedSurvey && - this.BOTTLENECKISOLINE.getVisible() + this.layers.BOTTLENECKISOLINE.getVisible() ) { let survey = this.selectedSurvey;
--- a/client/src/components/layers/Layerselect.vue Mon Apr 08 17:17:01 2019 +0200 +++ b/client/src/components/layers/Layerselect.vue Tue Apr 09 08:51:02 2019 +0200 @@ -15,10 +15,10 @@ {{ label }} </label> </div> - <div v-if="layer.getVisible() && isBottleneckIsolineLayer"> + <div v-if="layer.getVisible() && layer === layers.BOTTLENECKISOLINE"> <img class="rounded my-1 d-block" :src="isolinesLegendImgDataURL" /> </div> - <div v-if="layer.getVisible() && isBottleneckDifferences"> + <div v-if="layer.getVisible() && layer === layers.DIFFERENCES"> <img class="rounded my-1 d-block" :src="differencesLegendImgDataURL" /> </div> </div> @@ -40,12 +40,13 @@ * * Bernhard Reiter <bernhard.reiter@intevation.de> */ import { HTTP } from "@/lib/http"; +import { displayError } from "@/lib/errors"; import { mapState } from "vuex"; export default { props: ["layer"], components: { - LegendElement: () => import("./LegendElement.vue") + LegendElement: () => import("./LegendElement") }, computed: { ...mapState("map", [ @@ -53,12 +54,6 @@ "isolinesLegendImgDataURL", "differencesLegendImgDataURL" ]), - isBottleneckIsolineLayer() { - return this.layer == this.layers.BOTTLENECKISOLINE; - }, - isBottleneckDifferences() { - return this.layer == this.layers.DIFFERENCES; - }, label() { return this.$gettext(this.layer.get("label")); } @@ -66,46 +61,45 @@ methods: { visibilityToggled() { this.layer.setVisible(!this.layer.getVisible()); + }, + 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 === this.layers.BOTTLENECKISOLINE) { + this.loadLegendImage( + "sounding_results_contour_lines_geoserver", + "isolinesLegendImgDataURL" + ); } - if (this.isBottleneckDifferences) { - const src = - "/internal/wms?REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH=20&HEIGHT=20&LAYER=sounding_differences&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/differencesLegendImgDataURL", this.result); - }; - reader.readAsDataURL(response.data); - }); + if (this.layer === this.layers.DIFFERENCES) { + this.loadLegendImage( + "sounding_differences", + "differencesLegendImgDataURL" + ); } } };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/controllers/bottlenecks.go Tue Apr 09 08:51:02 2019 +0200 @@ -0,0 +1,379 @@ +// 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 ( + "database/sql" + "fmt" + "log" + "net/http" + "sort" + "strconv" + "strings" + "time" + + "gemma.intevation.de/gemma/pkg/common" + "github.com/gorilla/mux" +) + +const ( + selectAvailableDepthSQL = ` +WITH data AS ( + SELECT + efa.measure_date, + efa.available_depth_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 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 bns.fk_g_fid = grwl.gauge_id +WHERE bns.objnam = $1 AND ( + grwl.depth_reference like 'HDC%' OR + grwl.depth_reference like 'LDC%' OR + grwl.depth_reference like 'MW%' +) +` +) + +type ( + availReferenceValue struct { + level int + value int + } + + availMeasurement struct { + when time.Time + depth int + value int + } +) + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func interpolate( + m1, m2 *availMeasurement, + diff time.Duration, +) int { + tdiff := m2.when.Sub(m1.when) + + // f(0) = m1.value + // f(tdiff) = m2.value + // f(x) = m*x + b + // m1.value = m*0 + b <=> b = m1.value + // m2.value = m*tdiff + b <=> m = (m2.value - b)/tdiff + // f(diff) = diff*(m2.value - m1.value)/tdiff + m1.value + + return int(diff*time.Duration(m2.value-m1.value)/tdiff) + m1.value +} + +func classifyAvailMeasurements( + from, to time.Time, + measurements []availMeasurement, + classes []availReferenceValue, +) []time.Duration { + + results := make([]time.Duration, len(classes)+2) + + if from.Before(measurements[0].when) { + results[len(results)-1] = measurements[0].when.Sub(from) + from = measurements[0].when + } + + if to.After(measurements[len(measurements)-1].when) { + results[len(results)-1] += to.Sub(measurements[len(measurements)-1].when) + to = measurements[len(measurements)-1].when + } + + for i := 0; i < len(measurements)-1; i++ { + p1 := &measurements[i] + p2 := &measurements[i+1] + tdiff := p2.when.Sub(p1.when) + if tdiff <= 0 { + continue + } + + if from.After(p2.when) || to.Before(p1.when) { + continue + } + + if from.After(p1.when) { + tdiff2 := from.Sub(p1.when) + vf := interpolate(p1, p2, tdiff2) + p1 = &availMeasurement{when: from, value: vf} + tdiff = p2.when.Sub(from) + } + + if to.Before(p2.when) { + tdiff2 := p2.when.Sub(to) + vt := interpolate(p1, p2, tdiff2) + p2 = &availMeasurement{when: to, value: vt} + tdiff = p2.when.Sub(p1.when) + } + + if max(p1.value, p2.value) <= classes[0].value { + results[0] += tdiff + continue + } + if min(p1.value, p2.value) > classes[len(classes)-1].value { + results[len(results)-2] += tdiff + continue + } + + // TODO: Do the real classes. + } + + return results +} + +func bottleneckAvailabilty( + _ interface{}, + req *http.Request, + conn *sql.Conn, +) (jr JSONResult, err error) { + bn := mux.Vars(req)["objnam"] + + if bn == "" { + err = JSONError{ + Code: http.StatusBadRequest, + Message: "Missing objnam of bottleneck", + } + return + } + + ctx := req.Context() + + loadReferenceValues := func() ([]availReferenceValue, error) { + rows, err := conn.QueryContext(ctx, selectGaugeLevelsSQL, bn) + if err != nil { + return nil, err + } + defer rows.Close() + + var levels []availReferenceValue + + loop: + for rows.Next() { + var what string + var value int + if err := rows.Scan(&what, &value); err != nil { + return nil, err + } + var level int + switch { + case strings.HasPrefix(what, "LDC"): + level = 0 + case strings.HasPrefix(what, "MW"): + level = 1 + case strings.HasPrefix(what, "HDC"): + level = 2 + default: + return nil, fmt.Errorf("Unexpected reference level type '%s'", what) + } + for i := range levels { + if levels[i].level == level { + levels[i].value = value + continue loop + } + } + levels = append(levels, availReferenceValue{level: level, value: value}) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + sort.Slice(levels, func(i, j int) bool { return levels[i].level < levels[j].level }) + + return levels, nil + } + + var refVals []availReferenceValue + if refVals, err = loadReferenceValues(); err != nil { + return + } + + if len(refVals) == 0 { + err = JSONError{ + Code: http.StatusNotFound, + Message: "No gauge reference values found for bottleneck", + } + } + + var from, to time.Time + + if f := req.FormValue("from"); f != "" { + if from, err = time.Parse(common.TimeFormat, f); err != nil { + err = JSONError{ + Code: http.StatusBadRequest, + Message: fmt.Sprintf("Invalid time format for 'from' field: %v", err), + } + return + } + from = from.UTC() + } else { + from = time.Now().AddDate(-1, 0, 0).UTC() + } + + if t := req.FormValue("to"); t != "" { + if to, err = time.Parse(common.TimeFormat, t); err != nil { + err = JSONError{ + Code: http.StatusBadRequest, + Message: fmt.Sprintf("Invalid time format for 'from' field: %v", err), + } + return + } + to = to.UTC() + } else { + to = from.AddDate(1, 0, 0).UTC() + } + + if to.Before(from) { + to, from = from, to + } + + log.Printf("info: time interval: (%v - %v)\n", from, to) + + var los int + if l := req.FormValue("los"); l != "" { + if los, err = strconv.Atoi(l); err != nil { + err = JSONError{ + Code: http.StatusBadRequest, + Message: fmt.Sprintf("Invalid value for field 'los': %v", err), + } + return + } + } else { + los = 1 + } + + loadDepthValues := func() ([]availMeasurement, error) { + + rows, err := conn.QueryContext( + ctx, selectAvailableDepthSQL, bn, los, from, to) + if err != nil { + return nil, err + } + defer rows.Close() + + var ms []availMeasurement + + for rows.Next() { + var m availMeasurement + if err := rows.Scan(&m.when, &m.depth, &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 + } + + var ms []availMeasurement + + if ms, err = loadDepthValues(); err != nil { + return + } + + if len(ms) == 0 { + err = JSONError{ + Code: http.StatusNotFound, + Message: "No available fairway depth values found", + } + return + } + + // TODO: Calculate the ranges. + + type outputLevel struct { + Level string `json:"level"` + Value int `json:"value"` + } + + type output struct { + Levels []outputLevel `json:"levels"` + } + + out := output{} + + for i := range refVals { + var level string + switch refVals[i].level { + case 0: + level = "LDC" + case 1: + level = "MW" + case 2: + level = "HDC" + } + out.Levels = append(out.Levels, outputLevel{ + Level: level, + Value: refVals[i].value, + }) + } + + jr = JSONResult{Result: &out} + + return +}
--- a/pkg/controllers/routes.go Mon Apr 08 17:17:01 2019 +0200 +++ b/pkg/controllers/routes.go Tue Apr 09 08:51:02 2019 +0200 @@ -298,6 +298,9 @@ })).Methods(http.MethodPut) // Handler to serve data to the client. + api.Handle("/data/bottleneck/availability/{objnam}", any(&JSONHandler{ + Handle: bottleneckAvailabilty, + })).Methods(http.MethodGet) api.Handle("/data/waterlevels/{gauge}", any( middleware.DBConn(http.HandlerFunc(waterlevels)))).Methods(http.MethodGet)
--- a/pkg/imports/sr.go Mon Apr 08 17:17:01 2019 +0200 +++ b/pkg/imports/sr.go Tue Apr 09 08:51:02 2019 +0200 @@ -431,18 +431,32 @@ var hasNegZ bool + const maxWarnings = 100 + var warnings int + + warn := func(format string, args ...interface{}) { + if warnings++; warnings <= maxWarnings { + feedback.Warn(format, args...) + } + } + defer func() { + if warnings > maxWarnings { + feedback.Warn("Too many warnings. %d ignored.", warnings-maxWarnings) + } + }() + 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:] @@ -451,19 +465,19 @@ 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") } } mpz = append(mpz, p)
--- a/pkg/models/sr.go Mon Apr 08 17:17:01 2019 +0200 +++ b/pkg/models/sr.go Tue Apr 09 08:51:02 2019 +0200 @@ -36,7 +36,12 @@ const ( checkDepthReferenceSQL = ` -SELECT true FROM depth_references WHERE depth_reference = $1` +SELECT EXISTS(SELECT 1 + FROM waterway.bottlenecks bn + JOIN waterway.gauges g ON g.location = bn.fk_g_fid + JOIN waterway.gauges_reference_water_levels rl ON rl.gauge_id = g.location + WHERE bn.objnam = $1 + AND rl.depth_reference = $2)` checkBottleneckSQL = ` SELECT true FROM waterway.bottlenecks WHERE objnam = $1` @@ -61,18 +66,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 +78,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 {