Mercurial > gemma
view pkg/mesh/classbreaks.go @ 5652:9b3779f649c2 v5.7.1
Bumped version for release.
author | Sascha Wilde <wilde@sha-bang.de> |
---|---|
date | Mon, 07 Aug 2023 17:59:18 +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) 2019 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann <sascha.teichmann@intevation.de> package mesh import ( "context" "database/sql" "errors" "math" "sort" "strconv" "strings" "gemma.intevation.de/gemma/pkg/common" ) const ( selectClassBreaksSQL = ` SELECT config_val FROM sys_admin.system_config WHERE config_key = $1` ) // ClassBreaks represents borders between scalar classes. type ClassBreaks []float64 // SampleDiffHeights generate a gradient from min to max with a // given step width. func SampleDiffHeights(min, max, step float64) ClassBreaks { var heights ClassBreaks 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 } // ParseClassBreaks deserialized class breaks from a // given string. key/value pairs a separated with // ','. Key and values are separated by ':'. func ParseClassBreaks(config string) (ClassBreaks, error) { parts := strings.Split(config, ",") classes := make(ClassBreaks, 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 } // LoadClassBreaks loads a class break from the database. // The given key identifies the dataset. func LoadClassBreaks(ctx context.Context, tx *sql.Tx, key string) (ClassBreaks, 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 round(v float64) float64 { return math.Round(v*10000) / 10000 } // ExtrapolateClassBreaks extends the given // classbreaks (as a copy) from min to max. // The border class sizes are used to extrapolate // step sizes. func (cbs ClassBreaks) ExtrapolateClassBreaks(min, max float64) ClassBreaks { if min > max { min, max = max, min } n := make(ClassBreaks, len(cbs)) copy(n, cbs) sort.Float64s(n) for len(n) > 0 && n[0] < min { n = n[1:] } if len(n) == 0 { return n } for len(n) > 0 && n[len(n)-1] > max { n = n[:len(n)-1] } if len(n) == 0 { return n } for min < n[0] { diff := n[1] - n[0] if diff == 0 { break } m := make([]float64, len(n)+1) m[0] = round(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, round(n[len(n)-1]+diff)) } return n } // Dedup removes duplicates from the class breaks. func (cbs ClassBreaks) Dedup() ClassBreaks { return ClassBreaks(common.DedupFloat64s(cbs)) } // Classify associates a class to all points of // a given multi points and returns a slice of // multi points containing the points that are // in the respective class. func (cbs ClassBreaks) Classify(points MultiPointZ) []MultiPointZ { if len(cbs) == 0 { return nil } classes := make([]MultiPointZ, len(cbs)) for _, v := range points { // Place in last class if greater than all. idx := len(cbs) - 1 for i, cb := range cbs { if v.Z <= cb { idx = i break } } classes[idx] = append(classes[idx], v) } return classes }