view pkg/imports/dmv.go @ 5671:4bfcbb747768

relenc is now optional in DMV data.
author Sascha Wilde <wilde@sha-bang.de>
date Fri, 15 Dec 2023 18:17:40 +0100
parents a4ecd66b5940
children 2dd155cc95ec
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,2023 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
//  * Sascha Wilde <wilde@intevation.de>

package imports

import (
	"context"
	"database/sql"
	"errors"
	"time"

	"gemma.intevation.de/gemma/pkg/models"
)

// DistanceMarksVirtual is a Job to import virtual distance marks
// from an external SOAP service into the database.
type DistanceMarksVirtual struct {
	// URL is the URL of the SOAP service.
	URL string `json:"url"`
	// Username is the username used to authenticate.
	Username string `json:"username"`
	// Passwort is the password to authenticate.
	Password string `json:"password"`
	// Insecure indicates if HTTPS traffic
	// should validate certificates or not.
	Insecure bool `json:"insecure"`
}

// Description gives a short info about relevant facts of this import.
func (dmv *DistanceMarksVirtual) Description([]string) (string, error) {
	return dmv.URL, nil
}

// DMVJobKind is the import queue type identifier.
const DMVJobKind JobKind = "dmv"

type dmvJobCreator struct{}

func init() { RegisterJobCreator(DMVJobKind, dmvJobCreator{}) }

func (dmvJobCreator) Description() string { return "distance marks virtual" }

func (dmvJobCreator) AutoAccept() bool { return true }

func (dmvJobCreator) Create() Job { return new(DistanceMarksVirtual) }

func (dmvJobCreator) Depends() [2][]string {
	return [2][]string{{"distance_marks_virtual"}}
}

// StageDone does nothing as there is no staging for distance marks virtual.
func (dmvJobCreator) StageDone(context.Context, *sql.Tx, int64, Feedback) error { return nil }

// CleanUp does nothing as there is nothing to cleanup with distance marks virtual.
func (*DistanceMarksVirtual) CleanUp() error { return nil }

const (
	insertDistanceMarksVirtualSQL = `
INSERT INTO waterway.distance_marks_virtual (
  location_code,
  geom,
  related_enc,
  wwname
)
VALUES (
  ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int),
  ST_SetSRID(ST_MakePoint($6, $7), 4326)::geography,
  $8,
  $9
) ON CONFLICT (location_code) DO UPDATE SET
  geom = ST_SetSRID(ST_MakePoint($6, $7), 4326)::geography,
  related_enc = $8,
  wwname = $9
`
)

// Do executes the actual downloading and importing
// of the virtual distance marks.
func (dmv *DistanceMarksVirtual) Do(
	ctx context.Context,
	importID int64,
	conn *sql.Conn,
	feedback Feedback,
) (interface{}, error) {

	start := time.Now()

	responseData, _, err := getRisData(
		ctx,
		conn,
		feedback,
		dmv.Username,
		dmv.Password,
		dmv.URL,
		dmv.Insecure,
		"dismar")
	if err != nil {
		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
	}
	defer insertStmt.Close()

	var ignored, features int

	for _, data := range responseData {
		for _, dr := range data.RisdataReturn {
			if dr.RisidxCode == nil {
				ignored++
				continue
			}

			code, err := models.IsrsFromString(string(*dr.RisidxCode))
			if err != nil {
				feedback.Warn("invalid ISRS code %v", err)
				ignored++
				continue
			}

			if dr.Lat == nil || dr.Lon == nil {
				feedback.Warn("missing lat/lon: %s", code)
				ignored++
				continue
			}

			if dr.Relenc != nil && len(*dr.Relenc) > 12 {
				feedback.Warn("relenc too long: %s", *dr.Relenc)
				ignored++
				continue
			}

			if dr.Wwname.Loc == nil {
				feedback.Warn("missing wwname: %s", code)
				ignored++
				continue
			}

			if _, err := insertStmt.ExecContext(
				ctx,
				code.CountryCode,
				code.LoCode,
				code.FairwaySection,
				code.Orc,
				code.Hectometre,
				float64(*dr.Lon), float64(*dr.Lat),
				dr.Relenc,
				string(*dr.Wwname.Loc),
			); err != nil {
				return nil, err
			}
			features++
		}
	}
	feedback.Info("ignored: %d", ignored)
	feedback.Info("features: %d", features)

	if features == 0 {
		return nil, errors.New("no features found")
	}

	if err = tx.Commit(); err == nil {
		feedback.Info("Refreshing distance marks (virtual) successfully took %s.",
			time.Since(start))
	} else {
		feedback.Error("Refreshing distance marks (virtual) failed after %s.",
			time.Since(start))
	}

	return nil, nil
}