Skip to content

Commit f9fa979

Browse files
committed
[pal] Improved test for candidate against polygon obstacles
Previous test was just checking point in polygon for the corner, mid points and center. This test was not sufficient for narrow or small polygons which were not covered by these points but were still covering parts of the label candidate. Now, the area of the intersection between the obstacle polygon and the label candidate is used to calculate the obstacle cost.
1 parent fe3e07e commit f9fa979

File tree

8 files changed

+388
-24
lines changed

8 files changed

+388
-24
lines changed

src/core/pal/costcalculator.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ namespace pal
6161
{
6262
case PolygonInterior:
6363
// n ranges from 0 -> 12
64-
n = lp->getNumPointsInPolygon( obstacle );
64+
n = lp->polygonIntersectionCost( obstacle );
6565
break;
6666
case PolygonBoundary:
6767
// penalty may need tweaking, given that interior mode ranges up to 12

src/core/pal/labelposition.cpp

+37-21
Original file line numberDiff line numberDiff line change
@@ -574,36 +574,52 @@ namespace pal
574574
return false;
575575
}
576576

577-
int LabelPosition::getNumPointsInPolygon( PointSet *polygon ) const
577+
int LabelPosition::polygonIntersectionCost( PointSet *polygon ) const
578578
{
579-
int a, k, count = 0;
580-
double px, py;
579+
if ( !mGeos )
580+
createGeosGeom();
581+
582+
if ( !polygon->mGeos )
583+
polygon->createGeosGeom();
581584

582-
// check each corner
583-
for ( k = 0; k < 4; k++ )
585+
GEOSContextHandle_t geosctxt = geosContext();
586+
587+
int cost = 0;
588+
//check the label center. if covered by polygon, initial cost of 4
589+
if ( polygon->containsPoint(( x[0] + x[2] ) / 2.0, ( y[0] + y[2] ) / 2.0 ) )
590+
cost += 4;
591+
592+
try
584593
{
585-
px = x[k];
586-
py = y[k];
594+
//calculate proportion of label candidate which is covered by polygon
595+
GEOSGeometry* intersectionGeom = GEOSIntersection_r( geosctxt, mGeos, polygon->mGeos );
596+
if ( !intersectionGeom )
597+
return cost;
587598

588-
for ( a = 0; a < 2; a++ ) // and each middle of segment
599+
double positionArea = 0;
600+
if ( GEOSArea_r( geosctxt, mGeos, &positionArea ) != 1 )
589601
{
590-
if ( polygon->containsPoint( px, py ) )
591-
count++;
592-
px = ( x[k] + x[( k+1 ) %4] ) / 2.0;
593-
py = ( y[k] + y[( k+1 ) %4] ) / 2.0;
602+
GEOSGeom_destroy_r( geosctxt, intersectionGeom );
603+
return cost;
594604
}
595-
}
596-
597-
px = ( x[0] + x[2] ) / 2.0;
598-
py = ( y[0] + y[2] ) / 2.0;
599605

600-
// and the label center
601-
if ( polygon->containsPoint( px, py ) )
602-
count += 4; // virtually 4 points
606+
double intersectionArea = 0;
607+
if ( GEOSArea_r( geosctxt, intersectionGeom, &intersectionArea ) != 1 )
608+
{
609+
intersectionArea = 0;
610+
}
603611

604-
// TODO: count with nextFeature
612+
GEOSGeom_destroy_r( geosctxt, intersectionGeom );
605613

606-
return count;
614+
double portionCovered = intersectionArea / positionArea;
615+
cost += ceil( portionCovered * 8.0 ); //cost of 8 if totally covered
616+
return cost;
617+
}
618+
catch ( GEOSException &e )
619+
{
620+
QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
621+
return cost;
622+
}
607623
}
608624

609625
} // end namespace

src/core/pal/labelposition.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,10 @@ namespace pal
133133
/** Returns true if this label crosses the boundary of the specified polygon */
134134
bool crossesBoundary( PointSet* polygon ) const;
135135

136-
/** Returns number of intersections with polygon (testing border and center) */
137-
int getNumPointsInPolygon( PointSet* polygon ) const;
136+
/** Returns cost of position intersection with polygon (testing area of intersection and center).
137+
* Cost ranges between 0 and 12, with extra cost if center of label position is covered.
138+
*/
139+
int polygonIntersectionCost( PointSet* polygon ) const;
138140

139141
/** Shift the label by specified offset */
140142
void offsetPosition( double xOffset, double yOffset );

tests/src/python/test_qgspallabeling_placement.py

+10
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ def test_point_placement_around_obstacle(self):
123123
self.removeMapLayer(self.layer)
124124
self.layer = None
125125

126+
def test_point_placement_narrow_polygon_obstacle(self):
127+
# Default point label placement with narrow polygon obstacle
128+
self.layer = TestQgsPalLabeling.loadFeatureLayer('point')
129+
polyLayer = TestQgsPalLabeling.loadFeatureLayer('narrow_polygon')
130+
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
131+
self.checkTest()
132+
self.removeMapLayer(self.layer)
133+
self.removeMapLayer(polyLayer)
134+
self.layer = None
135+
126136
if __name__ == '__main__':
127137
# NOTE: unless PAL_SUITE env var is set all test class methods will be run
128138
# SEE: test_qgspallabeling_tests.suiteTests() to define suite

0 commit comments

Comments
 (0)