| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
package org.locationtech.jts.geom.util; |
| 14 |
|
| 15 |
import java.util.ArrayList; |
| 16 |
import java.util.List; |
| 17 |
|
| 18 |
import org.locationtech.jts.geom.Coordinate; |
| 19 |
import org.locationtech.jts.geom.CoordinateSequence; |
| 20 |
import org.locationtech.jts.geom.Geometry; |
| 21 |
import org.locationtech.jts.geom.GeometryCollection; |
| 22 |
import org.locationtech.jts.geom.GeometryFactory; |
| 23 |
import org.locationtech.jts.geom.LineString; |
| 24 |
import org.locationtech.jts.geom.LinearRing; |
| 25 |
import org.locationtech.jts.geom.MultiLineString; |
| 26 |
import org.locationtech.jts.geom.MultiPoint; |
| 27 |
import org.locationtech.jts.geom.MultiPolygon; |
| 28 |
import org.locationtech.jts.geom.Point; |
| 29 |
import org.locationtech.jts.geom.Polygon; |
| 30 |
|
| 31 |
/** |
| 32 |
* A framework for processes which transform an input {@link Geometry} into |
| 33 |
* an output {@link Geometry}, possibly changing its structure and type(s). |
| 34 |
* This class is a framework for implementing subclasses |
| 35 |
* which perform transformations on |
| 36 |
* various different Geometry subclasses. |
| 37 |
* It provides an easy way of applying specific transformations |
| 38 |
* to given geometry types, while allowing unhandled types to be simply copied. |
| 39 |
* Also, the framework ensures that if subcomponents change type |
| 40 |
* the parent geometries types change appropriately to maintain valid structure. |
| 41 |
* Subclasses will override whichever <code>transformX</code> methods |
| 42 |
* they need to to handle particular Geometry types. |
| 43 |
* <p> |
| 44 |
* A typically usage would be a transformation class that transforms <tt>Polygons</tt> into |
| 45 |
* <tt>Polygons</tt>, <tt>LineStrings</tt> or <tt>Points</tt>, depending on the geometry of the input |
| 46 |
* (For instance, a simplification operation). |
| 47 |
* This class would likely need to override the {@link #transformMultiPolygon(MultiPolygon, Geometry)} |
| 48 |
* method to ensure that if input Polygons change type the result is a <tt>GeometryCollection</tt>, |
| 49 |
* not a <tt>MultiPolygon</tt>. |
| 50 |
* <p> |
| 51 |
* The default behaviour of this class is simply to recursively transform |
| 52 |
* each Geometry component into an identical object by deep copying down |
| 53 |
* to the level of, but not including, coordinates. |
| 54 |
* <p> |
| 55 |
* All <code>transformX</code> methods may return <code>null</code>, |
| 56 |
* to avoid creating empty or invalid geometry objects. This will be handled correctly |
| 57 |
* by the transformer. <code>transform<i>XXX</i></code> methods should always return valid |
| 58 |
* geometry - if they cannot do this they should return <code>null</code> |
| 59 |
* (for instance, it may not be possible for a transformLineString implementation |
| 60 |
* to return at least two points - in this case, it should return <code>null</code>). |
| 61 |
* The {@link #transform(Geometry)} method itself will always |
| 62 |
* return a non-null Geometry object (but this may be empty). |
| 63 |
* |
| 64 |
* @version 1.7 |
| 65 |
* |
| 66 |
* @see GeometryEditor |
| 67 |
*/ |
| 68 |
public class GeometryTransformer |
| 69 |
{ |
| 70 |
|
| 71 |
/** |
| 72 |
* Possible extensions: |
| 73 |
* getParent() method to return immediate parent e.g. of LinearRings in Polygons |
| 74 |
*/ |
| 75 |
|
| 76 |
private Geometry inputGeom; |
| 77 |
|
| 78 |
protected GeometryFactory factory = null; |
| 79 |
|
| 80 |
|
| 81 |
/** |
| 82 |
* <code>true</code> if empty geometries should not be included in the result |
| 83 |
*/ |
| 84 |
private boolean pruneEmptyGeometry = true; |
| 85 |
|
| 86 |
/** |
| 87 |
* <code>true</code> if a homogenous collection result |
| 88 |
* from a {@link GeometryCollection} should still |
| 89 |
* be a general GeometryCollection |
| 90 |
*/ |
| 91 |
private boolean preserveGeometryCollectionType = true; |
| 92 |
|
| 93 |
/** |
| 94 |
* <code>true</code> if the output from a collection argument should still be a collection |
| 95 |
*/ |
| 96 |
private boolean preserveCollections = false; |
| 97 |
|
| 98 |
/** |
| 99 |
* <code>true</code> if the type of the input should be preserved |
| 100 |
*/ |
| 101 |
private boolean preserveType = false; |
| 102 |
|
| 103 |
public GeometryTransformer() { |
| 104 |
} |
| 105 |
|
| 106 |
/** |
| 107 |
* Utility function to make input geometry available |
| 108 |
* |
| 109 |
* @return the input geometry |
| 110 |
*/ |
| 111 |
public Geometry getInputGeometry() { return inputGeom; } |
| 112 |
|
| 113 |
public final Geometry transform(Geometry inputGeom) |
| 114 |
{ |
| 115 |
this.inputGeom = inputGeom; |
| 116 |
this.factory = inputGeom.getFactory(); |
| 117 |
|
| 118 |
if (inputGeom instanceof Point) |
| 119 |
return transformPoint((Point) inputGeom, null); |
| 120 |
if (inputGeom instanceof MultiPoint) |
| 121 |
return transformMultiPoint((MultiPoint) inputGeom, null); |
| 122 |
if (inputGeom instanceof LinearRing) |
| 123 |
return transformLinearRing((LinearRing) inputGeom, null); |
| 124 |
if (inputGeom instanceof LineString) |
| 125 |
return transformLineString((LineString) inputGeom, null); |
| 126 |
if (inputGeom instanceof MultiLineString) |
| 127 |
return transformMultiLineString((MultiLineString) inputGeom, null); |
| 128 |
if (inputGeom instanceof Polygon) |
| 129 |
return transformPolygon((Polygon) inputGeom, null); |
| 130 |
if (inputGeom instanceof MultiPolygon) |
| 131 |
return transformMultiPolygon((MultiPolygon) inputGeom, null); |
| 132 |
if (inputGeom instanceof GeometryCollection) |
| 133 |
return transformGeometryCollection((GeometryCollection) inputGeom, null); |
| 134 |
|
| 135 |
throw new IllegalArgumentException("Unknown Geometry subtype: " + inputGeom.getClass().getName()); |
| 136 |
} |
| 137 |
|
| 138 |
/** |
| 139 |
* Convenience method which provides standard way of |
| 140 |
* creating a {@link CoordinateSequence} |
| 141 |
* |
| 142 |
* @param coords the coordinate array to copy |
| 143 |
* @return a coordinate sequence for the array |
| 144 |
*/ |
| 145 |
protected final CoordinateSequence createCoordinateSequence(Coordinate[] coords) |
| 146 |
{ |
| 147 |
return factory.getCoordinateSequenceFactory().create(coords); |
| 148 |
} |
| 149 |
|
| 150 |
/** |
| 151 |
* Convenience method which provides a standard way of copying {@link CoordinateSequence}s |
| 152 |
* @param seq the sequence to copy |
| 153 |
* @return a deep copy of the sequence |
| 154 |
*/ |
| 155 |
protected final CoordinateSequence copy(CoordinateSequence seq) |
| 156 |
{ |
| 157 |
return seq.copy(); |
| 158 |
} |
| 159 |
|
| 160 |
/** |
| 161 |
* Transforms a {@link CoordinateSequence}. |
| 162 |
* This method should always return a valid coordinate list for |
| 163 |
* the desired result type. (E.g. a coordinate list for a LineString |
| 164 |
* must have 0 or at least 2 points). |
| 165 |
* If this is not possible, return an empty sequence - |
| 166 |
* this will be pruned out. |
| 167 |
* |
| 168 |
* @param coords the coordinates to transform |
| 169 |
* @param parent the parent geometry |
| 170 |
* @return the transformed coordinates |
| 171 |
*/ |
| 172 |
protected CoordinateSequence transformCoordinates(CoordinateSequence coords, Geometry parent) |
| 173 |
{ |
| 174 |
return copy(coords); |
| 175 |
} |
| 176 |
|
| 177 |
protected Geometry transformPoint(Point geom, Geometry parent) { |
| 178 |
return factory.createPoint( |
| 179 |
transformCoordinates(geom.getCoordinateSequence(), geom)); |
| 180 |
} |
| 181 |
|
| 182 |
protected Geometry transformMultiPoint(MultiPoint geom, Geometry parent) { |
| 183 |
List transGeomList = new ArrayList(); |
| 184 |
for (int i = 0; i < geom.getNumGeometries(); i++) { |
| 185 |
Geometry transformGeom = transformPoint((Point) geom.getGeometryN(i), geom); |
| 186 |
if (transformGeom == null) continue; |
| 187 |
if (transformGeom.isEmpty()) continue; |
| 188 |
transGeomList.add(transformGeom); |
| 189 |
} |
| 190 |
return factory.buildGeometry(transGeomList); |
| 191 |
} |
| 192 |
|
| 193 |
/** |
| 194 |
* Transforms a LinearRing. |
| 195 |
* The transformation of a LinearRing may result in a coordinate sequence |
| 196 |
* which does not form a structurally valid ring (i.e. a degenerate ring of 3 or fewer points). |
| 197 |
* In this case a LineString is returned. |
| 198 |
* Subclasses may wish to override this method and check for this situation |
| 199 |
* (e.g. a subclass may choose to eliminate degenerate linear rings) |
| 200 |
* |
| 201 |
* @param geom the ring to simplify |
| 202 |
* @param parent the parent geometry |
| 203 |
* @return a LinearRing if the transformation resulted in a structurally valid ring |
| 204 |
* @return a LineString if the transformation caused the LinearRing to collapse to 3 or fewer points |
| 205 |
*/ |
| 206 |
protected Geometry transformLinearRing(LinearRing geom, Geometry parent) { |
| 207 |
CoordinateSequence seq = transformCoordinates(geom.getCoordinateSequence(), geom); |
| 208 |
if (seq == null) |
| 209 |
return factory.createLinearRing((CoordinateSequence) null); |
| 210 |
int seqSize = seq.size(); |
| 211 |
|
| 212 |
if (seqSize > 0 && seqSize < 4 && ! preserveType) |
| 213 |
return factory.createLineString(seq); |
| 214 |
return factory.createLinearRing(seq); |
| 215 |
} |
| 216 |
|
| 217 |
/** |
| 218 |
* Transforms a {@link LineString} geometry. |
| 219 |
* |
| 220 |
* @param geom |
| 221 |
* @param parent |
| 222 |
* @return |
| 223 |
*/ |
| 224 |
protected Geometry transformLineString(LineString geom, Geometry parent) { |
| 225 |
|
| 226 |
return factory.createLineString( |
| 227 |
transformCoordinates(geom.getCoordinateSequence(), geom)); |
| 228 |
} |
| 229 |
|
| 230 |
protected Geometry transformMultiLineString(MultiLineString geom, Geometry parent) { |
| 231 |
List transGeomList = new ArrayList(); |
| 232 |
for (int i = 0; i < geom.getNumGeometries(); i++) { |
| 233 |
Geometry transformGeom = transformLineString((LineString) geom.getGeometryN(i), geom); |
| 234 |
if (transformGeom == null) continue; |
| 235 |
if (transformGeom.isEmpty()) continue; |
| 236 |
transGeomList.add(transformGeom); |
| 237 |
} |
| 238 |
return factory.buildGeometry(transGeomList); |
| 239 |
} |
| 240 |
|
| 241 |
protected Geometry transformPolygon(Polygon geom, Geometry parent) { |
| 242 |
boolean isAllValidLinearRings = true; |
| 243 |
Geometry shell = transformLinearRing(geom.getExteriorRing(), geom); |
| 244 |
|
| 245 |
if (shell == null |
| 246 |
|| ! (shell instanceof LinearRing) |
| 247 |
|| shell.isEmpty() ) |
| 248 |
isAllValidLinearRings = false; |
| 249 |
|
| 250 |
ArrayList holes = new ArrayList(); |
| 251 |
for (int i = 0; i < geom.getNumInteriorRing(); i++) { |
| 252 |
Geometry hole = transformLinearRing(geom.getInteriorRingN(i), geom); |
| 253 |
if (hole == null || hole.isEmpty()) { |
| 254 |
continue; |
| 255 |
} |
| 256 |
if (! (hole instanceof LinearRing)) |
| 257 |
isAllValidLinearRings = false; |
| 258 |
|
| 259 |
holes.add(hole); |
| 260 |
} |
| 261 |
|
| 262 |
if (isAllValidLinearRings) |
| 263 |
return factory.createPolygon((LinearRing) shell, |
| 264 |
(LinearRing[]) holes.toArray(new LinearRing[] { })); |
| 265 |
else { |
| 266 |
List components = new ArrayList(); |
| 267 |
if (shell != null) components.add(shell); |
| 268 |
components.addAll(holes); |
| 269 |
return factory.buildGeometry(components); |
| 270 |
} |
| 271 |
} |
| 272 |
|
| 273 |
protected Geometry transformMultiPolygon(MultiPolygon geom, Geometry parent) { |
| 274 |
List transGeomList = new ArrayList(); |
| 275 |
for (int i = 0; i < geom.getNumGeometries(); i++) { |
| 276 |
Geometry transformGeom = transformPolygon((Polygon) geom.getGeometryN(i), geom); |
| 277 |
if (transformGeom == null) continue; |
| 278 |
if (transformGeom.isEmpty()) continue; |
| 279 |
transGeomList.add(transformGeom); |
| 280 |
} |
| 281 |
return factory.buildGeometry(transGeomList); |
| 282 |
} |
| 283 |
|
| 284 |
protected Geometry transformGeometryCollection(GeometryCollection geom, Geometry parent) { |
| 285 |
List transGeomList = new ArrayList(); |
| 286 |
for (int i = 0; i < geom.getNumGeometries(); i++) { |
| 287 |
Geometry transformGeom = transform(geom.getGeometryN(i)); |
| 288 |
if (transformGeom == null) continue; |
| 289 |
if (pruneEmptyGeometry && transformGeom.isEmpty()) continue; |
| 290 |
transGeomList.add(transformGeom); |
| 291 |
} |
| 292 |
if (preserveGeometryCollectionType) |
| 293 |
return factory.createGeometryCollection(GeometryFactory.toGeometryArray(transGeomList)); |
| 294 |
return factory.buildGeometry(transGeomList); |
| 295 |
} |
| 296 |
|
| 297 |
} |
| 298 |
|