mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-04 00:04:03 -04:00
836 lines
28 KiB
Python
836 lines
28 KiB
Python
"""QGIS Unit tests for QgsGraduatedSymbolRenderer
|
|
|
|
.. note:: This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
"""
|
|
|
|
__author__ = "Chris Crook"
|
|
__date__ = "3/10/2014"
|
|
__copyright__ = "Copyright 2014, The QGIS Project"
|
|
|
|
from qgis.PyQt.QtCore import Qt
|
|
from qgis.PyQt.QtGui import QColor
|
|
from qgis.PyQt.QtXml import QDomDocument
|
|
from qgis.core import (
|
|
QgsFeature,
|
|
QgsGeometry,
|
|
QgsGradientColorRamp,
|
|
QgsGraduatedSymbolRenderer,
|
|
QgsMarkerSymbol,
|
|
QgsPointXY,
|
|
QgsReadWriteContext,
|
|
QgsRenderContext,
|
|
QgsRendererRange,
|
|
QgsRendererRangeLabelFormat,
|
|
QgsVectorLayer,
|
|
QgsFillSymbol,
|
|
QgsClassificationFixedInterval,
|
|
QgsClassificationPrettyBreaks,
|
|
)
|
|
import unittest
|
|
from qgis.testing import start_app, QgisTestCase
|
|
|
|
start_app()
|
|
|
|
|
|
# ===========================================================
|
|
# Utility functions
|
|
|
|
|
|
def createMarkerSymbol():
|
|
symbol = QgsMarkerSymbol.createSimple(
|
|
{"color": "100,150,50", "name": "square", "size": "3.0"}
|
|
)
|
|
return symbol
|
|
|
|
|
|
def createFillSymbol():
|
|
symbol = QgsFillSymbol.createSimple({"color": "100,150,50"})
|
|
return symbol
|
|
|
|
|
|
def createMemoryLayer(values):
|
|
ml = QgsVectorLayer(
|
|
"Point?crs=epsg:4236&field=id:integer&field=value:double", "test_data", "memory"
|
|
)
|
|
# Data as list of x, y, id, value
|
|
assert ml.isValid()
|
|
pr = ml.dataProvider()
|
|
fields = pr.fields()
|
|
for id, value in enumerate(values):
|
|
x = id * 10.0
|
|
feat = QgsFeature(fields)
|
|
feat["id"] = id
|
|
feat["value"] = value
|
|
g = QgsGeometry.fromPointXY(QgsPointXY(x, x))
|
|
feat.setGeometry(g)
|
|
pr.addFeatures([feat])
|
|
ml.updateExtents()
|
|
return ml
|
|
|
|
|
|
def createColorRamp():
|
|
return QgsGradientColorRamp(QColor(255, 0, 0), QColor(0, 0, 255))
|
|
|
|
|
|
def createLabelFormat():
|
|
format = QgsRendererRangeLabelFormat()
|
|
template = "%1 - %2 meters"
|
|
precision = 5
|
|
format.setFormat(template)
|
|
format.setPrecision(precision)
|
|
format.setTrimTrailingZeroes(True)
|
|
return format
|
|
|
|
|
|
# Note: Dump functions are not designed for a user friendly dump, just
|
|
# for a moderately compact representation of a rendererer that is independent
|
|
# of the renderer source code and so appropriate for use in unit tests.
|
|
|
|
|
|
def dumpRangeBreaks(ranges):
|
|
return dumpRangeList(ranges, breaksOnly=True)
|
|
|
|
|
|
def dumpRangeLabels(ranges):
|
|
return dumpRangeList(ranges, labelsOnly=True)
|
|
|
|
|
|
def dumpLabelFormat(format):
|
|
return (
|
|
":"
|
|
+ format.format()
|
|
+ ":"
|
|
+ str(format.precision())
|
|
+ ":"
|
|
+ str(format.trimTrailingZeroes())
|
|
+ ":"
|
|
)
|
|
|
|
|
|
def dumpRangeList(rlist, breaksOnly=False, labelsOnly=False):
|
|
rstr = "("
|
|
format = "{0:.4f}-{1:.4f}"
|
|
if not breaksOnly:
|
|
format = format + ":{2}:{3}:{4}:"
|
|
if labelsOnly:
|
|
format = "{2}"
|
|
for r in rlist:
|
|
rstr = (
|
|
rstr
|
|
+ format.format(
|
|
r.lowerValue(),
|
|
r.upperValue(),
|
|
r.label(),
|
|
r.symbol().dump(),
|
|
r.renderState(),
|
|
)
|
|
+ ","
|
|
)
|
|
return rstr + ")"
|
|
|
|
|
|
# Crude dump for deterministic ramp - just dumps colors at a range of values
|
|
|
|
|
|
def dumpColorRamp(ramp):
|
|
if ramp is None:
|
|
return ":None:"
|
|
rampstr = ":"
|
|
for x in (0.0, 0.33, 0.66, 1.0):
|
|
rampstr = rampstr + ramp.color(x).name() + ":"
|
|
return rampstr
|
|
|
|
|
|
def dumpGraduatedRenderer(r):
|
|
rstr = ":"
|
|
rstr = rstr + r.classAttribute() + ":"
|
|
rstr = rstr + str(r.mode()) + ":"
|
|
symbol = r.sourceSymbol()
|
|
if symbol is None:
|
|
rstr = rstr + "None" + ":"
|
|
else:
|
|
rstr = rstr + symbol.dump() + ":"
|
|
rstr = rstr + dumpColorRamp(r.sourceColorRamp())
|
|
rstr = rstr + dumpRangeList(r.ranges())
|
|
return rstr
|
|
|
|
|
|
# =================================================================
|
|
# Tests
|
|
|
|
|
|
class TestQgsGraduatedSymbolRenderer(QgisTestCase):
|
|
|
|
def testQgsRendererRange_1(self):
|
|
"""Test QgsRendererRange getter/setter functions"""
|
|
range = QgsRendererRange()
|
|
self.assertTrue(range)
|
|
lower = 123.45
|
|
upper = 234.56
|
|
label = "Test label"
|
|
symbol = createMarkerSymbol()
|
|
range.setLowerValue(lower)
|
|
self.assertEqual(range.lowerValue(), lower, "Lower value getter/setter failed")
|
|
range.setUpperValue(upper)
|
|
self.assertEqual(range.upperValue(), upper, "Upper value getter/setter failed")
|
|
self.assertEqual(range[0], 123.45)
|
|
self.assertEqual(range[1], 234.56)
|
|
with self.assertRaises(IndexError):
|
|
range[2]
|
|
with self.assertRaises(IndexError):
|
|
range[-1]
|
|
range.setLabel(label)
|
|
self.assertEqual(range.label(), label, "Label getter/setter failed")
|
|
range.setRenderState(True)
|
|
self.assertTrue(range.renderState(), "Render state getter/setter failed")
|
|
range.setRenderState(False)
|
|
self.assertFalse(range.renderState(), "Render state getter/setter failed")
|
|
range.setSymbol(symbol.clone())
|
|
self.assertEqual(
|
|
symbol.dump(), range.symbol().dump(), "Symbol getter/setter failed"
|
|
)
|
|
range2 = QgsRendererRange(lower, upper, symbol.clone(), label, False)
|
|
self.assertEqual(
|
|
range2.lowerValue(), lower, "Lower value from constructor failed"
|
|
)
|
|
self.assertEqual(
|
|
range2.upperValue(), upper, "Upper value from constructor failed"
|
|
)
|
|
self.assertEqual(range2.label(), label, "Label from constructor failed")
|
|
self.assertEqual(
|
|
range2.symbol().dump(), symbol.dump(), "Symbol from constructor failed"
|
|
)
|
|
self.assertFalse(range2.renderState(), "Render state getter/setter failed")
|
|
|
|
def testQgsRendererRangeLabelFormat_1(self):
|
|
"""Test QgsRendererRangeLabelFormat getter/setter functions"""
|
|
format = QgsRendererRangeLabelFormat()
|
|
self.assertTrue(format, "QgsRendererRangeLabelFomat construction failed")
|
|
template = "%1 - %2 meters"
|
|
precision = 5
|
|
format.setFormat(template)
|
|
self.assertEqual(format.format(), template, "Format getter/setter failed")
|
|
format.setPrecision(precision)
|
|
self.assertEqual(
|
|
format.precision(), precision, "Precision getter/setter failed"
|
|
)
|
|
format.setTrimTrailingZeroes(True)
|
|
self.assertTrue(
|
|
format.trimTrailingZeroes(), "TrimTrailingZeroes getter/setter failed"
|
|
)
|
|
format.setTrimTrailingZeroes(False)
|
|
self.assertFalse(
|
|
format.trimTrailingZeroes(), "TrimTrailingZeroes getter/setter failed"
|
|
)
|
|
minprecision = -6
|
|
maxprecision = 15
|
|
self.assertEqual(
|
|
QgsRendererRangeLabelFormat.MIN_PRECISION,
|
|
minprecision,
|
|
"Minimum precision != -6",
|
|
)
|
|
self.assertEqual(
|
|
QgsRendererRangeLabelFormat.MAX_PRECISION,
|
|
maxprecision,
|
|
"Maximum precision != 15",
|
|
)
|
|
format.setPrecision(-10)
|
|
self.assertEqual(
|
|
format.precision(), minprecision, "Minimum precision not enforced"
|
|
)
|
|
format.setPrecision(20)
|
|
self.assertEqual(
|
|
format.precision(), maxprecision, "Maximum precision not enforced"
|
|
)
|
|
|
|
def testQgsRendererRangeLabelFormat_2(self):
|
|
"""Test QgsRendererRangeLabelFormat number format"""
|
|
format = QgsRendererRangeLabelFormat()
|
|
# Tests have precision, trim, value, expected
|
|
# (Note: Not sure what impact of locale is on these tests)
|
|
tests = (
|
|
(2, False, 1.0, "1.00"),
|
|
(2, True, 1.0, "1"),
|
|
(2, False, 1.234, "1.23"),
|
|
(2, True, 1.234, "1.23"),
|
|
(2, False, 1.236, "1.24"),
|
|
(2, False, -1.236, "-1.24"),
|
|
(2, False, -0.004, "0.00"),
|
|
(2, True, 1.002, "1"),
|
|
(2, True, 1.006, "1.01"),
|
|
(2, True, 1.096, "1.1"),
|
|
(3, True, 1.096, "1.096"),
|
|
(-2, True, 1496.45, "1500"),
|
|
(-2, True, 149.45, "100"),
|
|
(-2, True, 79.45, "100"),
|
|
(-2, True, 49.45, "0"),
|
|
(-2, True, -49.45, "0"),
|
|
(-2, True, -149.45, "-100"),
|
|
)
|
|
for f in tests:
|
|
precision, trim, value, expected = f
|
|
format.setPrecision(precision)
|
|
format.setTrimTrailingZeroes(trim)
|
|
result = format.formatNumber(value)
|
|
self.assertEqual(
|
|
result,
|
|
expected,
|
|
"Number format error {}:{}:{} => {}".format(
|
|
precision, trim, value, result
|
|
),
|
|
)
|
|
|
|
# Label tests - label format, expected result.
|
|
# Labels will be evaluated with lower=1.23 upper=2.34, precision=2
|
|
ltests = (
|
|
("%1 - %2", "1.23 - 2.34"),
|
|
("%1", "1.23"),
|
|
("%2", "2.34"),
|
|
("%2%", "2.34%"),
|
|
("%1%1", "1.231.23"),
|
|
("from %1 to %2 meters", "from 1.23 to 2.34 meters"),
|
|
("from %2 to %1 meters", "from 2.34 to 1.23 meters"),
|
|
("< %2", "< 2.34"),
|
|
)
|
|
format.setPrecision(2)
|
|
format.setTrimTrailingZeroes(False)
|
|
lower = 1.232
|
|
upper = 2.339
|
|
for t in ltests:
|
|
label, expected = t
|
|
format.setFormat(label)
|
|
result = format.labelForLowerUpper(lower, upper)
|
|
self.assertEqual(
|
|
result, expected, f"Label format error {label} => {result}"
|
|
)
|
|
|
|
range = QgsRendererRange()
|
|
range.setLowerValue(lower)
|
|
range.setUpperValue(upper)
|
|
label = ltests[0][0]
|
|
format.setFormat(label)
|
|
result = format.labelForRange(range)
|
|
self.assertEqual(
|
|
result, ltests[0][1], f"Label for range error {label} => {result}"
|
|
)
|
|
|
|
def testQgsGraduatedSymbolRenderer_1(self):
|
|
"""Test QgsGraduatedSymbolRenderer: Basic get/set functions"""
|
|
|
|
# Create a renderer
|
|
renderer = QgsGraduatedSymbolRenderer()
|
|
|
|
symbol = createMarkerSymbol()
|
|
renderer.setSourceSymbol(symbol.clone())
|
|
self.assertEqual(
|
|
symbol.dump(),
|
|
renderer.sourceSymbol().dump(),
|
|
"Get/set renderer source symbol",
|
|
)
|
|
|
|
attr = '"value"*"value"'
|
|
renderer.setClassAttribute(attr)
|
|
self.assertEqual(
|
|
attr, renderer.classAttribute(), "Get/set renderer class attribute"
|
|
)
|
|
|
|
for m in (
|
|
QgsGraduatedSymbolRenderer.Mode.Custom,
|
|
QgsGraduatedSymbolRenderer.Mode.EqualInterval,
|
|
QgsGraduatedSymbolRenderer.Mode.Quantile,
|
|
QgsGraduatedSymbolRenderer.Mode.Jenks,
|
|
QgsGraduatedSymbolRenderer.Mode.Pretty,
|
|
QgsGraduatedSymbolRenderer.Mode.StdDev,
|
|
):
|
|
renderer.setMode(m)
|
|
self.assertEqual(m, renderer.mode(), "Get/set renderer mode")
|
|
|
|
format = createLabelFormat()
|
|
renderer.setLabelFormat(format)
|
|
self.assertEqual(
|
|
dumpLabelFormat(format),
|
|
dumpLabelFormat(renderer.labelFormat()),
|
|
"Get/set renderer label format",
|
|
)
|
|
|
|
ramp = createColorRamp()
|
|
renderer.setSourceColorRamp(ramp)
|
|
self.assertEqual(
|
|
dumpColorRamp(ramp),
|
|
dumpColorRamp(renderer.sourceColorRamp()),
|
|
"Get/set renderer color ramp",
|
|
)
|
|
|
|
renderer.setSourceColorRamp(ramp)
|
|
self.assertEqual(
|
|
dumpColorRamp(ramp),
|
|
dumpColorRamp(renderer.sourceColorRamp()),
|
|
"Get/set renderer color ramp",
|
|
)
|
|
|
|
# test for classificatio with varying size
|
|
renderer.setGraduatedMethod(
|
|
QgsGraduatedSymbolRenderer.GraduatedMethod.GraduatedSize
|
|
)
|
|
renderer.setSourceColorRamp(None)
|
|
renderer.addClassLowerUpper(0, 2)
|
|
renderer.addClassLowerUpper(2, 4)
|
|
renderer.addClassLowerUpper(4, 6)
|
|
renderer.setSymbolSizes(2, 13)
|
|
self.assertEqual(renderer.maxSymbolSize(), 13)
|
|
self.assertEqual(renderer.minSymbolSize(), 2)
|
|
refSizes = [2, (13 + 2) * 0.5, 13]
|
|
ctx = QgsRenderContext()
|
|
for idx, symbol in enumerate(renderer.symbols(ctx)):
|
|
self.assertEqual(symbol.size(), refSizes[idx])
|
|
|
|
def testQgsGraduatedSymbolRenderer_2(self):
|
|
"""Test QgsGraduatedSymbolRenderer: Adding /removing/editing classes"""
|
|
# Create a renderer
|
|
renderer = QgsGraduatedSymbolRenderer()
|
|
symbol = createMarkerSymbol()
|
|
renderer.setSourceSymbol(symbol.clone())
|
|
symbol.setColor(QColor(255, 0, 0))
|
|
|
|
# Add class without start and end ranges
|
|
|
|
renderer.addClass(symbol.clone())
|
|
renderer.addClass(symbol.clone())
|
|
renderer.updateRangeLabel(1, "Second range")
|
|
renderer.updateRangeLowerValue(1, 10.0)
|
|
renderer.updateRangeUpperValue(1, 25.0)
|
|
renderer.updateRangeRenderState(1, False)
|
|
symbol.setColor(QColor(0, 0, 255))
|
|
renderer.updateRangeSymbol(1, symbol.clone())
|
|
|
|
# Add as a rangeobject
|
|
symbol.setColor(QColor(0, 255, 0))
|
|
range = QgsRendererRange(20.0, 25.5, symbol.clone(), "Third range", False)
|
|
renderer.addClassRange(range)
|
|
|
|
# Add classification method label and precision
|
|
renderer.classificationMethod().setLabelFormat("111")
|
|
renderer.classificationMethod().setLabelPrecision(1)
|
|
|
|
# Add class by lower and upper
|
|
renderer.addClassLowerUpper(25.5, 30.5)
|
|
# (Update label for sorting tests)
|
|
renderer.updateRangeLabel(3, "Another range")
|
|
|
|
self.assertEqual(
|
|
dumpRangeLabels(renderer.ranges()),
|
|
"(0.0 - 0.0,Second range,Third range,Another range,)",
|
|
"Added ranges labels not correct",
|
|
)
|
|
self.assertEqual(
|
|
dumpRangeBreaks(renderer.ranges()),
|
|
"(0.0000-0.0000,10.0000-25.0000,20.0000-25.5000,25.5000-30.5000,)",
|
|
"Added ranges lower/upper values not correct",
|
|
)
|
|
|
|
# Check that clone function works
|
|
|
|
renderer2 = renderer.clone()
|
|
self.assertEqual(
|
|
dumpGraduatedRenderer(renderer),
|
|
dumpGraduatedRenderer(renderer2),
|
|
"clone function doesn't replicate renderer properly",
|
|
)
|
|
|
|
# Check save and reload from Dom works
|
|
|
|
doc = QDomDocument()
|
|
element = renderer.save(doc, QgsReadWriteContext())
|
|
renderer2 = QgsGraduatedSymbolRenderer.create(element, QgsReadWriteContext())
|
|
self.assertEqual(
|
|
dumpGraduatedRenderer(renderer),
|
|
dumpGraduatedRenderer(renderer2),
|
|
"Save/create from DOM doesn't replicate renderer properly",
|
|
)
|
|
|
|
# Check classification method label and precision properly created from DOM
|
|
self.assertEqual(renderer2.classificationMethod().labelFormat(), "111")
|
|
self.assertEqual(renderer2.classificationMethod().labelPrecision(), 1)
|
|
|
|
# Check sorting
|
|
|
|
renderer.sortByLabel()
|
|
self.assertEqual(
|
|
dumpRangeList(renderer.ranges(), labelsOnly=True),
|
|
"(0.0 - 0.0,Another range,Second range,Third range,)",
|
|
"sortByLabel not correct",
|
|
)
|
|
renderer.sortByValue()
|
|
self.assertEqual(
|
|
dumpRangeBreaks(renderer.ranges()),
|
|
"(0.0000-0.0000,10.0000-25.0000,20.0000-25.5000,25.5000-30.5000,)",
|
|
"sortByValue not correct",
|
|
)
|
|
renderer.sortByValue(Qt.SortOrder.DescendingOrder)
|
|
self.assertEqual(
|
|
dumpRangeBreaks(renderer.ranges()),
|
|
"(25.5000-30.5000,20.0000-25.5000,10.0000-25.0000,0.0000-0.0000,)",
|
|
"sortByValue descending not correct",
|
|
)
|
|
|
|
# Check deleting
|
|
|
|
renderer.deleteClass(2)
|
|
self.assertEqual(
|
|
dumpRangeBreaks(renderer.ranges()),
|
|
"(25.5000-30.5000,20.0000-25.5000,0.0000-0.0000,)",
|
|
"deleteClass not correct",
|
|
)
|
|
|
|
renderer.deleteAllClasses()
|
|
self.assertEqual(
|
|
len(renderer.ranges()), 0, "deleteAllClasses didn't delete all"
|
|
)
|
|
|
|
# void addClass( QgsSymbol* symbol );
|
|
# //! \note available in python bindings as addClassRange
|
|
# void addClass( QgsRendererRange range ) /PyName=addClassRange/;
|
|
# //! \note available in python bindings as addClassLowerUpper
|
|
# void addClass( double lower, double upper ) /PyName=addClassLowerUpper/;
|
|
# void deleteClass( int idx );
|
|
# void deleteAllClasses();
|
|
|
|
def testQgsGraduatedSymbolRenderer_3(self):
|
|
"""Test QgsGraduatedSymbolRenderer: Reading attribute data, calculating classes"""
|
|
|
|
# Create a renderer
|
|
renderer = QgsGraduatedSymbolRenderer()
|
|
symbol = createMarkerSymbol()
|
|
renderer.setSourceSymbol(symbol.clone())
|
|
|
|
# Test retrieving data values from a layer
|
|
ml = createMemoryLayer((1.2, 0.5, 5.0, 1.0, 1.0, 1.2))
|
|
|
|
renderer.setClassAttribute("value")
|
|
# Equal interval calculations
|
|
renderer.updateClasses(ml, renderer.EqualInterval, 3)
|
|
self.assertEqual(
|
|
dumpRangeBreaks(renderer.ranges()),
|
|
"(0.5000-2.0000,2.0000-3.5000,3.5000-5.0000,)",
|
|
"Equal interval classification not correct",
|
|
)
|
|
|
|
# Quantile classes
|
|
renderer.updateClasses(ml, renderer.Quantile, 3)
|
|
self.assertEqual(
|
|
dumpRangeBreaks(renderer.ranges()),
|
|
"(0.5000-1.0000,1.0000-1.2000,1.2000-5.0000,)",
|
|
"Quantile classification not correct",
|
|
)
|
|
renderer.updateClasses(ml, renderer.Quantile, 4)
|
|
self.assertEqual(
|
|
dumpRangeBreaks(renderer.ranges()),
|
|
"(0.5000-1.0000,1.0000-1.1000,1.1000-1.2000,1.2000-5.0000,)",
|
|
"Quantile classification not correct",
|
|
)
|
|
|
|
def testUsedAttributes(self):
|
|
renderer = QgsGraduatedSymbolRenderer()
|
|
ctx = QgsRenderContext()
|
|
|
|
# attribute can contain either attribute name or an expression.
|
|
# Sometimes it is not possible to distinguish between those two,
|
|
# e.g. "a - b" can be both a valid attribute name or expression.
|
|
# Since we do not have access to fields here, the method should return both options.
|
|
renderer.setClassAttribute("value")
|
|
self.assertEqual(renderer.usedAttributes(ctx), {"value"})
|
|
renderer.setClassAttribute("value - 1")
|
|
self.assertEqual(renderer.usedAttributes(ctx), {"value", "value - 1"})
|
|
renderer.setClassAttribute("valuea - valueb")
|
|
self.assertEqual(
|
|
renderer.usedAttributes(ctx), {"valuea", "valuea - valueb", "valueb"}
|
|
)
|
|
|
|
def testFilterNeedsGeometry(self):
|
|
renderer = QgsGraduatedSymbolRenderer()
|
|
|
|
renderer.setClassAttribute("value")
|
|
self.assertFalse(renderer.filterNeedsGeometry())
|
|
renderer.setClassAttribute("$area")
|
|
self.assertTrue(renderer.filterNeedsGeometry())
|
|
renderer.setClassAttribute("value - $area")
|
|
self.assertTrue(renderer.filterNeedsGeometry())
|
|
|
|
def test_legend_keys(self):
|
|
renderer = QgsGraduatedSymbolRenderer()
|
|
renderer.setClassAttribute("field_name")
|
|
|
|
self.assertFalse(renderer.legendKeys())
|
|
|
|
symbol_a = createMarkerSymbol()
|
|
renderer.addClassRange(QgsRendererRange(1, 2, symbol_a, "a", True, "0"))
|
|
symbol_b = createMarkerSymbol()
|
|
renderer.addClassRange(QgsRendererRange(5, 6, symbol_b, "b", True, "1"))
|
|
symbol_c = createMarkerSymbol()
|
|
renderer.addClassRange(QgsRendererRange(15.5, 16.5, symbol_c, "c", False, "2"))
|
|
|
|
self.assertEqual(renderer.legendKeys(), {"0", "1", "2"})
|
|
|
|
def test_legend_key_to_expression(self):
|
|
renderer = QgsGraduatedSymbolRenderer()
|
|
renderer.setClassAttribute("field_name")
|
|
|
|
exp, ok = renderer.legendKeyToExpression("xxxx", None)
|
|
self.assertFalse(ok)
|
|
|
|
# no categories
|
|
exp, ok = renderer.legendKeyToExpression("0", None)
|
|
self.assertFalse(ok)
|
|
|
|
symbol_a = createMarkerSymbol()
|
|
renderer.addClassRange(QgsRendererRange(1, 2, symbol_a, "a", True, "0"))
|
|
symbol_b = createMarkerSymbol()
|
|
renderer.addClassRange(QgsRendererRange(5, 6, symbol_b, "b", True, "1"))
|
|
symbol_c = createMarkerSymbol()
|
|
renderer.addClassRange(QgsRendererRange(15.5, 16.5, symbol_c, "c", False, "2"))
|
|
|
|
exp, ok = renderer.legendKeyToExpression("0", None)
|
|
self.assertTrue(ok)
|
|
self.assertEqual(exp, "(field_name >= 1) AND (field_name <= 2)")
|
|
|
|
exp, ok = renderer.legendKeyToExpression("1", None)
|
|
self.assertTrue(ok)
|
|
self.assertEqual(exp, "(field_name >= 5) AND (field_name <= 6)")
|
|
|
|
exp, ok = renderer.legendKeyToExpression("2", None)
|
|
self.assertTrue(ok)
|
|
self.assertEqual(exp, "(field_name >= 15.5) AND (field_name <= 16.5)")
|
|
|
|
exp, ok = renderer.legendKeyToExpression("3", None)
|
|
self.assertFalse(ok)
|
|
|
|
layer = QgsVectorLayer(
|
|
"Point?field=field_name:double&field=fldint:integer", "addfeat", "memory"
|
|
)
|
|
# with layer
|
|
exp, ok = renderer.legendKeyToExpression("2", layer)
|
|
self.assertTrue(ok)
|
|
self.assertEqual(exp, """("field_name" >= 15.5) AND ("field_name" <= 16.5)""")
|
|
|
|
# with expression as attribute
|
|
renderer.setClassAttribute('log("field_name")')
|
|
|
|
exp, ok = renderer.legendKeyToExpression("0", None)
|
|
self.assertTrue(ok)
|
|
self.assertEqual(
|
|
exp, """(log("field_name") >= 1) AND (log("field_name") <= 2)"""
|
|
)
|
|
|
|
exp, ok = renderer.legendKeyToExpression("1", None)
|
|
self.assertTrue(ok)
|
|
self.assertEqual(
|
|
exp, """(log("field_name") >= 5) AND (log("field_name") <= 6)"""
|
|
)
|
|
|
|
exp, ok = renderer.legendKeyToExpression("2", None)
|
|
self.assertTrue(ok)
|
|
self.assertEqual(
|
|
exp, """(log("field_name") >= 15.5) AND (log("field_name") <= 16.5)"""
|
|
)
|
|
|
|
exp, ok = renderer.legendKeyToExpression("2", layer)
|
|
self.assertTrue(ok)
|
|
self.assertEqual(
|
|
exp, """(log("field_name") >= 15.5) AND (log("field_name") <= 16.5)"""
|
|
)
|
|
|
|
def test_to_sld(self):
|
|
renderer = QgsGraduatedSymbolRenderer()
|
|
renderer.setClassAttribute("field_name")
|
|
|
|
symbol_a = createMarkerSymbol()
|
|
renderer.addClassRange(QgsRendererRange(1, 2, symbol_a, "a", True, "0"))
|
|
symbol_b = createMarkerSymbol()
|
|
renderer.addClassRange(QgsRendererRange(5, 6, symbol_b, "b", True, "1"))
|
|
symbol_c = createMarkerSymbol()
|
|
renderer.addClassRange(QgsRendererRange(15.5, 16.5, symbol_c, "c", False, "2"))
|
|
|
|
# this category should NOT be included in the SLD, as it would otherwise result
|
|
# in an invalid se:rule with no symbolizer element
|
|
symbol_which_is_empty_in_sld = createFillSymbol()
|
|
symbol_which_is_empty_in_sld[0].setBrushStyle(Qt.BrushStyle.NoBrush)
|
|
symbol_which_is_empty_in_sld[0].setStrokeStyle(Qt.PenStyle.NoPen)
|
|
renderer.addClassRange(
|
|
QgsRendererRange(25.5, 26.5, symbol_which_is_empty_in_sld, "d", False, "2")
|
|
)
|
|
|
|
dom = QDomDocument()
|
|
root = dom.createElement("FakeRoot")
|
|
dom.appendChild(root)
|
|
renderer.toSld(dom, root, {})
|
|
|
|
expected = """<FakeRoot>
|
|
<se:Rule>
|
|
<se:Name>a</se:Name>
|
|
<se:Description>
|
|
<se:Title>a</se:Title>
|
|
</se:Description>
|
|
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
|
|
<ogc:And>
|
|
<ogc:PropertyIsGreaterThanOrEqualTo>
|
|
<ogc:PropertyName>field_name</ogc:PropertyName>
|
|
<ogc:Literal>1</ogc:Literal>
|
|
</ogc:PropertyIsGreaterThanOrEqualTo>
|
|
<ogc:PropertyIsLessThanOrEqualTo>
|
|
<ogc:PropertyName>field_name</ogc:PropertyName>
|
|
<ogc:Literal>2</ogc:Literal>
|
|
</ogc:PropertyIsLessThanOrEqualTo>
|
|
</ogc:And>
|
|
</ogc:Filter>
|
|
<se:PointSymbolizer>
|
|
<se:Graphic>
|
|
<se:Mark>
|
|
<se:WellKnownName>square</se:WellKnownName>
|
|
<se:Fill>
|
|
<se:SvgParameter name="fill">#649632</se:SvgParameter>
|
|
</se:Fill>
|
|
<se:Stroke>
|
|
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
|
|
<se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
|
|
</se:Stroke>
|
|
</se:Mark>
|
|
<se:Size>11</se:Size>
|
|
</se:Graphic>
|
|
</se:PointSymbolizer>
|
|
</se:Rule>
|
|
<se:Rule>
|
|
<se:Name>b</se:Name>
|
|
<se:Description>
|
|
<se:Title>b</se:Title>
|
|
</se:Description>
|
|
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
|
|
<ogc:And>
|
|
<ogc:PropertyIsGreaterThan>
|
|
<ogc:PropertyName>field_name</ogc:PropertyName>
|
|
<ogc:Literal>5</ogc:Literal>
|
|
</ogc:PropertyIsGreaterThan>
|
|
<ogc:PropertyIsLessThanOrEqualTo>
|
|
<ogc:PropertyName>field_name</ogc:PropertyName>
|
|
<ogc:Literal>6</ogc:Literal>
|
|
</ogc:PropertyIsLessThanOrEqualTo>
|
|
</ogc:And>
|
|
</ogc:Filter>
|
|
<se:PointSymbolizer>
|
|
<se:Graphic>
|
|
<se:Mark>
|
|
<se:WellKnownName>square</se:WellKnownName>
|
|
<se:Fill>
|
|
<se:SvgParameter name="fill">#649632</se:SvgParameter>
|
|
</se:Fill>
|
|
<se:Stroke>
|
|
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
|
|
<se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
|
|
</se:Stroke>
|
|
</se:Mark>
|
|
<se:Size>11</se:Size>
|
|
</se:Graphic>
|
|
</se:PointSymbolizer>
|
|
</se:Rule>
|
|
<se:Rule>
|
|
<se:Name>c</se:Name>
|
|
<se:Description>
|
|
<se:Title>c</se:Title>
|
|
</se:Description>
|
|
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
|
|
<ogc:And>
|
|
<ogc:PropertyIsGreaterThan>
|
|
<ogc:PropertyName>field_name</ogc:PropertyName>
|
|
<ogc:Literal>15.5</ogc:Literal>
|
|
</ogc:PropertyIsGreaterThan>
|
|
<ogc:PropertyIsLessThanOrEqualTo>
|
|
<ogc:PropertyName>field_name</ogc:PropertyName>
|
|
<ogc:Literal>16.5</ogc:Literal>
|
|
</ogc:PropertyIsLessThanOrEqualTo>
|
|
</ogc:And>
|
|
</ogc:Filter>
|
|
<se:PointSymbolizer>
|
|
<se:Graphic>
|
|
<se:Mark>
|
|
<se:WellKnownName>square</se:WellKnownName>
|
|
<se:Fill>
|
|
<se:SvgParameter name="fill">#649632</se:SvgParameter>
|
|
</se:Fill>
|
|
<se:Stroke>
|
|
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
|
|
<se:SvgParameter name="stroke-width">0.5</se:SvgParameter>
|
|
</se:Stroke>
|
|
</se:Mark>
|
|
<se:Size>11</se:Size>
|
|
</se:Graphic>
|
|
</se:PointSymbolizer>
|
|
</se:Rule>
|
|
</FakeRoot>
|
|
"""
|
|
|
|
self.assertEqual(dom.toString(), expected)
|
|
|
|
def testFixedIntervalSingleClass(self):
|
|
"""Test single class with fixed interval, issue GH #63277"""
|
|
|
|
# Create a renderer
|
|
renderer = QgsGraduatedSymbolRenderer()
|
|
renderer.setClassificationMethod(QgsClassificationFixedInterval())
|
|
symbol = createMarkerSymbol()
|
|
renderer.setSourceSymbol(symbol.clone())
|
|
|
|
# Test retrieving data values from a layer
|
|
ml = createMemoryLayer((0, 0))
|
|
|
|
renderer.setClassAttribute("value")
|
|
# Equal interval calculations
|
|
renderer.updateClasses(ml, 5)
|
|
self.assertEqual(
|
|
dumpRangeBreaks(renderer.ranges()),
|
|
"(0.0000-1.0000,)",
|
|
"Equal interval classification not correct",
|
|
)
|
|
|
|
ml = createMemoryLayer((1, 1))
|
|
renderer.updateClasses(ml, 5)
|
|
self.assertEqual(
|
|
dumpRangeBreaks(renderer.ranges()),
|
|
"(1.0000-2.0000,)",
|
|
"Equal interval classification not correct",
|
|
)
|
|
|
|
def testPrettyBreakSingleClass(self):
|
|
"""Test single class with pretty break, issue GH #63277"""
|
|
|
|
# Create a renderer
|
|
renderer = QgsGraduatedSymbolRenderer()
|
|
renderer.setClassificationMethod(QgsClassificationPrettyBreaks())
|
|
symbol = createMarkerSymbol()
|
|
renderer.setSourceSymbol(symbol.clone())
|
|
|
|
# Test retrieving data values from a layer
|
|
ml = createMemoryLayer((0, 0))
|
|
|
|
renderer.setClassAttribute("value")
|
|
# Equal interval calculations
|
|
renderer.updateClasses(ml, 5)
|
|
self.assertEqual(
|
|
dumpRangeBreaks(renderer.ranges()),
|
|
"(0.0000-1.0000,)",
|
|
"Equal interval classification not correct",
|
|
)
|
|
|
|
ml = createMemoryLayer((1, 1))
|
|
renderer.updateClasses(ml, 5)
|
|
self.assertEqual(
|
|
dumpRangeBreaks(renderer.ranges()),
|
|
"(1.0000-2.0000,)",
|
|
"Equal interval classification not correct",
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|