I'd like to use JTS to calculate a simplified (fewer points) convex hull from a collection of points including inner and perimeter points, where the hull is allowed to be a bit larger in area in order to simplify, but must contain all of the points. I think that PolygonHullSimplifier.hullByAreaDelta is designed for that.
Code snippet here (same data as plots but in different units.) https://gist.github.com/joshgold22/11d39c85456271109b2421dd124be241
I tried creating a MultiPoint, calling convexHull() on it, and passing that to PolygonHullSimplifier.hullByAreaDelta(hull, true /*outer*/, 0.1 /*areaDeltaRatio*/)
There were two surprises for me. First, convexHull didn't seem to return a convexHull, though it did remove many points. Showing the original set and then the result of convexHull()
:
Second, no matter how I play with the areaDeltaRatio
, the result of hullByAreaDelta
was always identical to the basic (intermediate) convex hull Polygon. (It did differ when I tried an inner hull.)
I'll add one more picture, of a DouglasPeuckerSimplifier simplification, which does work, but which I understand would not be guaranteed to contain all of the original points.
Can you help clarify what I'm misunderstanding and show me how to do this?
What’s likely happening?
convexHull() on MultiPoint might return a GeometryCollection or a polygon with redundant points. JTS convexHull() should return the minimal convex polygon containing all points. But if your points are aligned or clustered, it might return a LineString or Point or a polygon with unnecessary collinear points, which can look “not strictly convex” or overly detailed. Also, when printing or plotting, the polygon might look strange if you don’t interpret the geometry correctly.
PolygonHullSimplifier.hullByAreaDelta only works on polygons, not on convex hulls of MultiPoints directly. hullByAreaDelta expects a polygon that can be simplified. But the polygon from convexHull() is already minimal in convex terms; it's hard to simplify a convex polygon further without breaking convexity. The method likely won't simplify a convex polygon further, so your area delta parameter has no effect.
You want a simplified convex hull with fewer points, allowing it to be slightly larger (outer hull). The convex hull itself is the minimum convex polygon enclosing points — it can’t be smaller, but it can be simplified to fewer vertices if you relax convexity or allow an outer approximation. PolygonHullSimplifier is not a convex hull simplifier but a polygon simplifier controlling area increase.
What are your options?
Use a convex hull algorithm, then run a simplification like Douglas-Peucker, but this breaks convexity and may exclude points. You saw this and noted it breaks the “contain all points” rule.
Use an outer hull that is not strictly convex, allowing simplification but still containing all points. PolygonHullSimplifier.hullByAreaDelta can be used here, but you need to start from a polygon with some interior points and more complex shape (not just convex hull). For example, you might build an initial polygon around the points (like an alpha shape or concave hull) and then simplify outward.
Use a custom approach: build a convex hull and then reduce points greedily while ensuring convexity and containment. This is non-trivial and not directly supported by JTS.
What might fix your code approach?
Make sure the input to hullByAreaDelta is a polygon with multiple points and some detail, not just a convex hull polygon
If you want a simplified convex polygon enclosing all points, try this:
Compute the convex hull polygon from your MultiPoint.
Then try a polygon simplifier that preserves convexity or implement a heuristic:
For example, attempt to remove vertices one by one, checking if the polygon remains convex and contains all points.
Unfortunately, JTS does not have a built-in simplified convex hull with tolerance.
Quick test you can do in JTS
MultiPoint multiPoint = ... // your points
Geometry convexHull = multiPoint.convexHull();
// convexHull is a Polygon
System.out.println("Hull points: " + convexHull.getNumPoints());
// Try simplifying with DouglasPeucker - might break containment
Geometry simplified = DouglasPeuckerSimplifier.simplify(convexHull, tolerance);
// Try hullByAreaDelta on the convex hull polygon (may not simplify)
Geometry simplifiedByAreaDelta = PolygonHullSimplifier.hullByAreaDelta(convexHull, true, 0.1);
// Print/plot and compare areas, number of points
System.out.println("Original area: " + convexHull.getArea());
System.out.println("Simplified area: " + simplified.getArea());
System.out.println("Simplified by area delta area: " + simplifiedByAreaDelta.getArea());
You will likely see no difference between convexHull and hullByAreaDelta output because convex polygons are minimal for convexity.
Summary
convexHull() returns the minimal convex polygon; it won’t simplify further or reduce points unless some are exactly collinear or duplicated.
PolygonHullSimplifier.hullByAreaDelta simplifies polygons allowing area increase but won’t simplify the minimal convex polygon further.
If you want a simplified hull that contains all points but is simpler than the convex hull, you need a polygon more complex than the convex hull (e.g., alpha shape), then simplify outward.
Alternatively, create your own routine to reduce convex hull points under convexity and containment constraints.