Take the invalid polygon POLYGON((0 100, 100 100, 0 0, 100 0, 0 100))
- an egg timer shape with an undeclared point of intersection
Many instructions say that JTS can create a valid version of this using the buffer
method:
Geometry input = new WKTReader().read("POLYGON((0 100, 100 100, 0 0, 100 0, 0 100))"); Geometry output = geom.buffer(0); return output;
However, this produces the output POLYGON ((0 100, 100 100, 50 50, 0 100))
where part of the polygon is lost:
Is there a way to get JTS to validate polygons such that it will produce the output MULTIPOLYGON(((0 100, 100 100, 50 50, 0 100)), ((0 0, 100 0, 50 50, 0 0)))
for the input given?
This seems like something that should be built in to the API (maybe this behaviour is a bug) - have I missed something?
Thank you.
A polygon can be self-intersecting, meaning edges cross other edges. (The points of intersection are not vertices.)
Star polygon: a polygon which self-intersects in a regular way.
If a feature intersects itself at a point and continues by crossing itself, it is considered a self-intersection. However, if the feature snaps to itself at a point and turns back without crossing itself, it is not considered a self-intersection.
JTS seems to offer the behaviour I require, though I had to do a little legwork in my own code. The validate
function I wrote breaks down a polygon/multipolygon into a collection of non self intersecting linestrings, and then uses the Polygonizer
class to build polygons from the result. I have tested it on the following (limited) set of inputs, and it seems to behave the way I require:
POLYGON((0 100, 100 100, 0 0, 100 0, 0 100)) POLYGON((0 0, 0 100, 100 100, 100 0, 0 0)) MULTIPOLYGON(((0 0, 0 100, 100 100, 100 0, 0 0)),((50 50, 50 150, 150 150, 150 50, 50 50))) POLYGON((0 0, 50 50, 100 0, 150 0, 200 50, 250 0, 0 0))
Code:
/** * Get / create a valid version of the geometry given. If the geometry is a polygon or multi polygon, self intersections / * inconsistencies are fixed. Otherwise the geometry is returned. * * @param geom * @return a geometry */ public static Geometry validate(Geometry geom){ if(geom instanceof Polygon){ if(geom.isValid()){ geom.normalize(); // validate does not pick up rings in the wrong order - this will fix that return geom; // If the polygon is valid just return it } Polygonizer polygonizer = new Polygonizer(); addPolygon((Polygon)geom, polygonizer); return toPolygonGeometry(polygonizer.getPolygons(), geom.getFactory()); }else if(geom instanceof MultiPolygon){ if(geom.isValid()){ geom.normalize(); // validate does not pick up rings in the wrong order - this will fix that return geom; // If the multipolygon is valid just return it } Polygonizer polygonizer = new Polygonizer(); for(int n = geom.getNumGeometries(); n-- > 0;){ addPolygon((Polygon)geom.getGeometryN(n), polygonizer); } return toPolygonGeometry(polygonizer.getPolygons(), geom.getFactory()); }else{ return geom; // In my case, I only care about polygon / multipolygon geometries } } /** * Add all line strings from the polygon given to the polygonizer given * * @param polygon polygon from which to extract line strings * @param polygonizer polygonizer */ static void addPolygon(Polygon polygon, Polygonizer polygonizer){ addLineString(polygon.getExteriorRing(), polygonizer); for(int n = polygon.getNumInteriorRing(); n-- > 0;){ addLineString(polygon.getInteriorRingN(n), polygonizer); } } /** * Add the linestring given to the polygonizer * * @param linestring line string * @param polygonizer polygonizer */ static void addLineString(LineString lineString, Polygonizer polygonizer){ if(lineString instanceof LinearRing){ // LinearRings are treated differently to line strings : we need a LineString NOT a LinearRing lineString = lineString.getFactory().createLineString(lineString.getCoordinateSequence()); } // unioning the linestring with the point makes any self intersections explicit. Point point = lineString.getFactory().createPoint(lineString.getCoordinateN(0)); Geometry toAdd = lineString.union(point); //Add result to polygonizer polygonizer.add(toAdd); } /** * Get a geometry from a collection of polygons. * * @param polygons collection * @param factory factory to generate MultiPolygon if required * @return null if there were no polygons, the polygon if there was only one, or a MultiPolygon containing all polygons otherwise */ static Geometry toPolygonGeometry(Collection<Polygon> polygons, GeometryFactory factory){ switch(polygons.size()){ case 0: return null; // No valid polygons! case 1: return polygons.iterator().next(); // single polygon - no need to wrap default: //polygons may still overlap! Need to sym difference them Iterator<Polygon> iter = polygons.iterator(); Geometry ret = iter.next(); while(iter.hasNext()){ ret = ret.symDifference(iter.next()); } return ret; } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With