-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
test_qgspallabeling_placement.py
456 lines (402 loc) · 20.9 KB
/
test_qgspallabeling_placement.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsPalLabeling: base suite of render check tests
Class is meant to be inherited by classes that test different labeling outputs
See <qgis-src-dir>/tests/testdata/labeling/README.rst for description.
.. 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__ = 'Nyall Dawson'
__date__ = '2015-08-24'
__copyright__ = 'Copyright 2015, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import qgis # NOQA
import os
import sys
from qgis.PyQt.QtCore import QThreadPool, qDebug
from qgis.core import (QgsPalLayerSettings,
QgsSingleSymbolRenderer,
QgsMarkerSymbol,
QgsProperty)
from utilities import getTempfilePath, renderMapToImage, mapSettingsString
from test_qgspallabeling_base import TestQgsPalLabeling, runSuite
# noinspection PyPep8Naming
class TestPlacementBase(TestQgsPalLabeling):
@classmethod
def setUpClass(cls):
if not cls._BaseSetup:
TestQgsPalLabeling.setUpClass()
cls._Pal.setDrawLabelRectOnly(True)
cls._Pal.saveEngineSettings()
@classmethod
def tearDownClass(cls):
TestQgsPalLabeling.tearDownClass()
# avoid crash on finish, probably related to https://bugreports.qt.io/browse/QTBUG-35760
QThreadPool.globalInstance().waitForDone()
def setUp(self):
"""Run before each test."""
super(TestPlacementBase, self).setUp()
self.removeAllLayers()
self.configTest('pal_placement', 'sp')
self._TestImage = ''
# ensure per test map settings stay encapsulated
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self._Mismatch = 0
self._ColorTol = 0
self._Mismatches.clear()
self._ColorTols.clear()
def checkTest(self, **kwargs):
self.lyr.writeToLayer(self.layer)
ms = self._MapSettings # class settings
settings_type = 'Class'
if self._TestMapSettings is not None:
ms = self._TestMapSettings # per test settings
settings_type = 'Test'
if 'PAL_VERBOSE' in os.environ:
qDebug('MapSettings type: {0}'.format(settings_type))
qDebug(mapSettingsString(ms))
img = renderMapToImage(ms, parallel=False)
self._TestImage = getTempfilePath('png')
if not img.save(self._TestImage, 'png'):
os.unlink(self._TestImage)
raise OSError('Failed to save output from map render job')
self.saveControlImage(self._TestImage)
mismatch = 0
if 'PAL_NO_MISMATCH' not in os.environ:
# some mismatch expected
mismatch = self._Mismatch if self._Mismatch else 0
if self._TestGroup in self._Mismatches:
mismatch = self._Mismatches[self._TestGroup]
colortol = 0
if 'PAL_NO_COLORTOL' not in os.environ:
colortol = self._ColorTol if self._ColorTol else 0
if self._TestGroup in self._ColorTols:
colortol = self._ColorTols[self._TestGroup]
self.assertTrue(*self.renderCheck(mismatch=mismatch,
colortol=colortol,
imgpath=self._TestImage))
# noinspection PyPep8Naming
class TestPointPlacement(TestPlacementBase):
@classmethod
def setUpClass(cls):
TestPlacementBase.setUpClass()
cls.layer = None
def test_point_placement_around(self):
# Default point label placement
self.layer = TestQgsPalLabeling.loadFeatureLayer('point')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_point_placement_around_obstacle(self):
# Default point label placement with obstacle
self.layer = TestQgsPalLabeling.loadFeatureLayer('point2')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_point_placement_narrow_polygon_obstacle(self):
# Default point label placement with narrow polygon obstacle
self.layer = TestQgsPalLabeling.loadFeatureLayer('point')
polyLayer = TestQgsPalLabeling.loadFeatureLayer('narrow_polygon')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.checkTest()
self.removeMapLayer(self.layer)
self.removeMapLayer(polyLayer)
self.layer = None
def test_point_placement_around_obstacle_large_symbol(self):
# Default point label placement with obstacle and large symbols
self.layer = TestQgsPalLabeling.loadFeatureLayer('point3')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_polygon_placement_with_hole(self):
# Horizontal label placement for polygon with hole
# Note for this test, the mask is used to check only pixels outside of the polygon.
# We don't care where in the polygon the label is, just that it
# is INSIDE the polygon
self.layer = TestQgsPalLabeling.loadFeatureLayer('polygon_with_hole')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Horizontal
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_polygon_placement_with_hole_and_point(self):
# Testing that hole from a feature is not treated as an obstacle for other feature's labels
self.layer = TestQgsPalLabeling.loadFeatureLayer('point')
polyLayer = TestQgsPalLabeling.loadFeatureLayer('polygon_with_hole')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.checkTest()
self.removeMapLayer(self.layer)
self.removeMapLayer(polyLayer)
self.layer = None
def test_polygon_multiple_labels(self):
# Horizontal label placement for polygon with hole
# Note for this test, the mask is used to check only pixels outside of the polygon.
# We don't care where in the polygon the label is, just that it
# is INSIDE the polygon
self.layer = TestQgsPalLabeling.loadFeatureLayer('polygon_rule_based')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Horizontal
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_multipolygon_obstacle(self):
# Test that all parts of multipolygon are used as an obstacle
self.layer = TestQgsPalLabeling.loadFeatureLayer('point')
polyLayer = TestQgsPalLabeling.loadFeatureLayer('multi_polygon')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.checkTest()
self.removeMapLayer(self.layer)
self.removeMapLayer(polyLayer)
self.layer = None
def test_point_ordered_placement1(self):
# Test ordered placements for point
self.layer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_placement')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.OrderedPositionsAroundPoint
self.lyr.dist = 2
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_point_ordered_placement2(self):
# Test ordered placements for point (1 obstacle)
self.layer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_placement')
obstacleLayer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_obstacle1')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.OrderedPositionsAroundPoint
self.lyr.dist = 2
self.checkTest()
self.removeMapLayer(obstacleLayer)
self.removeMapLayer(self.layer)
self.layer = None
def test_point_ordered_placement3(self):
# Test ordered placements for point (2 obstacle)
self.layer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_placement')
obstacleLayer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_obstacle2')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.OrderedPositionsAroundPoint
self.lyr.dist = 2
self.checkTest()
self.removeMapLayer(obstacleLayer)
self.removeMapLayer(self.layer)
self.layer = None
def test_point_ordered_placement4(self):
# Test ordered placements for point (3 obstacle)
self.layer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_placement')
obstacleLayer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_obstacle3')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.OrderedPositionsAroundPoint
self.lyr.dist = 2
self.checkTest()
self.removeMapLayer(obstacleLayer)
self.removeMapLayer(self.layer)
self.layer = None
def test_point_dd_ordered_placement(self):
# Test ordered placements for point with data defined order
self.layer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_placement')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.OrderedPositionsAroundPoint
self.lyr.dist = 2
self.lyr.properties().setProperty(QgsPalLayerSettings.PredefinedPositionOrder, QgsProperty.fromExpression("'T,B'"))
self.checkTest()
self.removeMapLayer(self.layer)
self.lyr.removeDataDefinedProperty(QgsPalLayerSettings.PredefinedPositionOrder)
self.layer = None
def test_point_dd_ordered_placement1(self):
# Test ordered placements for point with data defined order and obstacle
self.layer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_placement')
obstacleLayer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_obstacle_top')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.OrderedPositionsAroundPoint
self.lyr.dist = 2
self.lyr.properties().setProperty(QgsPalLayerSettings.PredefinedPositionOrder, QgsProperty.fromExpression("'T,B'"))
self.checkTest()
self.removeMapLayer(obstacleLayer)
self.removeMapLayer(self.layer)
self.lyr.removeDataDefinedProperty(QgsPalLayerSettings.PredefinedPositionOrder)
self.layer = None
def test_point_ordered_symbol_bound_offset(self):
# Test ordered placements for point using symbol bounds offset
self.layer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_placement')
# Make a big symbol
symbol = QgsMarkerSymbol.createSimple({'color': '31,120,180,255',
'outline_color': '0,0,0,0',
'outline_style': 'solid',
'size': '10',
'name': 'rectangle',
'size_unit': 'MM'})
renderer = QgsSingleSymbolRenderer(symbol)
self.layer.setRenderer(renderer)
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.OrderedPositionsAroundPoint
self.lyr.dist = 2
self.lyr.offsetType = QgsPalLayerSettings.FromSymbolBounds
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_polygon_placement_perimeter(self):
# Default polygon perimeter placement
self.layer = TestQgsPalLabeling.loadFeatureLayer('polygon_perimeter')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Line
self.lyr.placementFlags = QgsPalLayerSettings.AboveLine
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_small_polygon_placement_perimeter(self):
# Default polygon perimeter placement for small polygon
self.layer = TestQgsPalLabeling.loadFeatureLayer('polygon_small')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Line
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_small_polygon_perimeter_only_fit(self):
# Polygon perimeter placement for small polygon when set to only show labels which fit in polygon
self.layer = TestQgsPalLabeling.loadFeatureLayer('polygon_small')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Line
self.lyr.fitInPolygonOnly = True
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_small_polygon_curvedperimeter_only_fit(self):
# Polygon perimeter placement for small polygon when set to only show labels which fit in polygon
self.layer = TestQgsPalLabeling.loadFeatureLayer('polygon_small')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.PerimeterCurved
self.lyr.fitInPolygonOnly = True
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_small_polygon_over_point_only_fit(self):
# Polygon over point placement for small polygon when set to only show labels which fit in polygon
self.layer = TestQgsPalLabeling.loadFeatureLayer('polygon_small')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.OverPoint
self.lyr.fitInPolygonOnly = True
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_prefer_line_curved_above_instead_of_below(self):
# Test that labeling a line using curved labels when both above and below placement are allowed that above
# is preferred
self.layer = TestQgsPalLabeling.loadFeatureLayer('line')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Curved
self.lyr.placementFlags = QgsPalLayerSettings.AboveLine | QgsPalLayerSettings.BelowLine | QgsPalLayerSettings.MapOrientation
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_prefer_line_curved_above_instead_of_online(self):
# Test that labeling a line using curved labels when both above and online placement are allowed that above
# is preferred
self.layer = TestQgsPalLabeling.loadFeatureLayer('line')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Curved
self.lyr.placementFlags = QgsPalLayerSettings.AboveLine | QgsPalLayerSettings.OnLine | QgsPalLayerSettings.MapOrientation
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_prefer_line_curved_below_instead_of_online(self):
# Test that labeling a line using curved labels when both below and online placement are allowed that below
# is preferred
self.layer = TestQgsPalLabeling.loadFeatureLayer('line')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Curved
self.lyr.placementFlags = QgsPalLayerSettings.BelowLine | QgsPalLayerSettings.OnLine | QgsPalLayerSettings.MapOrientation
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_prefer_line_above_instead_of_below(self):
# Test that labeling a line using parallel labels when both above and below placement are allowed that above
# is preferred
self.layer = TestQgsPalLabeling.loadFeatureLayer('line')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Line
self.lyr.placementFlags = QgsPalLayerSettings.AboveLine | QgsPalLayerSettings.BelowLine | QgsPalLayerSettings.MapOrientation
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_prefer_line_above_instead_of_online(self):
# Test that labeling a line using parallel labels when both above and online placement are allowed that above
# is preferred
self.layer = TestQgsPalLabeling.loadFeatureLayer('line')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Line
self.lyr.placementFlags = QgsPalLayerSettings.AboveLine | QgsPalLayerSettings.OnLine | QgsPalLayerSettings.MapOrientation
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_prefer_line_below_instead_of_online(self):
# Test that labeling a line using parallel labels when both below and online placement are allowed that below
# is preferred
self.layer = TestQgsPalLabeling.loadFeatureLayer('line')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Line
self.lyr.placementFlags = QgsPalLayerSettings.BelowLine | QgsPalLayerSettings.OnLine | QgsPalLayerSettings.MapOrientation
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_prefer_longer_lines_over_shorter(self):
# Test that labeling a line using parallel labels will tend to place the labels over the longer straight parts of
# the line
self.layer = TestQgsPalLabeling.loadFeatureLayer('line_placement_1')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Line
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_prefer_more_horizontal_lines(self):
# Test that labeling a line using parallel labels will tend to place the labels over more horizontal sections
self.layer = TestQgsPalLabeling.loadFeatureLayer('line_placement_2')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Line
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_label_line_over_small_angles(self):
# Test that labeling a line using parallel labels will place labels near center of straightish line
self.layer = TestQgsPalLabeling.loadFeatureLayer('line_placement_3')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Line
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_label_line_toward_center(self):
# Test that labeling a line using parallel labels will try to place labels as close to center of line as possible
self.layer = TestQgsPalLabeling.loadFeatureLayer('line_placement_4')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Line
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_label_line_avoid_jaggy(self):
# Test that labeling a line using parallel labels won't place labels over jaggy bits of line
self.layer = TestQgsPalLabeling.loadFeatureLayer('line_placement_5')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Line
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
def test_label_curved_zero_width_char(self):
# Test that curved label work with zero-width characters
self.layer = TestQgsPalLabeling.loadFeatureLayer('line')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Curved
self.lyr.placementFlags = QgsPalLayerSettings.OnLine
self.lyr.fieldName = "'invisiblespace'"
self.lyr.isExpression = True
self.checkTest()
self.removeMapLayer(self.layer)
self.layer = None
if __name__ == '__main__':
# NOTE: unless PAL_SUITE env var is set all test class methods will be run
# SEE: test_qgspallabeling_tests.suiteTests() to define suite
suite = ('TestPointPlacement')
res = runSuite(sys.modules[__name__], suite)
sys.exit(not res.wasSuccessful())