Skip to content

Commit

Permalink
[pal] Improved test for candidate against polygon obstacles
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nyalldawson committed Aug 31, 2015
1 parent fe3e07e commit f9fa979
Show file tree
Hide file tree
Showing 8 changed files with 388 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/core/pal/costcalculator.cpp
Expand Up @@ -61,7 +61,7 @@ namespace pal
{
case PolygonInterior:
// n ranges from 0 -> 12
n = lp->getNumPointsInPolygon( obstacle );
n = lp->polygonIntersectionCost( obstacle );
break;
case PolygonBoundary:
// penalty may need tweaking, given that interior mode ranges up to 12
Expand Down
58 changes: 37 additions & 21 deletions src/core/pal/labelposition.cpp
Expand Up @@ -574,36 +574,52 @@ namespace pal
return false;
}

int LabelPosition::getNumPointsInPolygon( PointSet *polygon ) const
int LabelPosition::polygonIntersectionCost( PointSet *polygon ) const
{
int a, k, count = 0;
double px, py;
if ( !mGeos )
createGeosGeom();

if ( !polygon->mGeos )
polygon->createGeosGeom();

// check each corner
for ( k = 0; k < 4; k++ )
GEOSContextHandle_t geosctxt = geosContext();

int cost = 0;
//check the label center. if covered by polygon, initial cost of 4
if ( polygon->containsPoint(( x[0] + x[2] ) / 2.0, ( y[0] + y[2] ) / 2.0 ) )
cost += 4;

try
{
px = x[k];
py = y[k];
//calculate proportion of label candidate which is covered by polygon
GEOSGeometry* intersectionGeom = GEOSIntersection_r( geosctxt, mGeos, polygon->mGeos );
if ( !intersectionGeom )
return cost;

for ( a = 0; a < 2; a++ ) // and each middle of segment
double positionArea = 0;
if ( GEOSArea_r( geosctxt, mGeos, &positionArea ) != 1 )
{
if ( polygon->containsPoint( px, py ) )
count++;
px = ( x[k] + x[( k+1 ) %4] ) / 2.0;
py = ( y[k] + y[( k+1 ) %4] ) / 2.0;
GEOSGeom_destroy_r( geosctxt, intersectionGeom );
return cost;
}
}

px = ( x[0] + x[2] ) / 2.0;
py = ( y[0] + y[2] ) / 2.0;

// and the label center
if ( polygon->containsPoint( px, py ) )
count += 4; // virtually 4 points
double intersectionArea = 0;
if ( GEOSArea_r( geosctxt, intersectionGeom, &intersectionArea ) != 1 )
{
intersectionArea = 0;
}

// TODO: count with nextFeature
GEOSGeom_destroy_r( geosctxt, intersectionGeom );

return count;
double portionCovered = intersectionArea / positionArea;
cost += ceil( portionCovered * 8.0 ); //cost of 8 if totally covered
return cost;
}
catch ( GEOSException &e )
{
QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
return cost;
}
}

} // end namespace
Expand Down
6 changes: 4 additions & 2 deletions src/core/pal/labelposition.h
Expand Up @@ -133,8 +133,10 @@ namespace pal
/** Returns true if this label crosses the boundary of the specified polygon */
bool crossesBoundary( PointSet* polygon ) const;

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

/** Shift the label by specified offset */
void offsetPosition( double xOffset, double yOffset );
Expand Down
10 changes: 10 additions & 0 deletions tests/src/python/test_qgspallabeling_placement.py
Expand Up @@ -123,6 +123,16 @@ def test_point_placement_around_obstacle(self):
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

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
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit f9fa979

Please sign in to comment.