| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
package org.locationtech.jts.geom.util; |
| 14 |
|
| 15 |
|
| 16 |
import java.util.ArrayList; |
| 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 |
import org.locationtech.jts.util.Assert; |
| 31 |
|
| 32 |
|
| 33 |
/** |
| 34 |
* A class which supports creating new {@link Geometry}s |
| 35 |
* which are modifications of existing ones, |
| 36 |
* maintaining the same type structure. |
| 37 |
* Geometry objects are intended to be treated as immutable. |
| 38 |
* This class "modifies" Geometrys |
| 39 |
* by traversing them, applying a user-defined |
| 40 |
* {@link GeometryEditorOperation}, {@link CoordinateSequenceOperation} or {@link CoordinateOperation} |
| 41 |
* and creating new Geometrys with the same structure but |
| 42 |
* (possibly) modified components. |
| 43 |
* <p> |
| 44 |
* Examples of the kinds of modifications which can be made are: |
| 45 |
* <ul> |
| 46 |
* <li>the values of the coordinates may be changed. |
| 47 |
* The editor does not check whether changing coordinate values makes the result Geometry invalid |
| 48 |
* <li>the coordinate lists may be changed |
| 49 |
* (e.g. by adding, deleting or modifying coordinates). |
| 50 |
* The modified coordinate lists must be consistent with their original parent component |
| 51 |
* (e.g. a <tt>LinearRing</tt> must always have at least 4 coordinates, and the first and last |
| 52 |
* coordinate must be equal) |
| 53 |
* <li>components of the original geometry may be deleted |
| 54 |
* (e.g. holes may be removed from a Polygon, or LineStrings removed from a MultiLineString). |
| 55 |
* Deletions will be propagated up the component tree appropriately. |
| 56 |
* </ul> |
| 57 |
* All changes must be consistent with the original Geometry's structure |
| 58 |
* (e.g. a <tt>Polygon</tt> cannot be collapsed into a <tt>LineString</tt>). |
| 59 |
* If changing the structure is required, use a {@link GeometryTransformer}. |
| 60 |
* <p> |
| 61 |
* This class supports creating an edited Geometry |
| 62 |
* using a different <code>GeometryFactory</code> via the {@link #GeometryEditor(GeometryFactory)} |
| 63 |
* constructor. |
| 64 |
* Examples of situations where this is required is if the geometry is |
| 65 |
* transformed to a new SRID and/or a new PrecisionModel. |
| 66 |
* <p> |
| 67 |
* <b>Usage Notes</b> |
| 68 |
* <ul> |
| 69 |
* <li>The resulting Geometry is not checked for validity. |
| 70 |
* If validity needs to be enforced, the new Geometry's |
| 71 |
* {@link Geometry#isValid} method should be called. |
| 72 |
* <li>By default the UserData of the input geometry is not copied to the result. |
| 73 |
* </ul> |
| 74 |
* |
| 75 |
* @see GeometryTransformer |
| 76 |
* @see Geometry#isValid |
| 77 |
* |
| 78 |
* @version 1.7 |
| 79 |
*/ |
| 80 |
public class GeometryEditor |
| 81 |
{ |
| 82 |
/** |
| 83 |
* The factory used to create the modified Geometry. |
| 84 |
* If <tt>null</tt> the GeometryFactory of the input is used. |
| 85 |
*/ |
| 86 |
private GeometryFactory factory = null; |
| 87 |
private boolean isUserDataCopied = false; |
| 88 |
|
| 89 |
/** |
| 90 |
* Creates a new GeometryEditor object which will create |
| 91 |
* edited {@link Geometry}s with the same {@link GeometryFactory} as the input Geometry. |
| 92 |
*/ |
| 93 |
public GeometryEditor() |
| 94 |
{ |
| 95 |
} |
| 96 |
|
| 97 |
/** |
| 98 |
* Creates a new GeometryEditor object which will create |
| 99 |
* edited {@link Geometry}s with the given {@link GeometryFactory}. |
| 100 |
* |
| 101 |
* @param factory the GeometryFactory to create edited Geometrys with |
| 102 |
*/ |
| 103 |
public GeometryEditor(GeometryFactory factory) |
| 104 |
{ |
| 105 |
this.factory = factory; |
| 106 |
} |
| 107 |
|
| 108 |
/** |
| 109 |
* Sets whether the User Data is copied to the edit result. |
| 110 |
* Only the object reference is copied. |
| 111 |
* |
| 112 |
* @param isUserDataCopied true if the input user data should be copied. |
| 113 |
*/ |
| 114 |
public void setCopyUserData(boolean isUserDataCopied) |
| 115 |
{ |
| 116 |
this.isUserDataCopied = isUserDataCopied; |
| 117 |
} |
| 118 |
|
| 119 |
/** |
| 120 |
* Edit the input {@link Geometry} with the given edit operation. |
| 121 |
* Clients can create subclasses of {@link GeometryEditorOperation} or |
| 122 |
* {@link CoordinateOperation} to perform required modifications. |
| 123 |
* |
| 124 |
* @param geometry the Geometry to edit |
| 125 |
* @param operation the edit operation to carry out |
| 126 |
* @return a new {@link Geometry} which is the result of the editing (which may be empty) |
| 127 |
*/ |
| 128 |
public Geometry edit(Geometry geometry, GeometryEditorOperation operation) |
| 129 |
{ |
| 130 |
|
| 131 |
if (geometry == null) return null; |
| 132 |
|
| 133 |
Geometry result = editInternal(geometry, operation); |
| 134 |
if (isUserDataCopied) { |
| 135 |
result.setUserData(geometry.getUserData()); |
| 136 |
} |
| 137 |
return result; |
| 138 |
} |
| 139 |
|
| 140 |
private Geometry editInternal(Geometry geometry, GeometryEditorOperation operation) |
| 141 |
{ |
| 142 |
|
| 143 |
if (factory == null) |
| 144 |
factory = geometry.getFactory(); |
| 145 |
|
| 146 |
if (geometry instanceof GeometryCollection) { |
| 147 |
return editGeometryCollection((GeometryCollection) geometry, |
| 148 |
operation); |
| 149 |
} |
| 150 |
|
| 151 |
if (geometry instanceof Polygon) { |
| 152 |
return editPolygon((Polygon) geometry, operation); |
| 153 |
} |
| 154 |
|
| 155 |
if (geometry instanceof Point) { |
| 156 |
return operation.edit(geometry, factory); |
| 157 |
} |
| 158 |
|
| 159 |
if (geometry instanceof LineString) { |
| 160 |
return operation.edit(geometry, factory); |
| 161 |
} |
| 162 |
|
| 163 |
Assert.shouldNeverReachHere("Unsupported Geometry class: " + geometry.getClass().getName()); |
| 164 |
return null; |
| 165 |
} |
| 166 |
|
| 167 |
private Polygon editPolygon(Polygon polygon, |
| 168 |
GeometryEditorOperation operation) { |
| 169 |
Polygon newPolygon = (Polygon) operation.edit(polygon, factory); |
| 170 |
|
| 171 |
if (newPolygon == null) |
| 172 |
newPolygon = factory.createPolygon(); |
| 173 |
if (newPolygon.isEmpty()) { |
| 174 |
|
| 175 |
return newPolygon; |
| 176 |
} |
| 177 |
|
| 178 |
LinearRing shell = (LinearRing) edit(newPolygon.getExteriorRing(), operation); |
| 179 |
if (shell == null || shell.isEmpty()) { |
| 180 |
|
| 181 |
return factory.createPolygon(); |
| 182 |
} |
| 183 |
|
| 184 |
ArrayList holes = new ArrayList(); |
| 185 |
for (int i = 0; i < newPolygon.getNumInteriorRing(); i++) { |
| 186 |
LinearRing hole = (LinearRing) edit(newPolygon.getInteriorRingN(i), operation); |
| 187 |
if (hole == null || hole.isEmpty()) { |
| 188 |
continue; |
| 189 |
} |
| 190 |
holes.add(hole); |
| 191 |
} |
| 192 |
|
| 193 |
return factory.createPolygon(shell, |
| 194 |
(LinearRing[]) holes.toArray(new LinearRing[] { })); |
| 195 |
} |
| 196 |
|
| 197 |
private GeometryCollection editGeometryCollection( |
| 198 |
GeometryCollection collection, GeometryEditorOperation operation) { |
| 199 |
|
| 200 |
|
| 201 |
GeometryCollection collectionForType = (GeometryCollection) operation.edit(collection, |
| 202 |
factory); |
| 203 |
|
| 204 |
|
| 205 |
ArrayList geometries = new ArrayList(); |
| 206 |
for (int i = 0; i < collectionForType.getNumGeometries(); i++) { |
| 207 |
Geometry geometry = edit(collectionForType.getGeometryN(i), operation); |
| 208 |
if (geometry == null || geometry.isEmpty()) { |
| 209 |
continue; |
| 210 |
} |
| 211 |
geometries.add(geometry); |
| 212 |
} |
| 213 |
|
| 214 |
if (collectionForType.getClass() == MultiPoint.class) { |
| 215 |
return factory.createMultiPoint((Point[]) geometries.toArray( |
| 216 |
new Point[] { })); |
| 217 |
} |
| 218 |
if (collectionForType.getClass() == MultiLineString.class) { |
| 219 |
return factory.createMultiLineString((LineString[]) geometries.toArray( |
| 220 |
new LineString[] { })); |
| 221 |
} |
| 222 |
if (collectionForType.getClass() == MultiPolygon.class) { |
| 223 |
return factory.createMultiPolygon((Polygon[]) geometries.toArray( |
| 224 |
new Polygon[] { })); |
| 225 |
} |
| 226 |
return factory.createGeometryCollection((Geometry[]) geometries.toArray( |
| 227 |
new Geometry[] { })); |
| 228 |
} |
| 229 |
|
| 230 |
/** |
| 231 |
* A interface which specifies an edit operation for Geometries. |
| 232 |
* |
| 233 |
* @version 1.7 |
| 234 |
*/ |
| 235 |
public interface GeometryEditorOperation |
| 236 |
{ |
| 237 |
/** |
| 238 |
* Edits a Geometry by returning a new Geometry with a modification. |
| 239 |
* The returned geometry may be: |
| 240 |
* <ul> |
| 241 |
* <li>the input geometry itself. |
| 242 |
* The returned Geometry might be the same as the Geometry passed in. |
| 243 |
* <li><code>null</code> if the geometry is to be deleted. |
| 244 |
* </ul> |
| 245 |
* |
| 246 |
* @param geometry the Geometry to modify |
| 247 |
* @param factory the factory with which to construct the modified Geometry |
| 248 |
* (may be different to the factory of the input geometry) |
| 249 |
* @return a new Geometry which is a modification of the input Geometry |
| 250 |
* @return null if the Geometry is to be deleted completely |
| 251 |
*/ |
| 252 |
Geometry edit(Geometry geometry, GeometryFactory factory); |
| 253 |
} |
| 254 |
|
| 255 |
/** |
| 256 |
* A GeometryEditorOperation which does not modify |
| 257 |
* the input geometry. |
| 258 |
* This can be used for simple changes of |
| 259 |
* GeometryFactory (including PrecisionModel and SRID). |
| 260 |
* |
| 261 |
* @author mbdavis |
| 262 |
* |
| 263 |
*/ |
| 264 |
public static class NoOpGeometryOperation |
| 265 |
implements GeometryEditorOperation |
| 266 |
{ |
| 267 |
public Geometry edit(Geometry geometry, GeometryFactory factory) |
| 268 |
{ |
| 269 |
return geometry; |
| 270 |
} |
| 271 |
} |
| 272 |
|
| 273 |
/** |
| 274 |
* A {@link GeometryEditorOperation} which edits the coordinate list of a {@link Geometry}. |
| 275 |
* Operates on Geometry subclasses which contains a single coordinate list. |
| 276 |
*/ |
| 277 |
public abstract static class CoordinateOperation |
| 278 |
implements GeometryEditorOperation |
| 279 |
{ |
| 280 |
public final Geometry edit(Geometry geometry, GeometryFactory factory) { |
| 281 |
if (geometry instanceof LinearRing) { |
| 282 |
return factory.createLinearRing(edit(geometry.getCoordinates(), |
| 283 |
geometry)); |
| 284 |
} |
| 285 |
|
| 286 |
if (geometry instanceof LineString) { |
| 287 |
return factory.createLineString(edit(geometry.getCoordinates(), |
| 288 |
geometry)); |
| 289 |
} |
| 290 |
|
| 291 |
if (geometry instanceof Point) { |
| 292 |
Coordinate[] newCoordinates = edit(geometry.getCoordinates(), |
| 293 |
geometry); |
| 294 |
|
| 295 |
return factory.createPoint((newCoordinates.length > 0) |
| 296 |
? newCoordinates[0] : null); |
| 297 |
} |
| 298 |
|
| 299 |
return geometry; |
| 300 |
} |
| 301 |
|
| 302 |
/** |
| 303 |
* Edits the array of {@link Coordinate}s from a {@link Geometry}. |
| 304 |
* <p> |
| 305 |
* If it is desired to preserve the immutability of Geometrys, |
| 306 |
* if the coordinates are changed a new array should be created |
| 307 |
* and returned. |
| 308 |
* |
| 309 |
* @param coordinates the coordinate array to operate on |
| 310 |
* @param geometry the geometry containing the coordinate list |
| 311 |
* @return an edited coordinate array (which may be the same as the input) |
| 312 |
*/ |
| 313 |
public abstract Coordinate[] edit(Coordinate[] coordinates, |
| 314 |
Geometry geometry); |
| 315 |
} |
| 316 |
|
| 317 |
/** |
| 318 |
* A {@link GeometryEditorOperation} which edits the {@link CoordinateSequence} |
| 319 |
* of a {@link Geometry}. |
| 320 |
* Operates on Geometry subclasses which contains a single coordinate list. |
| 321 |
*/ |
| 322 |
public abstract static class CoordinateSequenceOperation |
| 323 |
implements GeometryEditorOperation |
| 324 |
{ |
| 325 |
public final Geometry edit(Geometry geometry, GeometryFactory factory) { |
| 326 |
if (geometry instanceof LinearRing) { |
| 327 |
return factory.createLinearRing(edit( |
| 328 |
((LinearRing)geometry).getCoordinateSequence(), |
| 329 |
geometry)); |
| 330 |
} |
| 331 |
|
| 332 |
if (geometry instanceof LineString) { |
| 333 |
return factory.createLineString(edit( |
| 334 |
((LineString)geometry).getCoordinateSequence(), |
| 335 |
geometry)); |
| 336 |
} |
| 337 |
|
| 338 |
if (geometry instanceof Point) { |
| 339 |
return factory.createPoint(edit( |
| 340 |
((Point)geometry).getCoordinateSequence(), |
| 341 |
geometry)); |
| 342 |
} |
| 343 |
|
| 344 |
return geometry; |
| 345 |
} |
| 346 |
|
| 347 |
/** |
| 348 |
* Edits a {@link CoordinateSequence} from a {@link Geometry}. |
| 349 |
* |
| 350 |
* @param coordSeq the coordinate array to operate on |
| 351 |
* @param geometry the geometry containing the coordinate list |
| 352 |
* @return an edited coordinate sequence (which may be the same as the input) |
| 353 |
*/ |
| 354 |
public abstract CoordinateSequence edit(CoordinateSequence coordSeq, |
| 355 |
Geometry geometry); |
| 356 |
} |
| 357 |
} |
| 358 |
|