Mercurial > gemma
view pkg/models/sr.go @ 5637:7f9600ab5461
merge
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Wed, 14 Jun 2023 08:45:11 +0200 |
parents | 1222b777f51f |
children |
line wrap: on
line source
// This is Free Software under GNU Affero General Public License v >= 3.0 // without warranty, see README.md and license for details. // // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // // Copyright (C) 2018 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann <sascha.teichmann@intevation.de> // * Bernhard E. Reiter <bernhard.reiter@intevation.de> package models import ( "context" "database/sql" "encoding/json" "errors" "fmt" "io" "gemma.intevation.de/gemma/pkg/common" ) // SurveyType is the type of a survey. type SurveyType string const ( // SurveyTypeMultiBeam is multi beam scan. SurveyTypeMultiBeam = SurveyType("multi") // SurveyTypeSingleBeam is a single beam scan. SurveyTypeSingleBeam = SurveyType("single") // SurveyTypeMarking is a scan from a marking vessel. SurveyTypeMarking = SurveyType("marking") ) // SoundingResultMeta is the JSON structure of // a metadata.json file. type SoundingResultMeta struct { Date Date `json:"date"` Bottleneck string `json:"bottleneck"` EPSG uint `json:"epsg"` DepthReference string `json:"depth-reference"` SingleBeam *bool `json:"single-beam,omitempty"` // kept in for compat! SurveyType SurveyType `json:"survey-type,omitempty"` NegateZ bool `json:"negate-z,omitempty"` } // UnmarshalJSON implements the json.Unmarshaler interface. func (st *SurveyType) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { return err } switch x := SurveyType(s); x { case SurveyTypeMultiBeam, SurveyTypeSingleBeam, SurveyTypeMarking: *st = x return nil default: return fmt.Errorf("unkown survey type '%s'", s) } } const ( checkDepthReferenceSQL = ` SELECT EXISTS(SELECT 1 FROM waterway.bottlenecks bn JOIN waterway.gauges g ON bn.gauge_location = g.location AND $3::timestamptz <@ g.validity JOIN waterway.gauges_reference_water_levels rl ON g.location = rl.location AND g.validity = rl.validity WHERE bn.bottleneck_id = $1 AND rl.depth_reference = $2)` checkBottleneckSQL = ` SELECT true FROM waterway.bottlenecks WHERE bottleneck_id = $1` checkBottleneckDateUniqueSQL = ` SELECT true FROM waterway.sounding_results sr JOIN waterway.bottlenecks bn ON sr.bottleneck_id = bn.bottleneck_id WHERE bn.bottleneck_id = $1 AND sr.date_info = $2` ) // Decode deserializes a metadata.json and checks if // the data fields met the needed constraints. func (m *SoundingResultMeta) Decode(r io.Reader) error { if err := json.NewDecoder(r).Decode(m); err != nil { return err } if m.EPSG == 0 { m.EPSG = WGS84 } if m.SingleBeam != nil { // Check if single-beam and survey-type match. if m.SurveyType != "" { if (*m.SingleBeam && m.SurveyType != SurveyTypeSingleBeam) || (!*m.SingleBeam && m.SurveyType != SurveyTypeMultiBeam) { return errors.New("'single-beam' and 'survey-type' mismatch") } } else { // Only single-beam given if *m.SingleBeam { m.SurveyType = SurveyTypeSingleBeam } else { m.SurveyType = SurveyTypeMultiBeam } } // Kill single-beam m.SingleBeam = nil } if m.SurveyType == "" { // default to multi-beam m.SurveyType = SurveyTypeMultiBeam } return nil } // Validate validates the metadata.json data against the // constraints in the database. func (m *SoundingResultMeta) Validate(ctx context.Context, conn *sql.Conn) []error { var errs []error var b bool err := conn.QueryRowContext(ctx, checkBottleneckSQL, m.Bottleneck).Scan(&b) switch { case err == sql.ErrNoRows: errs = append(errs, fmt.Errorf("unknown bottleneck '%s'", m.Bottleneck)) case err != nil: errs = append(errs, err) case !b: errs = append(errs, errors.New("unexpected bottleneck")) } if m.DepthReference != "ZPG" { err = conn.QueryRowContext(ctx, checkDepthReferenceSQL, m.Bottleneck, m.DepthReference, m.Date.Time).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 { case err == sql.ErrNoRows: // good! -> unique. case err != nil: errs = append(errs, err) case b: errs = append(errs, fmt.Errorf("sounding result for date %s already exists at bottleneck '%s'", m.Date.Time.Format(common.DateFormat), m.Bottleneck)) } return errs }