changeset 3826:4b1184fa0326 sld-colors

Added templating and a failing test.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 03 Jul 2019 17:47:17 +0200
parents 36129677ff24
children 6028326b88d6
files 3rdpartylibs.sh pkg/geoserver/templates.go pkg/geoserver/templates_test.go
diffstat 3 files changed, 1131 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/3rdpartylibs.sh	Wed Jun 26 17:42:51 2019 +0200
+++ b/3rdpartylibs.sh	Wed Jul 03 17:47:17 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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/geoserver/templates.go	Wed Jul 03 17:47:17 2019 +0200
@@ -0,0 +1,169 @@
+// 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 (
+	"image/color"
+	"math"
+	"sort"
+	"strconv"
+	"strings"
+)
+
+type colorClass struct {
+	value float64
+	color color.RGBA
+}
+
+type colorClasses []colorClass
+
+func round(c float64) uint8 {
+	c = math.Round(c)
+	if c < 0 {
+		c = 0
+	} else if c > 255 {
+		c = 255
+	}
+	return uint8(c)
+}
+
+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
+
+			r := (1-s)*float64(cc[i].color.R) + s*float64(cc[i+1].color.R)
+			g := (1-s)*float64(cc[i].color.G) + s*float64(cc[i+1].color.G)
+			b := (1-s)*float64(cc[i].color.B) + s*float64(cc[i+1].color.B)
+
+			return color.RGBA{
+				R: round(r),
+				G: round(g),
+				B: round(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
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/geoserver/templates_test.go	Wed Jul 03 17:47:17 2019 +0200
@@ -0,0 +1,958 @@
+// 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 (
+	"fmt"
+	"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 . -}}
+        {{- if and .HasLow .HasHigh -}}
+        <se:Rule>
+          <se:Name>&#8804; {{ printf "%.1f" .High }}</se:Name>
+          <ogc:Filter>
+            <ogc:And>
+              <ogc:PropertyIsGreaterThan>
+                <ogc:PropertyName>height</ogc:PropertyName>
+                <ogc:Literal>{{.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>
+          <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>
+        {{ else if .HasLow -}}
+        <se:Rule>
+          <se:Name>&gt; {{ printf "%.1f" .Low }}</se:Name>
+          <ogc:Filter>
+            <ogc:PropertyIsGreaterThan>
+              <ogc:PropertyName>height</ogc:PropertyName>
+              <ogc:Literal>{{ printf "%g" .Low }}</ogc:Literal>
+            </ogc:PropertyIsGreaterThan>
+          </ogc:Filter>
+          <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>
+        {{ else if .HasHigh -}}
+        <se:Rule>
+          <se:Name>&#8804; {{ printf "%.1f" .High }}</se:Name>
+          <ogc:Filter>
+            <ogc:PropertyIsLessThanOrEqualTo>
+              <ogc:PropertyName>height</ogc:PropertyName>
+              <ogc:Literal>{{ printf "%g" .High }}</ogc:Literal>
+            </ogc:PropertyIsLessThanOrEqualTo>
+          </ogc:Filter>
+          <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 -}}
+        {{ 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 "%.1f" .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 "%.1f" .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 "%.1f" .High}}</ogc:Literal><ogc:Literal>{{ printf "%.1f" .High}}</ogc:Literal>
+				{{- else if .HasLow -}}
+				<ogc:Literal>{{ printf "%.1f" .Low}}</ogc:Literal><ogc:Literal>{{ printf "%.1f" .Low}}</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.0</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">#fb08b6</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">#f81090</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">#f5196a</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">#f22144</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">#f03326</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">#f38b1a</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">#f5b814</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">#f7e40e</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">#e2f30e</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">#b6e414</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 4.0</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">#5ec620</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 5.0</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">#32b726</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">#249a49</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 6.0</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">#207977</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">#1c57a4</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&#8804; 7.0</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">#1835d1</se:SvgParameter>
+              <se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
+            </se:Stroke>
+          </se:LineSymbolizer>
+        </se:Rule>
+        <se:Rule>
+          <se:Name>&gt; 7.0</se:Name>
+          <ogc:Filter>
+            <ogc:PropertyIsGreaterThan>
+              <ogc:PropertyName>height</ogc:PropertyName>
+              <ogc:Literal>7</ogc:Literal>
+            </ogc:PropertyIsGreaterThan>
+          </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.0</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.0</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.0</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.0</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.0</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.5</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.0</ogc:Literal>
+                  <ogc:Literal>#ff00dd</ogc:Literal>
+                  <ogc:Literal>1.5</ogc:Literal>
+                  <ogc:Literal>#fb08b6</ogc:Literal>
+                  <ogc:Literal>1.7</ogc:Literal>
+                  <ogc:Literal>#f81090</ogc:Literal>
+                  <ogc:Literal>1.9</ogc:Literal>
+                  <ogc:Literal>#f5196a</ogc:Literal>
+                  <ogc:Literal>2.1</ogc:Literal>
+                  <ogc:Literal>#f22144</ogc:Literal>
+                  <ogc:Literal>2.3</ogc:Literal>
+                  <ogc:Literal>#f03326</ogc:Literal>
+                  <ogc:Literal>2.5</ogc:Literal>
+                  <ogc:Literal>#f25f20</ogc:Literal>
+                  <ogc:Literal>2.7</ogc:Literal>
+                  <ogc:Literal>#f38b1a</ogc:Literal>
+                  <ogc:Literal>2.9</ogc:Literal>
+                  <ogc:Literal>#f5b814</ogc:Literal>
+                  <ogc:Literal>3.1</ogc:Literal>
+                  <ogc:Literal>#f7e40e</ogc:Literal>
+                  <ogc:Literal>3.3</ogc:Literal>
+                  <ogc:Literal>#e2f30e</ogc:Literal>
+                  <ogc:Literal>3.5</ogc:Literal>
+                  <ogc:Literal>#b6e414</ogc:Literal>
+                  <ogc:Literal>4.0</ogc:Literal>
+                  <ogc:Literal>#8ad51a</ogc:Literal>
+                  <ogc:Literal>4.5</ogc:Literal>
+                  <ogc:Literal>#5ec620</ogc:Literal>
+                  <ogc:Literal>5.0</ogc:Literal>
+                  <ogc:Literal>#32b726</ogc:Literal>
+                  <ogc:Literal>5.5</ogc:Literal>
+                  <ogc:Literal>#249a49</ogc:Literal>
+                  <ogc:Literal>6.0</ogc:Literal>
+                  <ogc:Literal>#207977</ogc:Literal>
+                  <ogc:Literal>6.5</ogc:Literal>
+                  <ogc:Literal>#1c57a4</ogc:Literal>
+                  <ogc:Literal>7.0</ogc:Literal>
+                  <ogc:Literal>#1835d1</ogc:Literal>
+                  <ogc:Literal>7.5</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.0</ogc:Literal><ogc:Literal>1.0</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.0</ogc:Literal><ogc:Literal>4.0</ogc:Literal>
+                <ogc:Literal>4.5</ogc:Literal><ogc:Literal>4.5</ogc:Literal>
+                <ogc:Literal>5.0</ogc:Literal><ogc:Literal>5.0</ogc:Literal>
+                <ogc:Literal>5.5</ogc:Literal><ogc:Literal>5.5</ogc:Literal>
+                <ogc:Literal>6.0</ogc:Literal><ogc:Literal>6.0</ogc:Literal>
+                <ogc:Literal>6.5</ogc:Literal><ogc:Literal>6.5</ogc:Literal>
+                <ogc:Literal>7.0</ogc:Literal><ogc:Literal>7.0</ogc:Literal>
+                <ogc:Literal>7.5</ogc:Literal><ogc:Literal>7.5</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,7.5:#1414ff`
+
+type classBreaks struct {
+	High    float64
+	HasHigh bool
+	Low     float64
+	HasLow  bool
+	Color   string
+}
+
+func TestTemplate(t *testing.T) {
+
+	ccs, err := parseColorClasses(classBreaksConfig)
+	if err != nil {
+		t.Fatalf("parsing color config failed: %v", err)
+	}
+
+	/*
+		for i := range ccs {
+			fmt.Printf("%.1f #%02x%02x%02x\n",
+				ccs[i].value,
+				ccs[i].color.R,
+				ccs[i].color.G,
+				ccs[i].color.B)
+		}
+	*/
+
+	cbs := make([]classBreaks, len(ccs))
+	for i := range ccs {
+		if i > 0 {
+			cbs[i].Low = ccs[i-1].value
+			cbs[i].HasLow = true
+		}
+		if i < len(ccs)-1 {
+			cbs[i].High = ccs[i].value
+			cbs[i].HasHigh = true
+		}
+		cbs[i].Color = fmt.Sprintf("#%02x%02x%02x",
+			ccs[i].color.R,
+			ccs[i].color.G,
+			ccs[i].color.B)
+	}
+
+	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))
+	}
+}