Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to convert a self intersecting polygon to a multipolygon in JTS?

Tags:

java

geometry

jts

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

Invalid polygon

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:

Part of the polygon was 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?

Desired output

This seems like something that should be built in to the API (maybe this behaviour is a bug) - have I missed something?

Thank you.

like image 661
tofarr Avatar asked Jul 17 '15 10:07

tofarr


People also ask

Can a polygon be self-intersecting?

A polygon can be self-intersecting, meaning edges cross other edges. (The points of intersection are not vertices.)

What is a polygon that intersects itself?

Star polygon: a polygon which self-intersects in a regular way.

What is a self intersection in Arcgis?

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.


1 Answers

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;     } } 
like image 195
tofarr Avatar answered Sep 23 '22 06:09

tofarr