view pkg/octree/classbreaks.go @ 4650:f5fce22184da stree-experiment

Added a deserializer from STRTrees.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 14 Oct 2019 01:28:18 +0200
parents 49564382ffff
children 7a9388943840
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>

package octree

import (
	"context"
	"database/sql"
	"errors"
	"math"
	"sort"
	"strconv"
	"strings"
)

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

func SampleDiffHeights(min, max, step float64) []float64 {
	var heights []float64
	switch {
	case min >= 0: // All values positive.
		for v := 0.0; v <= max; v += step {
			if v >= min {
				heights = append(heights, v)
			}
		}
	case max <= 0: // All values negative.
		for v := 0.0; v >= min; v -= step {
			if v <= max {
				heights = append(heights, v)
			}
		}
	default: // Positive and negative.
		for v := step; v <= max; v += step {
			heights = append(heights, v)
		}
		for i, j := 0, len(heights)-1; i < j; i, j = i+1, j-1 {
			heights[i], heights[j] = heights[j], heights[i]
		}
		for v := 0.0; v >= min; v -= step {
			heights = append(heights, v)
		}
	}
	return heights
}

func ParseClassBreaks(config string) ([]float64, error) {

	parts := strings.Split(config, ",")
	classes := make([]float64, 0, len(parts))
	for _, part := range parts {
		if idx := strings.IndexRune(part, ':'); idx >= 0 {
			part = part[:idx]
		}
		if part = strings.TrimSpace(part); part == "" {
			continue
		}
		v, err := strconv.ParseFloat(part, 64)
		if err != nil {
			return nil, err
		}
		classes = append(classes, v)
	}

	sort.Float64s(classes)
	return classes, nil
}

func LoadClassBreaks(ctx context.Context, tx *sql.Tx, key string) ([]float64, error) {

	var config sql.NullString

	err := tx.QueryRowContext(ctx, selectClassBreaksSQL, key).Scan(&config)

	switch {
	case err == sql.ErrNoRows:
		return nil, nil
	case err != nil:
		return nil, err
	case !config.Valid:
		return nil, errors.New("Invalid config string")
	}

	return ParseClassBreaks(config.String)
}

func ExtrapolateClassBreaks(cbs []float64, min, max float64) []float64 {
	if min > max {
		min, max = max, min
	}

	if len(cbs) < 2 {
		return cbs
	}

	if min >= cbs[0] && max <= cbs[len(cbs)-1] {
		return cbs
	}

	n := make([]float64, len(cbs))
	copy(n, cbs)
	sort.Float64s(n)

	for min < n[0] {
		diff := n[1] - n[0]
		if diff == 0 {
			break
		}
		m := make([]float64, len(n)+1)
		m[0] = n[0] - diff
		copy(m[1:], n)
		n = m
	}

	for max > n[len(n)-1] {
		diff := n[len(n)-1] - n[len(n)-2]
		if diff == 0 {
			break
		}
		n = append(n, n[len(n)-1]+diff)
	}

	return n
}

func InBetweenClassBreaks(cbs []float64, min float64, steps int) []float64 {
	if len(cbs) < 2 || steps < 2 {
		return cbs
	}

	out := make([]float64, 1, len(cbs)*steps)

	out[0] = cbs[0]

	_1steps := 1 / float64(steps)

	for i := 1; i < len(cbs); i++ {
		last, curr := cbs[i-1], cbs[i]

		// Gap already too small -> proceed with next gap.
		diff := curr - last
		if math.Abs(diff) < min {
			out = append(out, curr)
			continue
		}

		delta := diff * _1steps
		for p := last + delta; p < curr; p += delta {
			out = append(out, p)
		}

		out = append(out, curr)
	}

	return out
}