Skip to content
Permalink
Browse files

Fix bug in polygon label candidate ranking

Candidates furthest from any obstacles were being preferred, even
when this resulted in labels being located around the edges of polygon
features.

The correct logic should be only to consider direct overlaps of the
candidate with an obstacle as a conflict, and if a candidate does
NOT overlap and obstacles then we rely on the "put labels furthest
from edges as possible" rule.
  • Loading branch information
nyalldawson committed Dec 24, 2019
1 parent 032e8fd commit bb42c43829a7838d09541aef401bee221c74e4d9
@@ -182,7 +182,13 @@ void CostCalculator::setCandidateCostFromPolygon( LabelPosition *lp, PalRtree<Fe
obstacles->intersects( lp->feature->boundingBox(), [&pCost]( const FeaturePart * obstacle )->bool
{
LabelPosition *lp = pCost.getLabel();
if ( ( obstacle == lp->feature ) || ( obstacle->getHoleOf() && obstacle->getHoleOf() != lp->feature ) )

// we only care about obstacles which are polygon holes, AND only holes which belong to this same feature
// because:
// 1. holes for other features are a good place to put labels for this feature
// 2. we handle obstacle avoidance for all candidate types elsewhere -- here we are solely concerned with
// ranking the relative candidates for a single feature while considering that feature's shape alone.
if ( ( obstacle == lp->feature ) || ( !obstacle->getHoleOf() ) || ( obstacle->getHoleOf() && obstacle->getHoleOf() != lp->feature ) )
{
return true;
}
@@ -165,6 +165,24 @@ def test_polygon_placement_with_hole_and_point(self):
self.removeMapLayer(polyLayer)
self.layer = None

def test_polygon_placement_with_obstacle(self):
# Horizontal label placement for polygon and a line obstacle
self.layer = TestQgsPalLabeling.loadFeatureLayer('polygon_rect')
obstacleLayer = TestQgsPalLabeling.loadFeatureLayer('polygon_with_hole_line_obstacle')
obstacle_label_settings = QgsPalLayerSettings()
obstacle_label_settings.obstacle = True
obstacle_label_settings.drawLabels = False
obstacle_label_settings.obstacleFactor = 7
obstacleLayer.setLabeling(QgsVectorLayerSimpleLabeling(obstacle_label_settings))
obstacleLayer.setLabelsEnabled(True)

self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.lyr.placement = QgsPalLayerSettings.Horizontal
self.checkTest()
self.removeMapLayer(obstacleLayer)
self.removeMapLayer(self.layer)
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.
Binary file not shown.
@@ -0,0 +1,8 @@
{
"type": "FeatureCollection",
"name": "polygon_rect",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::32613" } },
"features": [
{ "type": "Feature", "properties": { "text": "Polygon" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 606348.47103323682677, 4823011.594442130066454 ], [ 606348.47103323682677, 4827007.190112059004605 ], [ 613301.258978648926131, 4827007.190112059004605 ], [ 613301.258978648926131, 4823011.594442130066454 ], [ 606348.47103323682677, 4823011.594442130066454 ] ] ] } }
]
}
@@ -0,0 +1,254 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis simplifyDrawingHints="1" simplifyLocal="1" simplifyMaxScale="1" labelsEnabled="1" simplifyAlgorithm="0" minScale="1e+08" readOnly="0" version="3.11.0-Master" hasScaleBasedVisibilityFlag="0" styleCategories="AllStyleCategories" maxScale="100000" simplifyDrawingTol="1">
<flags>
<Identifiable>1</Identifiable>
<Removable>1</Removable>
<Searchable>1</Searchable>
</flags>
<renderer-v2 symbollevels="0" enableorderby="0" type="singleSymbol" forceraster="0">
<symbols>
<symbol clip_to_extent="1" force_rhr="0" type="fill" name="0" alpha="1">
<layer locked="0" pass="0" enabled="1" class="SimpleFill">
<prop k="border_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="color" v="133,149,161,255"/>
<prop k="joinstyle" v="bevel"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="0,0,0,255"/>
<prop k="outline_style" v="no"/>
<prop k="outline_width" v="0.26"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="style" v="solid"/>
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
</renderer-v2>
<labeling type="simple">
<settings calloutType="simple">
<text-style useSubstitutions="0" fontWeight="50" fontItalic="0" fieldName="text" previewBkgrdColor="255,255,255,255" fontWordSpacing="0" multilineHeight="1" fontSizeMapUnitScale="3x:0,0,0,0,0,0" fontStrikeout="0" isExpression="0" fontSize="11" textOrientation="horizontal" fontLetterSpacing="0" textOpacity="1" fontFamily="Cantarell" fontSizeUnit="Point" textColor="0,0,0,255" fontCapitals="0" blendMode="0" namedStyle="Regular" fontKerning="1" fontUnderline="0">
<text-buffer bufferJoinStyle="64" bufferBlendMode="0" bufferOpacity="1" bufferSizeMapUnitScale="3x:0,0,0,0,0,0" bufferSizeUnits="MM" bufferColor="255,255,255,255" bufferSize="1" bufferNoFill="0" bufferDraw="0"/>
<text-mask maskOpacity="1" maskSizeUnits="MM" maskSize="1.5" maskedSymbolLayers="" maskJoinStyle="64" maskEnabled="0" maskSizeMapUnitScale="3x:0,0,0,0,0,0" maskType="0"/>
<background shapeSizeUnit="MM" shapeRadiiY="0" shapeSVGFile="" shapeJoinStyle="64" shapeRadiiUnit="MM" shapeRotationType="0" shapeRotation="0" shapeSizeX="0" shapeType="0" shapeOffsetMapUnitScale="3x:0,0,0,0,0,0" shapeSizeMapUnitScale="3x:0,0,0,0,0,0" shapeDraw="0" shapeBlendMode="0" shapeBorderWidthUnit="MM" shapeOffsetUnit="MM" shapeRadiiMapUnitScale="3x:0,0,0,0,0,0" shapeBorderColor="128,128,128,255" shapeRadiiX="0" shapeBorderWidthMapUnitScale="3x:0,0,0,0,0,0" shapeOffsetY="0" shapeSizeY="0" shapeSizeType="0" shapeFillColor="255,255,255,255" shapeBorderWidth="0" shapeOffsetX="0" shapeOpacity="1">
<symbol clip_to_extent="1" force_rhr="0" type="marker" name="markerSymbol" alpha="1">
<layer locked="0" pass="0" enabled="1" class="SimpleMarker">
<prop k="angle" v="0"/>
<prop k="color" v="141,90,153,255"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="35,35,35,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="2"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="MM"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</background>
<shadow shadowOffsetGlobal="1" shadowUnder="0" shadowOffsetUnit="MM" shadowScale="100" shadowOffsetDist="1" shadowDraw="0" shadowOpacity="0.7" shadowRadiusAlphaOnly="0" shadowOffsetAngle="135" shadowRadiusMapUnitScale="3x:0,0,0,0,0,0" shadowOffsetMapUnitScale="3x:0,0,0,0,0,0" shadowBlendMode="6" shadowColor="0,0,0,255" shadowRadius="1.5" shadowRadiusUnit="MM"/>
<dd_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</dd_properties>
<substitutions/>
</text-style>
<text-format useMaxLineLengthForAutoWrap="1" leftDirectionSymbol="&lt;" multilineAlign="0" rightDirectionSymbol=">" addDirectionSymbol="0" formatNumbers="0" placeDirectionSymbol="0" wrapChar="" autoWrapLength="0" reverseDirectionSymbol="0" decimals="3" plussign="0"/>
<placement overrunDistanceMapUnitScale="3x:0,0,0,0,0,0" geometryGeneratorType="PointGeometry" centroidInside="0" centroidWhole="0" xOffset="0" geometryGenerator="" maxCurvedCharAngleIn="20" quadOffset="4" repeatDistance="0" overrunDistanceUnit="MM" layerType="PolygonGeometry" preserveRotation="1" maxCurvedCharAngleOut="-20" predefinedPositionOrder="TR,TL,BR,BL,R,L,TSR,BSR" distMapUnitScale="3x:0,0,0,0,0,0" geometryGeneratorEnabled="0" offsetType="0" rotationAngle="0" repeatDistanceUnits="MM" placement="5" distUnits="MM" placementFlags="10" yOffset="0" fitInPolygonOnly="0" dist="0" labelOffsetMapUnitScale="3x:0,0,0,0,0,0" overrunDistance="0" priority="5" offsetUnits="MapUnit" repeatDistanceMapUnitScale="3x:0,0,0,0,0,0"/>
<rendering scaleMin="1" maxNumLabels="2000" zIndex="0" fontMinPixelSize="3" obstacleType="0" obstacle="1" fontMaxPixelSize="10000" labelPerPart="0" upsidedownLabels="0" scaleMax="10000000" minFeatureSize="0" displayAll="0" mergeLines="0" drawLabels="1" limitNumLabels="0" fontLimitPixelSize="0" obstacleFactor="1" scaleVisibility="0"/>
<dd_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</dd_properties>
<callout type="simple">
<Option type="Map">
<Option type="QString" name="anchorPoint" value="pole_of_inaccessibility"/>
<Option type="Map" name="ddProperties">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
<Option type="bool" name="drawToAllParts" value="false"/>
<Option type="QString" name="enabled" value="0"/>
<Option type="QString" name="lineSymbol" value="&lt;symbol clip_to_extent=&quot;1&quot; force_rhr=&quot;0&quot; type=&quot;line&quot; name=&quot;symbol&quot; alpha=&quot;1&quot;>&lt;layer locked=&quot;0&quot; pass=&quot;0&quot; enabled=&quot;1&quot; class=&quot;SimpleLine&quot;>&lt;prop k=&quot;capstyle&quot; v=&quot;square&quot;/>&lt;prop k=&quot;customdash&quot; v=&quot;5;2&quot;/>&lt;prop k=&quot;customdash_map_unit_scale&quot; v=&quot;3x:0,0,0,0,0,0&quot;/>&lt;prop k=&quot;customdash_unit&quot; v=&quot;MM&quot;/>&lt;prop k=&quot;draw_inside_polygon&quot; v=&quot;0&quot;/>&lt;prop k=&quot;joinstyle&quot; v=&quot;bevel&quot;/>&lt;prop k=&quot;line_color&quot; v=&quot;60,60,60,255&quot;/>&lt;prop k=&quot;line_style&quot; v=&quot;solid&quot;/>&lt;prop k=&quot;line_width&quot; v=&quot;0.3&quot;/>&lt;prop k=&quot;line_width_unit&quot; v=&quot;MM&quot;/>&lt;prop k=&quot;offset&quot; v=&quot;0&quot;/>&lt;prop k=&quot;offset_map_unit_scale&quot; v=&quot;3x:0,0,0,0,0,0&quot;/>&lt;prop k=&quot;offset_unit&quot; v=&quot;MM&quot;/>&lt;prop k=&quot;ring_filter&quot; v=&quot;0&quot;/>&lt;prop k=&quot;use_custom_dash&quot; v=&quot;0&quot;/>&lt;prop k=&quot;width_map_unit_scale&quot; v=&quot;3x:0,0,0,0,0,0&quot;/>&lt;data_defined_properties>&lt;Option type=&quot;Map&quot;>&lt;Option type=&quot;QString&quot; name=&quot;name&quot; value=&quot;&quot;/>&lt;Option name=&quot;properties&quot;/>&lt;Option type=&quot;QString&quot; name=&quot;type&quot; value=&quot;collection&quot;/>&lt;/Option>&lt;/data_defined_properties>&lt;/layer>&lt;/symbol>"/>
<Option type="double" name="minLength" value="0"/>
<Option type="QString" name="minLengthMapUnitScale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="minLengthUnit" value="MM"/>
<Option type="double" name="offsetFromAnchor" value="0"/>
<Option type="QString" name="offsetFromAnchorMapUnitScale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="offsetFromAnchorUnit" value="MM"/>
<Option type="double" name="offsetFromLabel" value="0"/>
<Option type="QString" name="offsetFromLabelMapUnitScale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="offsetFromLabelUnit" value="MM"/>
</Option>
</callout>
</settings>
</labeling>
<customproperties>
<property key="dualview/previewExpressions">
<value>text</value>
</property>
<property key="embeddedWidgets/count" value="0"/>
<property key="variableNames"/>
<property key="variableValues"/>
</customproperties>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>1</layerOpacity>
<SingleCategoryDiagramRenderer diagramType="Histogram" attributeLegend="1">
<DiagramCategory sizeType="MM" barWidth="5" scaleDependency="Area" direction="1" minScaleDenominator="100000" height="15" width="15" labelPlacementMethod="XHeight" opacity="1" showAxis="0" spacing="0" lineSizeScale="3x:0,0,0,0,0,0" backgroundAlpha="255" penAlpha="255" scaleBasedVisibility="0" backgroundColor="#ffffff" spacingUnitScale="3x:0,0,0,0,0,0" diagramOrientation="Up" penWidth="0" lineSizeType="MM" rotationOffset="270" maxScaleDenominator="1e+08" spacingUnit="MM" penColor="#000000" enabled="0" sizeScale="3x:0,0,0,0,0,0" minimumSize="0">
<fontProperties style="" description="Ubuntu,11,-1,5,50,0,0,0,0,0"/>
<attribute label="" color="#000000" field=""/>
<axisSymbol>
<symbol clip_to_extent="1" force_rhr="0" type="line" name="" alpha="1">
<layer locked="0" pass="0" enabled="1" class="SimpleLine">
<prop k="capstyle" v="square"/>
<prop k="customdash" v="5;2"/>
<prop k="customdash_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="customdash_unit" v="MM"/>
<prop k="draw_inside_polygon" v="0"/>
<prop k="joinstyle" v="bevel"/>
<prop k="line_color" v="35,35,35,255"/>
<prop k="line_style" v="solid"/>
<prop k="line_width" v="0.26"/>
<prop k="line_width_unit" v="MM"/>
<prop k="offset" v="0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="ring_filter" v="0"/>
<prop k="use_custom_dash" v="0"/>
<prop k="width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</axisSymbol>
</DiagramCategory>
</SingleCategoryDiagramRenderer>
<DiagramLayerSettings placement="0" priority="0" dist="0" showAll="1" linePlacementFlags="2" zIndex="0" obstacle="0">
<properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</properties>
</DiagramLayerSettings>
<geometryOptions geometryPrecision="0" removeDuplicateNodes="0">
<activeChecks/>
<checkConfiguration type="Map">
<Option type="Map" name="QgsGeometryGapCheck">
<Option type="double" name="allowedGapsBuffer" value="0"/>
<Option type="bool" name="allowedGapsEnabled" value="false"/>
<Option type="QString" name="allowedGapsLayer" value=""/>
</Option>
</checkConfiguration>
</geometryOptions>
<referencedLayers/>
<referencingLayers/>
<fieldConfiguration>
<field name="text">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
</fieldConfiguration>
<aliases>
<alias name="" index="0" field="text"/>
</aliases>
<excludeAttributesWMS/>
<excludeAttributesWFS/>
<defaults>
<default applyOnUpdate="0" field="text" expression=""/>
</defaults>
<constraints>
<constraint unique_strength="0" notnull_strength="0" constraints="0" exp_strength="0" field="text"/>
</constraints>
<constraintExpressions>
<constraint exp="" desc="" field="text"/>
</constraintExpressions>
<expressionfields/>
<attributeactions>
<defaultAction key="Canvas" value="{00000000-0000-0000-0000-000000000000}"/>
</attributeactions>
<attributetableconfig sortExpression="" sortOrder="0" actionWidgetStyle="dropDown">
<columns>
<column type="actions" width="-1" hidden="1"/>
<column type="field" name="text" width="-1" hidden="0"/>
</columns>
</attributetableconfig>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
<storedexpressions/>
<editform tolerant="1"></editform>
<editforminit/>
<editforminitcodesource>0</editforminitcodesource>
<editforminitfilepath></editforminitfilepath>
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
"""
QGIS forms can have a Python function that is called when the form is
opened.
Use this function to add extra logic to your forms.
Enter the name of the function in the "Python Init function"
field.
An example follows:
"""
from PyQt4.QtGui import QWidget

def my_form_open(dialog, layer, feature):
geom = feature.geometry()
control = dialog.findChild(QWidget, "MyLineEdit")
]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<editorlayout>generatedlayout</editorlayout>
<editable>
<field name="text" editable="1"/>
</editable>
<labelOnTop>
<field name="text" labelOnTop="0"/>
</labelOnTop>
<widgets/>
<previewExpression>text</previewExpression>
<mapTip></mapTip>
<layerGeometryType>2</layerGeometryType>
</qgis>
@@ -0,0 +1,8 @@
{
"type": "FeatureCollection",
"name": "polygon_with_hole_line_obstacle",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::32613" } },
"features": [
{ "type": "Feature", "properties": { }, "geometry": { "type": "LineString", "coordinates": [ [ 609769.435287182219326, 4826971.812226937152445 ], [ 609825.629525374737568, 4823006.104560206644237 ] ] } }
]
}

0 comments on commit bb42c43

Please sign in to comment.
You can’t perform that action at this time.