Revision: 4696 http://sourceforge.net/p/jump-pilot/code/4696 Author: michaudm Date: 2015-12-27 16:02:32 +0000 (Sun, 27 Dec 2015) Log Message: ----------- Improvements to MakeValidOp (work in progress)
Modified Paths: -------------- core/trunk/src/com/vividsolutions/jump/geom/MakeValidOp.java core/trunk/src/org/openjump/core/ui/plugin/tools/MakeValidPlugIn.java Modified: core/trunk/src/com/vividsolutions/jump/geom/MakeValidOp.java =================================================================== --- core/trunk/src/com/vividsolutions/jump/geom/MakeValidOp.java 2015-12-27 14:20:56 UTC (rev 4695) +++ core/trunk/src/com/vividsolutions/jump/geom/MakeValidOp.java 2015-12-27 16:02:32 UTC (rev 4696) @@ -8,24 +8,46 @@ import com.vividsolutions.jts.noding.IntersectionAdder; import com.vividsolutions.jts.noding.MCIndexNoder; import com.vividsolutions.jts.noding.NodedSegmentString; +import com.vividsolutions.jts.operation.linemerge.LineMerger; import com.vividsolutions.jts.operation.polygonize.Polygonizer; +import visad.data.netcdf.in.Merger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; +import java.util.*; + import static com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory.*; /** * Operator to make a geometry valid. + * <br/> + * Making a geometry valid will remove duplicate points although duplicate points + * do not make a geometry invalid. */ public class MakeValidOp { private static final Coordinate[] EMPTY_COORD_ARRAY = new Coordinate[0]; private static final LinearRing[] EMPTY_RING_ARRAY = new LinearRing[0]; + // If preserveGeomDim is true, geometry components with a dimension lesser than + // input geometry dimension are ignored (except if input geometry is an heterogeneous + // GeometryCollection) + private boolean preserveGeomDim = false; + + // If preserveCoordDim is true, MakeValidOp preserves third and fourth ordinates. + // If preserveCoordDim is false, third dimension is preserved but not fourth one. + private boolean preserveCoordDim = false; + public MakeValidOp() {} + public MakeValidOp preserveGeomDim() { + this.preserveGeomDim = true; + return this; + } + + public MakeValidOp preserveCoordDim() { + this.preserveCoordDim = true; + return this; + } + /** * Decompose a geometry recursively into simple components. * @param geometry input geometry @@ -40,9 +62,6 @@ } - public static Geometry makeValid(Geometry geometry) { - return makeValid(geometry, true); - } /** * Repair an invalid geometry. @@ -58,10 +77,9 @@ * their M value after the noding or the polygonization phase. * TODO add an option to return a geometry preserving input dimension * @param geometry input geometry - * @param removeDuplicate * @return */ - public static Geometry makeValid(Geometry geometry, boolean removeDuplicate) { + public Geometry makeValid(Geometry geometry) { List<Geometry> list = new ArrayList<Geometry>(geometry.getNumGeometries()); decompose(geometry, list); List<Geometry> list2 = new ArrayList<Geometry>(); @@ -71,19 +89,22 @@ if (!p.isEmpty()) list2.add(p); } else if (component instanceof LineString) { - Geometry geom = makeLineStringValid((LineString) component, removeDuplicate); + Geometry geom = makeLineStringValid((LineString) component); for (int i = 0 ; i < geom.getNumGeometries() ; i++) { if (!geom.getGeometryN(i).isEmpty()) list2.add(geom.getGeometryN(i)); } } else if (component instanceof Polygon) { - Geometry geom = makePolygonValid((Polygon) component, removeDuplicate); + Geometry geom = makePolygonValid((Polygon) component); for (int i = 0 ; i < geom.getNumGeometries() ; i++) { if (!geom.getGeometryN(i).isEmpty()) list2.add(geom.getGeometryN(i)); } } else assert false : "Should never reach here"; } + if (preserveGeomDim && !geometry.getClass().getSimpleName().equals("GeometryCollection")) { + list2 = removeLowerDimension(list2, geometry.getDimension()); + } if (list2.isEmpty()) { GeometryFactory factory = geometry.getFactory(); if (geometry instanceof Point) return factory.createPoint((Coordinate)null); @@ -100,9 +121,23 @@ } } + // Remove geometries with a dimension less than dimension parameter + private List<Geometry> removeLowerDimension(List<Geometry> geometries, int dimension) { + List<Geometry> list = new ArrayList<Geometry>(); + for (Geometry geom : geometries) { + if (geom.getDimension() == dimension) { + list.add(geom); + } + } + return list; + } + // If X or Y is null, return an empty Point - private static Point makePointValid(Point point) { + private Point makePointValid(Point point) { CoordinateSequence sequence = point.getCoordinateSequence(); + // The case where sequence contains more than one point is not + // processed (it will return an empty point or the input point + // unchanged) if (Double.isNaN(sequence.getOrdinate(0, 0)) || Double.isNaN(sequence.getOrdinate(0, 1))) { return point.getFactory().createPoint(DOUBLE_FACTORY.create(0, sequence.getDimension())); } else { @@ -169,14 +204,11 @@ * <ul> * <li>an empty LineString if input CoordinateSequence has no valid point</li> * <li>a Point if input CoordinateSequence has a single valid Point</li> - * <li>a LineString retaining or not duplicate coordinates depending on - * removeDuplicate parameter</li> * </ul> * @param lineString - * @param removeDuplicate * @return */ - private static Geometry makeLineStringValid(LineString lineString, boolean removeDuplicate) { + private Geometry makeLineStringValid(LineString lineString) { CoordinateSequence sequence = lineString.getCoordinateSequence(); CoordinateSequence sequenceWithoutDuplicates = makeSequenceValid(sequence, true, false); if (sequenceWithoutDuplicates.size() == 0) { @@ -185,12 +217,9 @@ } else if (sequenceWithoutDuplicates.size() == 1) { // a single valid point -> returns a Point return lineString.getFactory().createPoint(sequenceWithoutDuplicates); - } else if (removeDuplicate) { + } else { // we use already calculated sequenceWithoutDuplicates return lineString.getFactory().createLineString(sequenceWithoutDuplicates); - } else { - // we need to recompute a sequence retaining duplicates but not coordinates with NaN X or Y - return lineString.getFactory().createLineString(makeSequenceValid(sequence, false, false)); } } @@ -205,15 +234,14 @@ * <li>a GeometryCollection if input has degenerate parts (ex. degenerate holes)</li> * </ul> * @param polygon - * @param removeDuplicate * @return */ - private static Geometry makePolygonValid(Polygon polygon, boolean removeDuplicate) { + private Geometry makePolygonValid(Polygon polygon) { //This first step analyze linear components and create degenerate geometries //of dimension 0 or 1 if they do not form valid LinearRings //If degenerate geometries are found, it may produce a GeometryCollection with //heterogeneous dimension - Geometry geom = makePolygonComponentsValid(polygon, removeDuplicate); + Geometry geom = makePolygonComponentsValid(polygon); List<Geometry> list = new ArrayList<Geometry>(); for (int i = 0 ; i < geom.getNumGeometries() ; i++) { Geometry component = geom.getGeometryN(i); @@ -241,19 +269,18 @@ * GeometryCollection of heterogeneous dimension. * </p> * @param polygon - * @param removeDuplicate * @return */ - private static Geometry makePolygonComponentsValid(Polygon polygon, boolean removeDuplicate) { + private Geometry makePolygonComponentsValid(Polygon polygon) { GeometryFactory factory = polygon.getFactory(); CoordinateSequence outerRingSeq = makeSequenceValid(polygon.getExteriorRing().getCoordinateSequence(), true, true); // The validated sequence of the outerRing does not form a valid LinearRing // -> build valid 0-dim or 1-dim geometry from all the rings if (outerRingSeq.size() == 0 || outerRingSeq.size() < 4) { List<Geometry> list = new ArrayList<Geometry>(); - if (outerRingSeq.size() > 0) list.add(makeLineStringValid(polygon.getExteriorRing(), removeDuplicate)); + if (outerRingSeq.size() > 0) list.add(makeLineStringValid(polygon.getExteriorRing())); for (int i = 0 ; i < polygon.getNumInteriorRing() ; i++) { - Geometry g = makeLineStringValid(polygon.getInteriorRingN(i), removeDuplicate); + Geometry g = makeLineStringValid(polygon.getInteriorRingN(i)); if (!g.isEmpty()) list.add(g); } if (list.isEmpty()) return factory.createPolygon(outerRingSeq); @@ -290,7 +317,7 @@ * @param geometry the geometry from which polygonal components wil be extracted * @param list the list into which polygonal components will be added. */ - protected static void extractPolygons(Geometry geometry, List<Polygon> list) { + protected void extractPolygons(Geometry geometry, List<Polygon> list) { for (int i = 0 ; i < geometry.getNumGeometries() ; i++) { Geometry g = geometry.getGeometryN(i); if (g == null) continue; // null components are discarded @@ -323,7 +350,7 @@ * <li>remove Geometries computed from noded interior boundaries</li> * </ul> */ - protected static Geometry nodePolygon(Polygon polygon) { + protected Geometry nodePolygon(Polygon polygon) { LinearRing exteriorRing = (LinearRing)polygon.getExteriorRing(); Geometry geom = getMultiPolygonFromLinearRing(exteriorRing); for (int i = 0 ; i < polygon.getNumInteriorRing() ; i++) { @@ -341,18 +368,21 @@ * </ul> * This is used to repair auto-intersecting Polygons */ - protected static MultiPolygon getMultiPolygonFromLinearRing(LinearRing ring) { + protected Geometry getMultiPolygonFromLinearRing(LinearRing ring) { if (ring.isSimple()) { return ring.getFactory().createMultiPolygon(new Polygon[]{ ring.getFactory().createPolygon(ring, EMPTY_RING_ARRAY) }); } else { - // TODO according to MD, we need to remove duplicate linestring before polygonizing Polygonizer polygonizer = new Polygonizer(); polygonizer.add(nodeLineString(ring.getCoordinates(), ring.getFactory())); - Collection<Polygon> polys = polygonizer.getPolygons(); - return ring.getFactory().createMultiPolygon(polys.toArray(new Polygon[polys.size()])); + Collection<Geometry> geoms = new ArrayList<Geometry>(); + geoms.addAll(polygonizer.getPolygons()); + geoms.addAll(polygonizer.getCutEdges()); + geoms.addAll(polygonizer.getDangles()); + geoms.addAll(polygonizer.getInvalidRingLines()); + return ring.getFactory().buildGeometry(geoms); } } @@ -366,7 +396,7 @@ * @param gf geometryFactory to use * @return a list of noded LineStrings */ - protected static List<LineString> nodeLineString(Coordinate[] coords, GeometryFactory gf) { + protected Set<LineString> nodeLineString(Coordinate[] coords, GeometryFactory gf) { MCIndexNoder noder = new MCIndexNoder(); noder.setSegmentIntersector(new IntersectionAdder(new RobustLineIntersector())); List<NodedSegmentString> list = new ArrayList<NodedSegmentString>(); @@ -378,31 +408,50 @@ ((NodedSegmentString)segmentString).getCoordinates() )); } - return lineStringList; + + // WARNING : merger loose original linestrings + // It is useful for LinearRings but should not be used for (Multi)LineStrings + LineMerger merger = new LineMerger(); + merger.add(lineStringList); + lineStringList = (List<LineString>)merger.getMergedLineStrings(); + + // Remove duplicate linestrings preserving main orientation + Set<LineString> lineStringSet = new HashSet<LineString>(); + for (LineString line : lineStringList) { + if (lineStringSet.contains(line) || lineStringSet.contains(line.reverse())) { + continue; + } else { + lineStringSet.add(line); + } + } + return lineStringSet; } public static void main(String[] args) throws ParseException { GeometryFactory factory = new GeometryFactory(); + MakeValidOp op = new MakeValidOp(); + MakeValidOp opClean = new MakeValidOp().preserveGeomDim(); + Geometry input, result; // check makePointValid Point p1 = factory.createPoint(new Coordinate(0,0)); - Point p2 = makePointValid(p1); + Point p2 = op.makePointValid(p1); assert p1.equals(p2); p1 = factory.createPoint(new Coordinate(Double.NaN,0)); - p2 = makePointValid(p1); + p2 = op.makePointValid(p1); assert !p1.isEmpty(); assert p2.isEmpty(); p1 = factory.createPoint(new Coordinate(0, Double.NaN)); - p2 = makePointValid(p1); + p2 = op.makePointValid(p1); assert !p1.isEmpty(); assert p2.isEmpty(); p1 = factory.createPoint(DOUBLE_FACTORY.create(new double[]{0,1,2,3}, 4)); - p2 = makePointValid(p1); + p2 = op.makePointValid(p1); assert p1.getCoordinateSequence().getOrdinate(0,3) == p2.getCoordinateSequence().getOrdinate(0,3); // check makeSequenceValid @@ -446,19 +495,36 @@ assert cs2.getOrdinate(3,3) == 3 : cs2.getOrdinate(3,3); WKTReader reader = new WKTReader(); - Geometry geometry = reader.read("LINESTRING(0 0, 10 0, 20 0, 20 0, 30 0)"); - assert geometry.getNumPoints() == 5; - List<LineString> list = nodeLineString(geometry.getCoordinates(), geometry.getFactory()); - assert list.size() == 1; - assert list.get(0).getCoordinates().length == 5; + // invalid polygon (single linearRing drawing 2 triangles joined by a line) + input = reader.read("POLYGON (( 322 354, 322 348, 325 351, 328 351, 331 348, 331 354, 328 351, 325 351, 322 354 ))"); + result = op.makeValid(input); + assert result.getNumGeometries() == 3; + result = opClean.makeValid(input); + assert result.getNumGeometries() == 2; - geometry = reader.read("LINESTRING(0 0, 20 0, 20 20, 20 20, 10 -10)"); - assert geometry.getNumPoints() == 5; - list = nodeLineString(geometry.getCoordinates(), geometry.getFactory()); - assert list.size() == 5; // creates a degenerate segment from 20 20 to 20 20 + reader = new WKTReader(); + // invalid polygon (single linearRing drawing 2 triangles joined by a line, first triangle has duplicated segments) + input = reader.read("POLYGON (( 322 354, 322 348, 322 354, 322 348, 325 351, 328 351, 331 348, 331 354, 328 351, 325 351, 322 354 ))"); + result = op.makeValid(input); + assert result.getNumGeometries() == 3; + result = opClean.makeValid(input); + assert result.getNumGeometries() == 2; + + reader = new WKTReader(); + input = reader.read("LINESTRING(0 0, 10 0, 20 0, 20 0, 30 0)"); + assert input.getNumPoints() == 5; + Set<LineString> set = op.nodeLineString(input.getCoordinates(), input.getFactory()); + assert set.size() == 1; + assert set.iterator().next().getCoordinates().length == 4; // removed duplicate coordinate + + input = reader.read("LINESTRING(0 0, 20 0, 20 20, 20 20, 10 -10)"); + assert input.getNumPoints() == 5; + set = op.nodeLineString(input.getCoordinates(), input.getFactory()); + assert set.size() == 3; // node + merge -> 3 line strings + Polygonizer polygonizer = new Polygonizer(); - polygonizer.add(list); + polygonizer.add(set); Collection<Polygon> polys = polygonizer.getPolygons(); System.out.println(polys); Modified: core/trunk/src/org/openjump/core/ui/plugin/tools/MakeValidPlugIn.java =================================================================== --- core/trunk/src/org/openjump/core/ui/plugin/tools/MakeValidPlugIn.java 2015-12-27 14:20:56 UTC (rev 4695) +++ core/trunk/src/org/openjump/core/ui/plugin/tools/MakeValidPlugIn.java 2015-12-27 16:02:32 UTC (rev 4696) @@ -108,7 +108,7 @@ MakeValidOp makeValidOp = new MakeValidOp(); for (Object o : result1.getFeatures()) { Feature feature = (Feature)o; - Geometry validGeom = MakeValidOp.makeValid(feature.getGeometry()); + Geometry validGeom = new MakeValidOp().makeValid(feature.getGeometry()); if (removeDegenerateParts) validGeom = removeDegenerateParts(feature.getGeometry(), validGeom); feature.setGeometry(validGeom); } ------------------------------------------------------------------------------ _______________________________________________ Jump-pilot-devel mailing list Jump-pilot-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel