view pkg/geoserver/templates.go @ 5423:24156a964eaa marking-single-beam

[WIP] Add support for scan marking points in geoserver.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Thu, 08 Jul 2021 12:21:55 +0200
parents 44b032028e48
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) 2019 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
//  * Markus Kottländer <markus.kottlaender@intevation.de>
//  * Bernhard Reiter <bernhard.reiter@intevation.de>

package geoserver

import (
	"context"
	"database/sql"
	"regexp"
	"strconv"
	"strings"
	"text/template"

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

const (
	selectConfigValSQL = `
SELECT config_val FROM sys_admin.system_config
WHERE config_key = $1`
)

func init() {
	RegisterStylePreprocessor(
		"sounding_results_areas_geoserver",
		templateContourLinesFunc("morphology_classbreaks"))
	RegisterStylePreprocessor(
		"sounding_results_marking_points_geoserver",
		templateContourLinesFunc("morphology_classbreaks"))
	RegisterStylePreprocessor(
		"sounding_differences",
		templateContourLinesFunc("morphology_classbreaks_compare"))
	RegisterStylePreprocessor(
		"distance_marks_geoserver",
		templateConfigValues)
	RegisterStylePreprocessor(
		"distance_marks_ashore_geoserver",
		templateConfigValues)
	RegisterStylePreprocessor(
		"waterway_area",
		templateConfigValues)
	RegisterStylePreprocessor(
		"waterway_axis",
		templateConfigValues)
	// TODO: Add more layers.
}

func templateConfigValues(tmplTxt string) (string, error) {
	// As SLDs cannot handle opacity as hex part of the color setting
	// we split out the 7-8 chars of keys ending on "_fill" color settings
	// into keys ending on "_fill_opacity"
	tmpl, err := template.New("template").Parse(tmplTxt)
	if err != nil {
		return "", err
	}

	// Try to extract the needed keys from the template.
	keys := extractKeysFromTemplate(tmplTxt)

	// filter out keys ending on "_opacity" and put them in their own slice
	var opacityKeys []string
	n := 0
	for _, key := range keys {
		if strings.HasSuffix(key, "_opacity") {
			opacityKeys = append(opacityKeys, key)
		} else {
			keys[n] = key
			n++
		}
	}
	keys = keys[:n]

	kv, err := loadConfigValues(keys)
	if err != nil {
		return "", err
	}

	// if there convert opacity hex value into float between 0-1
	// otherwise just use 1.0
	for _, opacityKey := range opacityKeys {
		fillKey := opacityKey[0 : len(opacityKey)-8]
		fillValue := kv[fillKey]
		if fillValue != "" && len(fillValue) == 9 {
			opacity, err := strconv.ParseInt(fillValue[7:9], 16, 0)
			if err == nil {
				kv[opacityKey] = strconv.FormatFloat(
					float64(opacity)/255, 'f', 2, 64)
				kv[fillKey] = kv[fillKey][0:7]
			} else {
				return "", err
			}

		} else {
			kv[opacityKey] = "1.0"
		}
	}

	var buf strings.Builder
	if err = tmpl.Execute(&buf, kv); err != nil {
		return "", err
	}
	return buf.String(), nil

}

// TODO: Use the parse tree of the template to extract keys.
var findKeysRe = regexp.MustCompile(`{{[-]?\s*\.([^-.\s]+)\s*[-]?}}`)

func extractKeysFromTemplate(tmpl string) []string {
	parts := findKeysRe.FindAllStringSubmatch(tmpl, -1)
	keys := make(map[string]struct{})
	for _, part := range parts {
		keys[part[1]] = struct{}{}
	}
	out := make([]string, len(keys))
	var i int
	for key := range keys {
		out[i] = key
		i++
	}
	return out
}

func loadConfigValues(keys []string) (map[string]string, error) {
	kv := make(map[string]string, len(keys))
	if len(keys) == 0 {
		return kv, nil
	}
	ctx := context.Background()
	if err := auth.RunAs(
		ctx,
		"sys_admin",
		func(conn *sql.Conn) error {
			stmt, err := conn.PrepareContext(ctx, selectConfigValSQL)
			if err != nil {
				return err
			}
			defer stmt.Close()
			for _, key := range keys {
				var val string
				if err := stmt.QueryRowContext(ctx, key).Scan(&val); err != nil {
					return err
				}
				kv[key] = val
			}
			return nil
		},
	); err != nil {
		return nil, err
	}

	return kv, nil
}

func templateContourLinesFunc(configKey string) func(string) (string, error) {
	return func(data string) (string, error) {
		return templateContourLines(data, configKey)
	}
}

func templateContourLines(data, configKey string) (string, error) {
	tmpl, err := template.New("template").Parse(data)
	if err != nil {
		return "", err
	}

	var cb []models.ClassBreak

	if cb, err = countourLinesClassBreaks(configKey); err != nil {
		return "", err
	}

	var buf strings.Builder
	if err = tmpl.Execute(&buf, cb); err != nil {
		return "", err
	}
	return buf.String(), nil
}

func countourLinesClassBreaks(configKey string) ([]models.ClassBreak, error) {

	var config string
	ctx := context.Background()
	if err := auth.RunAs(
		ctx,
		"sys_admin",
		func(conn *sql.Conn) error {
			return conn.QueryRowContext(
				ctx,
				selectConfigValSQL,
				configKey,
			).Scan(&config)
		},
	); err != nil {
		return nil, err
	}

	cc, err := models.ParseColorValues(config)
	if err != nil {
		return nil, err
	}

	return cc.ClassBreaks(), nil
}