|
From: <jum...@li...> - 2015-12-27 16:02:34
|
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);
}
|