| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
package org.locationtech.jts.densify; |
| 13 |
|
| 14 |
import org.locationtech.jts.geom.Coordinate; |
| 15 |
import org.locationtech.jts.geom.CoordinateList; |
| 16 |
import org.locationtech.jts.geom.CoordinateSequence; |
| 17 |
import org.locationtech.jts.geom.Geometry; |
| 18 |
import org.locationtech.jts.geom.LineSegment; |
| 19 |
import org.locationtech.jts.geom.LineString; |
| 20 |
import org.locationtech.jts.geom.MultiPolygon; |
| 21 |
import org.locationtech.jts.geom.Polygon; |
| 22 |
import org.locationtech.jts.geom.PrecisionModel; |
| 23 |
import org.locationtech.jts.geom.util.GeometryTransformer; |
| 24 |
|
| 25 |
/** |
| 26 |
* Densifies a {@link Geometry} by inserting extra vertices along the line segments |
| 27 |
* contained in the geometry. |
| 28 |
* All segments in the created densified geometry will be no longer than |
| 29 |
* than the given distance tolerance. |
| 30 |
* Densified polygonal geometries are guaranteed to be topologically correct. |
| 31 |
* The coordinates created during densification respect the input geometry's |
| 32 |
* {@link PrecisionModel}. |
| 33 |
* <p> |
| 34 |
* <b>Note:</b> At some future point this class will |
| 35 |
* offer a variety of densification strategies. |
| 36 |
* |
| 37 |
* @author Martin Davis |
| 38 |
*/ |
| 39 |
public class Densifier { |
| 40 |
/** |
| 41 |
* Densifies a geometry using a given distance tolerance, |
| 42 |
* and respecting the input geometry's {@link PrecisionModel}. |
| 43 |
* |
| 44 |
* @param geom the geometry to densify |
| 45 |
* @param distanceTolerance the distance tolerance to densify |
| 46 |
* @return the densified geometry |
| 47 |
*/ |
| 48 |
public static Geometry densify(Geometry geom, double distanceTolerance) { |
| 49 |
Densifier densifier = new Densifier(geom); |
| 50 |
densifier.setDistanceTolerance(distanceTolerance); |
| 51 |
return densifier.getResultGeometry(); |
| 52 |
} |
| 53 |
|
| 54 |
/** |
| 55 |
* Densifies a coordinate sequence. |
| 56 |
* |
| 57 |
* @param pts |
| 58 |
* @param distanceTolerance |
| 59 |
* @return the densified coordinate sequence |
| 60 |
*/ |
| 61 |
private static Coordinate[] densifyPoints(Coordinate[] pts, |
| 62 |
double distanceTolerance, PrecisionModel precModel) { |
| 63 |
LineSegment seg = new LineSegment(); |
| 64 |
CoordinateList coordList = new CoordinateList(); |
| 65 |
for (int i = 0; i < pts.length - 1; i++) { |
| 66 |
seg.p0 = pts[i]; |
| 67 |
seg.p1 = pts[i + 1]; |
| 68 |
coordList.add(seg.p0, false); |
| 69 |
double len = seg.getLength(); |
| 70 |
int densifiedSegCount = (int) (len / distanceTolerance) + 1; |
| 71 |
if (densifiedSegCount > 1) { |
| 72 |
double densifiedSegLen = len / densifiedSegCount; |
| 73 |
for (int j = 1; j < densifiedSegCount; j++) { |
| 74 |
double segFract = (j * densifiedSegLen) / len; |
| 75 |
Coordinate p = seg.pointAlong(segFract); |
| 76 |
precModel.makePrecise(p); |
| 77 |
coordList.add(p, false); |
| 78 |
} |
| 79 |
} |
| 80 |
} |
| 81 |
coordList.add(pts[pts.length - 1], false); |
| 82 |
return coordList.toCoordinateArray(); |
| 83 |
} |
| 84 |
|
| 85 |
private Geometry inputGeom; |
| 86 |
|
| 87 |
private double distanceTolerance; |
| 88 |
|
| 89 |
/** |
| 90 |
* Creates a new densifier instance. |
| 91 |
* |
| 92 |
* @param inputGeom |
| 93 |
*/ |
| 94 |
public Densifier(Geometry inputGeom) { |
| 95 |
this.inputGeom = inputGeom; |
| 96 |
} |
| 97 |
|
| 98 |
/** |
| 99 |
* Sets the distance tolerance for the densification. All line segments |
| 100 |
* in the densified geometry will be no longer than the distance tolerance. |
| 101 |
* simplified geometry will be within this distance of the original geometry. |
| 102 |
* The distance tolerance must be positive. |
| 103 |
* |
| 104 |
* @param distanceTolerance |
| 105 |
* the densification tolerance to use |
| 106 |
*/ |
| 107 |
public void setDistanceTolerance(double distanceTolerance) { |
| 108 |
if (distanceTolerance <= 0.0) |
| 109 |
throw new IllegalArgumentException("Tolerance must be positive"); |
| 110 |
this.distanceTolerance = distanceTolerance; |
| 111 |
} |
| 112 |
|
| 113 |
/** |
| 114 |
* Gets the densified geometry. |
| 115 |
* |
| 116 |
* @return the densified geometry |
| 117 |
*/ |
| 118 |
public Geometry getResultGeometry() { |
| 119 |
return (new DensifyTransformer(distanceTolerance)).transform(inputGeom); |
| 120 |
} |
| 121 |
|
| 122 |
static class DensifyTransformer extends GeometryTransformer { |
| 123 |
double distanceTolerance; |
| 124 |
|
| 125 |
DensifyTransformer(double distanceTolerance) { |
| 126 |
this.distanceTolerance = distanceTolerance; |
| 127 |
} |
| 128 |
|
| 129 |
protected CoordinateSequence transformCoordinates( |
| 130 |
CoordinateSequence coords, Geometry parent) { |
| 131 |
Coordinate[] inputPts = coords.toCoordinateArray(); |
| 132 |
Coordinate[] newPts = Densifier |
| 133 |
.densifyPoints(inputPts, distanceTolerance, parent.getPrecisionModel()); |
| 134 |
|
| 135 |
if (parent instanceof LineString && newPts.length == 1) { |
| 136 |
newPts = new Coordinate[0]; |
| 137 |
} |
| 138 |
return factory.getCoordinateSequenceFactory().create(newPts); |
| 139 |
} |
| 140 |
|
| 141 |
protected Geometry transformPolygon(Polygon geom, Geometry parent) { |
| 142 |
Geometry roughGeom = super.transformPolygon(geom, parent); |
| 143 |
|
| 144 |
if (parent instanceof MultiPolygon) { |
| 145 |
return roughGeom; |
| 146 |
} |
| 147 |
return createValidArea(roughGeom); |
| 148 |
} |
| 149 |
|
| 150 |
protected Geometry transformMultiPolygon(MultiPolygon geom, Geometry parent) { |
| 151 |
Geometry roughGeom = super.transformMultiPolygon(geom, parent); |
| 152 |
return createValidArea(roughGeom); |
| 153 |
} |
| 154 |
|
| 155 |
/** |
| 156 |
* Creates a valid area geometry from one that possibly has bad topology |
| 157 |
* (i.e. self-intersections). Since buffer can handle invalid topology, but |
| 158 |
* always returns valid geometry, constructing a 0-width buffer "corrects" |
| 159 |
* the topology. Note this only works for area geometries, since buffer |
| 160 |
* always returns areas. This also may return empty geometries, if the input |
| 161 |
* has no actual area. |
| 162 |
* |
| 163 |
* @param roughAreaGeom |
| 164 |
* an area geometry possibly containing self-intersections |
| 165 |
* @return a valid area geometry |
| 166 |
*/ |
| 167 |
private Geometry createValidArea(Geometry roughAreaGeom) { |
| 168 |
return roughAreaGeom.buffer(0.0); |
| 169 |
} |
| 170 |
} |
| 171 |
|
| 172 |
} |
| 173 |
|