changeset 3845:5983eec9436c

Merged sld-colors branch back into default branch.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 08 Jul 2019 16:04:02 +0200
parents cc585e068ca0 (current diff) 40b28a8c0aaa (diff)
children f1ce295d9283
files
diffstat 7 files changed, 1399 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/3rdpartylibs.sh	Mon Jul 08 15:45:42 2019 +0200
+++ b/3rdpartylibs.sh	Mon Jul 08 16:04:02 2019 +0200
@@ -44,6 +44,10 @@
 go get -u -v gonum.org/v1/gonum/stat
 # BSD-3-Clause
 
+go get -u -v github.com/sergi/go-diff/diffmatchpatch
+# MIT
+# Only used in tests.
+
 # Only needed when generating SVG graphics for debugging.
 # go get -u -v github.com/ajstarks/svgo
 # Attribution 3.0 United States (CC BY 3.0 US)
--- a/pkg/controllers/system.go	Mon Jul 08 15:45:42 2019 +0200
+++ b/pkg/controllers/system.go	Mon Jul 08 16:04:02 2019 +0200
@@ -20,10 +20,12 @@
 	"io/ioutil"
 	"net/http"
 	"strings"
+	"sync"
 
 	"github.com/gorilla/mux"
 
 	"gemma.intevation.de/gemma/pkg/config"
+	"gemma.intevation.de/gemma/pkg/geoserver"
 	"gemma.intevation.de/gemma/pkg/models"
 )
 
@@ -39,6 +41,11 @@
 SELECT config_key, config_val
 FROM sys_admin.system_config`
 
+	getConfigSQL = `
+SELECT config_val
+FROM sys_admin.system_config
+WHERE config_key = $1`
+
 	updateSettingSQL = `
 INSERT INTO sys_admin.system_config (config_key, config_val)
 VALUES ($1, $2)
@@ -139,6 +146,32 @@
 	return
 }
 
+var (
+	reconfigureFuncsMu sync.Mutex
+	reconfigureFuncs   = map[string]func(){}
+)
+
+func registerReconfigureFunc(key string, fn func()) {
+	reconfigureFuncsMu.Lock()
+	defer reconfigureFuncsMu.Unlock()
+	reconfigureFuncs[key] = fn
+}
+
+func reconfigureFunc(key string) func() {
+	reconfigureFuncsMu.Lock()
+	defer reconfigureFuncsMu.Unlock()
+	return reconfigureFuncs[key]
+}
+
+func init() {
+	registerReconfigureFunc("morphology_classbreaks", func() {
+		geoserver.ReconfigureStyle("sounding_results_contour_lines_geoserver")
+	})
+	registerReconfigureFunc("morphology_classbreaks_compare", func() {
+		geoserver.ReconfigureStyle("sounding_differences")
+	})
+}
+
 func setSystemSettings(
 	input interface{},
 	req *http.Request,
@@ -159,8 +192,30 @@
 		return
 	}
 	defer setStmt.Close()
+	var getStmt *sql.Stmt
+	if getStmt, err = tx.PrepareContext(ctx, getConfigSQL); err != nil {
+		return
+	}
+	defer getStmt.Close()
+
+	reconfigure := map[string]func(){}
 
 	for key, value := range *settings {
+		var old sql.NullString
+		err = getStmt.QueryRowContext(ctx, key).Scan(&old)
+		switch {
+		case err == sql.ErrNoRows:
+			old.Valid, err = false, nil
+		case err != nil:
+			return
+		}
+
+		if !old.Valid || old.String != value {
+			if fn := reconfigureFunc(key); fn != nil {
+				reconfigure[key] = fn
+			}
+		}
+
 		if _, err = setStmt.ExecContext(ctx, key, value); err != nil {
 			return
 		}
@@ -170,6 +225,10 @@
 		return
 	}
 
+	for _, fn := range reconfigure {
+		fn()
+	}
+
 	jr = JSONResult{
 		Code: http.StatusCreated,
 		Result: struct {
--- a/pkg/geoserver/boot.go	Mon Jul 08 15:45:42 2019 +0200
+++ b/pkg/geoserver/boot.go	Mon Jul 08 16:04:02 2019 +0200
@@ -23,6 +23,7 @@
 	"net/http"
 	"net/url"
 	"strings"
+	"sync"
 
 	"golang.org/x/net/html/charset"
 
@@ -391,6 +392,23 @@
 	return nil
 }
 
+var (
+	stylePreprocessorsMu sync.Mutex
+	stylePreprocessors   = map[string]func(string) (string, error){}
+)
+
+func RegisterStylePreprocessor(name string, processor func(string) (string, error)) {
+	stylePreprocessorsMu.Lock()
+	defer stylePreprocessorsMu.Unlock()
+	stylePreprocessors[name] = processor
+}
+
+func FindStylePreprocessor(name string) func(string) (string, error) {
+	stylePreprocessorsMu.Lock()
+	defer stylePreprocessorsMu.Unlock()
+	return stylePreprocessors[name]
+}
+
 func updateStyle(entry *models.IntEntry, create bool) error {
 
 	log.Printf("info: creating style %s\n", entry.Name)
@@ -401,6 +419,12 @@
 		return err
 	}
 
+	if processor := FindStylePreprocessor(entry.Name); processor != nil {
+		if data, err = processor(data); err != nil {
+			return err
+		}
+	}
+
 	var (
 		geoURL   = config.GeoServerURL()
 		user     = config.GeoServerUser()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/geoserver/templates.go	Mon Jul 08 16:04:02 2019 +0200
@@ -0,0 +1,279 @@
+// 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
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/geoserver/templates_test.go	Mon Jul 08 16:04:02 2019 +0200
@@ -0,0 +1,907 @@
+// 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 (
+	"strings"
+	"testing"
+	"text/template"
+
+	"github.com/sergi/go-diff/diffmatchpatch"
+)
+
+const sldTmplTxt = `<?xml version="1.0" encoding="UTF-8"?>
+<StyledLayerDescriptor
+    xmlns="http://www.opengis.net/sld"
+    xmlns:se="http://www.opengis.net/se"
+    xmlns:ogc="http://www.opengis.net/ogc"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd"
+    version="1.1.0">
+  <NamedLayer>
+    <se:Name>sounding_results_contour_lines</se:Name>
+    <UserStyle>
+      <se:Name>sounding_results_contour_lines</se:Name>
+      <se:FeatureTypeStyle>
+          <se:Name>contour_line_colours</se:Name>
+        <se:Description>
+          <se:Abstract>
+            FeatureTypeStyle defining colour classes for height attribute
+          </se:Abstract>
+        </se:Description>
+        {{ range . -}}
+        <se:Rule>
+        {{- if not .HasLow }}
+          <se:Name>&#8804; {{ printf "%g" .High }}</se:Name>
+          <ogc:Filter>
+            <ogc:PropertyIsLessThanOrEqualTo>
+              <ogc:PropertyName>height</ogc:PropertyName>
+              <ogc:Literal>{{ printf "%g" .High }}</ogc:Literal>
+            </ogc:PropertyIsLessThanOrEqualTo>
+          </ogc:Filter>
+        {{- else if not .HasHigh }}
+          <se:Name>&gt; {{ printf "%g" .Low }}</se:Name>
+          <ogc:Filter>
+            <ogc:PropertyIsGreaterThanOrEqualTo>
+              <ogc:PropertyName>height</ogc:PropertyName>
+              <ogc:Literal>{{ printf "%g" .Low }}</ogc:Literal>
+            </ogc:PropertyIsGreaterThanOrEqualTo>
+          </ogc:Filter>
+        {{- else }}
+          <se:Name>&#8804; {{ printf "%g" .High }}</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>{{ printf "%g" .Low }}</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>{{ printf "%g" .High }}</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+        {{- end }}
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">{{ .Color }}</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        {{ end }}
+      </se:FeatureTypeStyle>
+      <se:FeatureTypeStyle>
+        <se:Name>contour_lines_emph</se:Name>
+        <se:Description>
+          <se:Abstract>
+            FeatureTypeStyle for emphasized contour lines
+          </se:Abstract>
+          </se:Description>
+          <se:Rule>
+            <se:LegendGraphic>
+              <se:Graphic>
+            </se:Graphic>
+          </se:LegendGraphic>
+          <ogc:Filter>
+             <ogc:Or>
+              {{ range . -}}
+              {{ if .HasHigh -}}
+                <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>{{ printf "%g" .High }}</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              {{ end -}}
+              {{ end }}
+            </ogc:Or>
+          </ogc:Filter>
+          <se:MaxScaleDenominator>5e3</se:MaxScaleDenominator>
+          <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke-width">1.5</se:SvgParameter>
+              <se:SvgParameter name="stroke">
+                <ogc:Function name="Recode">
+                  <ogc:Function name="numberFormat">
+                    <ogc:Literal>0.0</ogc:Literal>
+                    <ogc:PropertyName>height</ogc:PropertyName>
+                  </ogc:Function>
+                  {{ range . -}}
+                  {{ if .HasHigh -}}
+                  <ogc:Literal>{{ printf "%g" .High }}</ogc:Literal>
+                  <ogc:Literal>{{ .Color }}</ogc:Literal>
+                  {{ end -}}
+                  {{ end }}
+                </ogc:Function>
+              </se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+      </se:FeatureTypeStyle>
+      <se:FeatureTypeStyle>
+        <se:Name>contour_lines_label</se:Name>
+        <se:Description>
+          <se:Abstract>
+            FeatureTypeStyle for labels at contour lines
+          </se:Abstract>
+        </se:Description>
+        <se:Rule>
+          <se:MaxScaleDenominator>5e3</se:MaxScaleDenominator>
+          <se:TextSymbolizer>
+            <se:Label>
+              <ogc:Function name="Recode">
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                {{ range . -}}
+                {{ if .HasHigh -}}
+                    <ogc:Literal>
+                    {{- printf "%g" .High -}}
+                    </ogc:Literal><ogc:Literal>
+                    {{- printf "%g" .High -}}
+                    </ogc:Literal>
+                {{ end -}}
+                {{ end }}
+              </ogc:Function>
+            </se:Label>
+            <se:LabelPlacement>
+              <se:LinePlacement>
+                <se:PerpendicularOffset>5</se:PerpendicularOffset>
+              </se:LinePlacement>
+            </se:LabelPlacement>
+            <se:Font>
+              <se:SvgParameter name="font-family">Avenir</se:SvgParameter>
+              <se:SvgParameter name="font-family">Helvetica</se:SvgParameter>
+              <se:SvgParameter name="font-family">Arial</se:SvgParameter>
+              <se:SvgParameter name="font-family">sans-serif</se:SvgParameter>
+            </se:Font>
+            <se:Fill>
+              <se:SvgParameter name="fill">#070707</se:SvgParameter>
+            </se:Fill>
+          </se:TextSymbolizer>
+        </se:Rule>
+      </se:FeatureTypeStyle>
+    </UserStyle>
+  </NamedLayer>
+</StyledLayerDescriptor>
+`
+
+const origSLD = `<?xml version="1.0" encoding="UTF-8"?>
+<StyledLayerDescriptor
+    xmlns="http://www.opengis.net/sld"
+    xmlns:se="http://www.opengis.net/se"
+    xmlns:ogc="http://www.opengis.net/ogc"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd"
+    version="1.1.0">
+  <NamedLayer>
+    <se:Name>sounding_results_contour_lines</se:Name>
+    <UserStyle>
+      <se:Name>sounding_results_contour_lines</se:Name>
+      <se:FeatureTypeStyle>
+          <se:Name>contour_line_colours</se:Name>
+        <se:Description>
+          <se:Abstract>
+            FeatureTypeStyle defining colour classes for height attribute
+          </se:Abstract>
+        </se:Description>
+        <se:Rule>
+          <se:Name>&#8804; 1</se:Name>
+          <ogc:Filter>
+            <ogc:PropertyIsLessThanOrEqualTo>
+              <ogc:PropertyName>height</ogc:PropertyName>
+              <ogc:Literal>1</ogc:Literal>
+            </ogc:PropertyIsLessThanOrEqualTo>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#ff00dd</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 1.5</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>1</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>1.5</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#fb209e</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 1.7</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>1.5</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>1.7</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#f92c85</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 1.9</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>1.7</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>1.9</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#f7396c</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 2.1</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>1.9</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>2.1</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#f54652</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 2.3</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>2.1</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>2.3</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#f45239</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 2.5</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>2.3</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>2.5</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#f25f20</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 2.7</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>2.5</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>2.7</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#e46f1f</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 2.9</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>2.7</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>2.9</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#d67e1e</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 3.1</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>2.9</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>3.1</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#c88e1e</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 3.3</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>3.1</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>3.3</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#bb9e1d</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 3.5</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>3.3</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>3.5</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#adae1c</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 4</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>3.5</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>4</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#8ad51a</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 4.5</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>4</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>4.5</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#76b540</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 5</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>4.5</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>5</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#639566</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 5.5</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>5</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>5.5</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#4f758d</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 6</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>5.5</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>6</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#3b54b3</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 6.5</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>6</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>6.5</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#2834d9</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 7</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>6.5</ogc:Literal>
+              </ogc:PropertyIsGreaterThan>
+              <ogc:PropertyIsLessThanOrEqualTo>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>7</ogc:Literal>
+              </ogc:PropertyIsLessThanOrEqualTo>
+            </ogc:And>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#1414ff</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&gt; 7</se:Name>
+          <ogc:Filter>
+            <ogc:PropertyIsGreaterThanOrEqualTo>
+              <ogc:PropertyName>height</ogc:PropertyName>
+              <ogc:Literal>7</ogc:Literal>
+            </ogc:PropertyIsGreaterThanOrEqualTo>
+          </ogc:Filter>
+           <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke">#1414ff</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        
+      </se:FeatureTypeStyle>
+      <se:FeatureTypeStyle>
+        <se:Name>contour_lines_emph</se:Name>
+        <se:Description>
+          <se:Abstract>
+            FeatureTypeStyle for emphasized contour lines
+          </se:Abstract>
+          </se:Description>
+          <se:Rule>
+            <se:LegendGraphic>
+              <se:Graphic>
+            </se:Graphic>
+          </se:LegendGraphic>
+          <ogc:Filter>
+             <ogc:Or>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>1</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>1.5</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>1.7</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>1.9</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>2.1</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>2.3</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>2.5</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>2.7</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>2.9</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>3.1</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>3.3</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>3.5</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>4</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>4.5</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>5</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>5.5</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>6</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>6.5</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              <ogc:PropertyIsEqualTo>
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>7</ogc:Literal>
+                </ogc:PropertyIsEqualTo>
+              
+            </ogc:Or>
+          </ogc:Filter>
+          <se:MaxScaleDenominator>5e3</se:MaxScaleDenominator>
+          <se:LineSymbolizer>
+            <se:Stroke>
+              <se:SvgParameter name="stroke-width">1.5</se:SvgParameter>
+              <se:SvgParameter name="stroke">
+                <ogc:Function name="Recode">
+                  <ogc:Function name="numberFormat">
+                    <ogc:Literal>0.0</ogc:Literal>
+                    <ogc:PropertyName>height</ogc:PropertyName>
+                  </ogc:Function>
+                  <ogc:Literal>1</ogc:Literal>
+                  <ogc:Literal>#ff00dd</ogc:Literal>
+                  <ogc:Literal>1.5</ogc:Literal>
+                  <ogc:Literal>#fb209e</ogc:Literal>
+                  <ogc:Literal>1.7</ogc:Literal>
+                  <ogc:Literal>#f92c85</ogc:Literal>
+                  <ogc:Literal>1.9</ogc:Literal>
+                  <ogc:Literal>#f7396c</ogc:Literal>
+                  <ogc:Literal>2.1</ogc:Literal>
+                  <ogc:Literal>#f54652</ogc:Literal>
+                  <ogc:Literal>2.3</ogc:Literal>
+                  <ogc:Literal>#f45239</ogc:Literal>
+                  <ogc:Literal>2.5</ogc:Literal>
+                  <ogc:Literal>#f25f20</ogc:Literal>
+                  <ogc:Literal>2.7</ogc:Literal>
+                  <ogc:Literal>#e46f1f</ogc:Literal>
+                  <ogc:Literal>2.9</ogc:Literal>
+                  <ogc:Literal>#d67e1e</ogc:Literal>
+                  <ogc:Literal>3.1</ogc:Literal>
+                  <ogc:Literal>#c88e1e</ogc:Literal>
+                  <ogc:Literal>3.3</ogc:Literal>
+                  <ogc:Literal>#bb9e1d</ogc:Literal>
+                  <ogc:Literal>3.5</ogc:Literal>
+                  <ogc:Literal>#adae1c</ogc:Literal>
+                  <ogc:Literal>4</ogc:Literal>
+                  <ogc:Literal>#8ad51a</ogc:Literal>
+                  <ogc:Literal>4.5</ogc:Literal>
+                  <ogc:Literal>#76b540</ogc:Literal>
+                  <ogc:Literal>5</ogc:Literal>
+                  <ogc:Literal>#639566</ogc:Literal>
+                  <ogc:Literal>5.5</ogc:Literal>
+                  <ogc:Literal>#4f758d</ogc:Literal>
+                  <ogc:Literal>6</ogc:Literal>
+                  <ogc:Literal>#3b54b3</ogc:Literal>
+                  <ogc:Literal>6.5</ogc:Literal>
+                  <ogc:Literal>#2834d9</ogc:Literal>
+                  <ogc:Literal>7</ogc:Literal>
+                  <ogc:Literal>#1414ff</ogc:Literal>
+                  
+                </ogc:Function>
+              </se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+      </se:FeatureTypeStyle>
+      <se:FeatureTypeStyle>
+        <se:Name>contour_lines_label</se:Name>
+        <se:Description>
+          <se:Abstract>
+            FeatureTypeStyle for labels at contour lines
+          </se:Abstract>
+        </se:Description>
+        <se:Rule>
+          <se:MaxScaleDenominator>5e3</se:MaxScaleDenominator>
+          <se:TextSymbolizer>
+            <se:Label>
+              <ogc:Function name="Recode">
+                <ogc:Function name="numberFormat">
+                  <ogc:Literal>0.0</ogc:Literal>
+                  <ogc:PropertyName>height</ogc:PropertyName>
+                </ogc:Function>
+                <ogc:Literal>1</ogc:Literal><ogc:Literal>1</ogc:Literal>
+                <ogc:Literal>1.5</ogc:Literal><ogc:Literal>1.5</ogc:Literal>
+                <ogc:Literal>1.7</ogc:Literal><ogc:Literal>1.7</ogc:Literal>
+                <ogc:Literal>1.9</ogc:Literal><ogc:Literal>1.9</ogc:Literal>
+                <ogc:Literal>2.1</ogc:Literal><ogc:Literal>2.1</ogc:Literal>
+                <ogc:Literal>2.3</ogc:Literal><ogc:Literal>2.3</ogc:Literal>
+                <ogc:Literal>2.5</ogc:Literal><ogc:Literal>2.5</ogc:Literal>
+                <ogc:Literal>2.7</ogc:Literal><ogc:Literal>2.7</ogc:Literal>
+                <ogc:Literal>2.9</ogc:Literal><ogc:Literal>2.9</ogc:Literal>
+                <ogc:Literal>3.1</ogc:Literal><ogc:Literal>3.1</ogc:Literal>
+                <ogc:Literal>3.3</ogc:Literal><ogc:Literal>3.3</ogc:Literal>
+                <ogc:Literal>3.5</ogc:Literal><ogc:Literal>3.5</ogc:Literal>
+                <ogc:Literal>4</ogc:Literal><ogc:Literal>4</ogc:Literal>
+                <ogc:Literal>4.5</ogc:Literal><ogc:Literal>4.5</ogc:Literal>
+                <ogc:Literal>5</ogc:Literal><ogc:Literal>5</ogc:Literal>
+                <ogc:Literal>5.5</ogc:Literal><ogc:Literal>5.5</ogc:Literal>
+                <ogc:Literal>6</ogc:Literal><ogc:Literal>6</ogc:Literal>
+                <ogc:Literal>6.5</ogc:Literal><ogc:Literal>6.5</ogc:Literal>
+                <ogc:Literal>7</ogc:Literal><ogc:Literal>7</ogc:Literal>
+                
+              </ogc:Function>
+            </se:Label>
+            <se:LabelPlacement>
+              <se:LinePlacement>
+                <se:PerpendicularOffset>5</se:PerpendicularOffset>
+              </se:LinePlacement>
+            </se:LabelPlacement>
+            <se:Font>
+              <se:SvgParameter name="font-family">Avenir</se:SvgParameter>
+              <se:SvgParameter name="font-family">Helvetica</se:SvgParameter>
+              <se:SvgParameter name="font-family">Arial</se:SvgParameter>
+              <se:SvgParameter name="font-family">sans-serif</se:SvgParameter>
+            </se:Font>
+            <se:Fill>
+              <se:SvgParameter name="fill">#070707</se:SvgParameter>
+            </se:Fill>
+          </se:TextSymbolizer>
+        </se:Rule>
+      </se:FeatureTypeStyle>
+    </UserStyle>
+  </NamedLayer>
+</StyledLayerDescriptor>
+`
+
+const classBreaksConfig = `1:#ff00dd,1.5,1.7,1.9,2.1,2.3,` +
+	`2.5:#f25f20,2.7,2.9,3.1,3.3,3.5,` +
+	`4:#8ad51a,4.5,5,5.5,6,6.5,` +
+	`7:#1414ff`
+
+func TestTemplate(t *testing.T) {
+
+	ccs, err := parseColorClasses(classBreaksConfig)
+	if err != nil {
+		t.Fatalf("parsing color config failed: %v", err)
+	}
+
+	cbs := ccs.toClassBreaks()
+
+	tmpl, err := template.New("test").Parse(sldTmplTxt)
+	if err != nil {
+		t.Fatalf("parsing template failed: %v", err)
+	}
+
+	var buf strings.Builder
+	if err := tmpl.Execute(&buf, cbs); err != nil {
+		t.Fatalf("templating failed: %v", err)
+	}
+
+	has := buf.String()
+	if has != origSLD {
+		dmp := diffmatchpatch.New()
+		diffs := dmp.DiffMain(has, origSLD, true)
+		t.Fatalf("Templating results differ: %s", dmp.DiffPrettyText(diffs))
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/octree/classbreaks.go	Mon Jul 08 16:04:02 2019 +0200
@@ -0,0 +1,126 @@
+// 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 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")
+	}
+
+	parts := strings.Split(config.String, ",")
+	classes := make([]float64, 0, len(parts))
+	for _, part := range parts {
+		if idx := strings.IndexRune(part, ':'); idx >= 0 {
+			part = part[idx+1:]
+		}
+		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 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
+}
--- a/pkg/octree/contours.go	Mon Jul 08 15:45:42 2019 +0200
+++ b/pkg/octree/contours.go	Mon Jul 08 16:04:02 2019 +0200
@@ -19,35 +19,6 @@
 	"sync"
 )
 
-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
-}
-
 // ContourResult stores an calculated iso line for a given height.
 // Is used as a future variable in the concurrent iso line calculation.
 type ContourResult struct {