Mercurial > gemma
view pkg/geoserver/templates.go @ 3841:8e47d6f12998 sld-colors
Merged default into sld-colors branch.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Mon, 08 Jul 2019 14:34:42 +0200 |
parents | 0ffea636d6b0 |
children | 3fcc4e11fc00 |
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> package geoserver import ( "context" "database/sql" "fmt" "image/color" "math" "sort" "strconv" "strings" "text/template" "gemma.intevation.de/gemma/pkg/auth" ) const ( selectClassBreaksSQL = ` SELECT config_val FROM sys_admin.system_config WHERE config_key = $1` ) func init() { RegisterStylePreprocessor( "sounding_results_contour_lines_geoserver", templateContourLinesFunc("morphology_classbreaks")) RegisterStylePreprocessor( "sounding_differences", templateContourLinesFunc("morphology_classbreaks_compare")) } type ( colorClass struct { value float64 color color.RGBA } colorClasses []colorClass classBreak struct { High float64 HasHigh bool Low float64 HasLow bool color color.RGBA } ) func (cb *classBreak) Color() string { return fmt.Sprintf("#%02x%02x%02x", cb.color.R, cb.color.G, cb.color.B, ) } func (cc colorClasses) toClassBreaks() []classBreak { cbs := make([]classBreak, len(cc), len(cc)+1) for i := range cc { if i > 0 { cbs[i].Low = cc[i-1].value cbs[i].HasLow = true } cbs[i].High = cc[i].value cbs[i].HasHigh = true cbs[i].color = cc[i].color } if len(cc) > 0 { cbs = append(cbs, classBreak{ color: cc[len(cc)-1].color, Low: cc[len(cc)-1].value, HasLow: true, }) } return cbs } func (cc colorClasses) interpolate(v float64) (color.RGBA, bool) { if len(cc) == 0 || v < cc[0].value || v > cc[len(cc)-1].value { return color.RGBA{}, false } if len(cc) == 1 { return cc[0].color, cc[0].value == v } for i := 0; i < len(cc)-1; i++ { v1 := cc[i].value v2 := cc[i+1].value if v1 <= v && v <= v2 { // f(v1) = 0 // f(v2) = 1 // 0 = m*v1 + c <=> c = -m*v1 // 1 = m*v2 + c // (1 - 0) = m*(v2 - v1) // m = 1/(v2 - v1) for v2 != v1 if v1 == v2 { return color.RGBA{ R: uint8((uint16(cc[i].color.R) + uint16(cc[i+1].color.R)) / 2), G: uint8((uint16(cc[i].color.G) + uint16(cc[i+1].color.G)) / 2), B: uint8((uint16(cc[i].color.B) + uint16(cc[i+1].color.B)) / 2), A: 0xff, }, true } m := 1 / (v2 - v1) c := -m * v1 s := v*m + c interpolate := func(a, b uint8) uint8 { v := math.Round(float64(a) + (float64(b)-float64(a))*s) if v < 0 { return 0 } if v > 255 { return 255 } return uint8(v) } return color.RGBA{ R: interpolate(cc[i].color.R, cc[i+1].color.R), G: interpolate(cc[i].color.G, cc[i+1].color.G), B: interpolate(cc[i].color.B, cc[i+1].color.B), A: 0xff, }, true } } return color.RGBA{}, false } func parseColorClasses(s string) (colorClasses, error) { var err error parseFloat := func(s string) float64 { var v float64 if err == nil { v, err = strconv.ParseFloat(s, 64) } return v } parseColor := func(s string) color.RGBA { if err != nil { return color.RGBA{} } s = strings.Map(func(r rune) rune { if ('a' <= r && r <= 'f') || ('0' <= r && r <= '9') { return r } return -1 }, strings.ToLower(s)) var v int64 v, err = strconv.ParseInt(s, 16, 64) return color.RGBA{ R: uint8(v >> 16), G: uint8(v >> 8), B: uint8(v >> 0), A: 0xff, } } lines := strings.Split(s, ",") // first pass: find defined colors var defined colorClasses for _, line := range lines { // ignore the lines w/o a color. if !strings.ContainsRune(line, ':') { continue } parts := strings.SplitN(line, ":", 2) if len(parts) < 2 { continue } value := parseFloat(parts[0]) color := parseColor(parts[1]) defined = append(defined, colorClass{ value: value, color: color, }) } if err != nil { return nil, err } sort.Slice(defined, func(i, j int) bool { return defined[i].value < defined[j].value }) // second pass: interpolate the rest var final colorClasses for _, line := range lines { if idx := strings.IndexRune(line, ':'); idx >= 0 { line = line[:idx] } value := parseFloat(line) if color, ok := defined.interpolate(value); ok { final = append(final, colorClass{ value: value, color: color, }) } } return final, err } 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 []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) ([]classBreak, error) { var config string ctx := context.Background() if err := auth.RunAs( ctx, "sys_admin", func(conn *sql.Conn) error { return conn.QueryRowContext( ctx, selectClassBreaksSQL, configKey, ).Scan(&config) }, ); err != nil { return nil, err } cc, err := parseColorClasses(config) if err != nil { return nil, err } return cc.toClassBreaks(), nil }