view pkg/models/sr.go @ 5427:235cfce555b5 marking-single-beam

Minor clean-up.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Sat, 10 Jul 2021 01:09:57 +0200
parents 850f5847d18a
children 7e8830c808ba
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"
)

type SurveyType string

const (
	SurveyTypeMultiBeam  = SurveyType("multi")
	SurveyTypeSingleBeam = SurveyType("single")
	SurveyTypeMarking    = SurveyType("marking")
)

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"`
	}
)

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.objnam = $1
    AND rl.depth_reference = $2)`

	checkBottleneckSQL = `
SELECT true FROM waterway.bottlenecks WHERE objnam = $1`

	checkBottleneckDateUniqueSQL = `
SELECT true FROM waterway.sounding_results sr JOIN
  waterway.bottlenecks bn ON sr.bottleneck_id = bn.bottleneck_id
WHERE bn.objnam = $1 AND sr.date_info = $2`
)

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
}

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
}