| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
package org.locationtech.jts.operation.union; |
| 13 |
|
| 14 |
|
| 15 |
import java.util.Collection; |
| 16 |
import java.util.List; |
| 17 |
|
| 18 |
import org.locationtech.jts.geom.Geometry; |
| 19 |
import org.locationtech.jts.geom.GeometryCollection; |
| 20 |
import org.locationtech.jts.geom.GeometryFactory; |
| 21 |
import org.locationtech.jts.geom.LineString; |
| 22 |
import org.locationtech.jts.geom.Point; |
| 23 |
import org.locationtech.jts.geom.Polygon; |
| 24 |
import org.locationtech.jts.geom.Puntal; |
| 25 |
import org.locationtech.jts.operation.linemerge.LineMerger; |
| 26 |
import org.locationtech.jts.operation.overlay.OverlayOp; |
| 27 |
import org.locationtech.jts.operation.overlay.snap.SnapIfNeededOverlayOp; |
| 28 |
|
| 29 |
/** |
| 30 |
* Unions a <code>Collection</code> of {@link Geometry}s or a single Geometry |
| 31 |
* (which may be a {@link GeoometryCollection}) together. |
| 32 |
* By using this special-purpose operation over a collection of geometries |
| 33 |
* it is possible to take advantage of various optimizations to improve performance. |
| 34 |
* Heterogeneous {@link GeometryCollection}s are fully supported. |
| 35 |
* <p> |
| 36 |
* The result obeys the following contract: |
| 37 |
* <ul> |
| 38 |
* <li>Unioning a set of {@link Polygon}s has the effect of |
| 39 |
* merging the areas (i.e. the same effect as |
| 40 |
* iteratively unioning all individual polygons together). |
| 41 |
* |
| 42 |
* <li>Unioning a set of {@link LineString}s has the effect of <b>noding</b> |
| 43 |
* and <b>dissolving</b> the input linework. |
| 44 |
* In this context "fully noded" means that there will be |
| 45 |
* an endpoint or node in the result |
| 46 |
* for every endpoint or line segment crossing in the input. |
| 47 |
* "Dissolved" means that any duplicate (i.e. coincident) line segments or portions |
| 48 |
* of line segments will be reduced to a single line segment in the result. |
| 49 |
* This is consistent with the semantics of the |
| 50 |
* {@link Geometry#union(Geometry)} operation. |
| 51 |
* If <b>merged</b> linework is required, the {@link LineMerger} class can be used. |
| 52 |
* |
| 53 |
* <li>Unioning a set of {@link Point}s has the effect of merging |
| 54 |
* all identical points (producing a set with no duplicates). |
| 55 |
* </ul> |
| 56 |
* |
| 57 |
* <tt>UnaryUnion</tt> always operates on the individual components of MultiGeometries. |
| 58 |
* So it is possible to use it to "clean" invalid self-intersecting MultiPolygons |
| 59 |
* (although the polygon components must all still be individually valid.) |
| 60 |
* |
| 61 |
* @author mbdavis |
| 62 |
* |
| 63 |
*/ |
| 64 |
public class UnaryUnionOp |
| 65 |
{ |
| 66 |
/** |
| 67 |
* Computes the geometric union of a {@link Collection} |
| 68 |
* of {@link Geometry}s. |
| 69 |
* |
| 70 |
* @param geoms a collection of geometries |
| 71 |
* @return the union of the geometries, |
| 72 |
* or <code>null</code> if the input is empty |
| 73 |
*/ |
| 74 |
public static Geometry union(Collection geoms) |
| 75 |
{ |
| 76 |
UnaryUnionOp op = new UnaryUnionOp(geoms); |
| 77 |
return op.union(); |
| 78 |
} |
| 79 |
|
| 80 |
/** |
| 81 |
* Computes the geometric union of a {@link Collection} |
| 82 |
* of {@link Geometry}s. |
| 83 |
* |
| 84 |
* If no input geometries were provided but a {@link GeometryFactory} was provided, |
| 85 |
* an empty {@link GeometryCollection} is returned. |
| 86 |
* |
| 87 |
* @param geoms a collection of geometries |
| 88 |
* @param geomFact the geometry factory to use if the collection is empty |
| 89 |
* @return the union of the geometries, |
| 90 |
* or an empty GEOMETRYCOLLECTION |
| 91 |
*/ |
| 92 |
public static Geometry union(Collection geoms, GeometryFactory geomFact) |
| 93 |
{ |
| 94 |
UnaryUnionOp op = new UnaryUnionOp(geoms, geomFact); |
| 95 |
return op.union(); |
| 96 |
} |
| 97 |
|
| 98 |
/** |
| 99 |
* Constructs a unary union operation for a {@link Geometry} |
| 100 |
* (which may be a {@link GeometryCollection}). |
| 101 |
* |
| 102 |
* @param geom a geometry to union |
| 103 |
* @return the union of the elements of the geometry |
| 104 |
* or an empty GEOMETRYCOLLECTION |
| 105 |
*/ |
| 106 |
public static Geometry union(Geometry geom) |
| 107 |
{ |
| 108 |
UnaryUnionOp op = new UnaryUnionOp(geom); |
| 109 |
return op.union(); |
| 110 |
} |
| 111 |
|
| 112 |
private GeometryFactory geomFact = null; |
| 113 |
private InputExtracter extracter; |
| 114 |
|
| 115 |
/** |
| 116 |
* Constructs a unary union operation for a {@link Collection} |
| 117 |
* of {@link Geometry}s. |
| 118 |
* |
| 119 |
* @param geoms a collection of geometries |
| 120 |
* @param geomFact the geometry factory to use if the collection is empty |
| 121 |
*/ |
| 122 |
public UnaryUnionOp(Collection geoms, GeometryFactory geomFact) |
| 123 |
{ |
| 124 |
this.geomFact = geomFact; |
| 125 |
extract(geoms); |
| 126 |
} |
| 127 |
|
| 128 |
/** |
| 129 |
* Constructs a unary union operation for a {@link Collection} |
| 130 |
* of {@link Geometry}s, using the {@link GeometryFactory} |
| 131 |
* of the input geometries. |
| 132 |
* |
| 133 |
* @param geoms a collection of geometries |
| 134 |
*/ |
| 135 |
public UnaryUnionOp(Collection geoms) |
| 136 |
{ |
| 137 |
extract(geoms); |
| 138 |
} |
| 139 |
|
| 140 |
/** |
| 141 |
* Constructs a unary union operation for a {@link Geometry} |
| 142 |
* (which may be a {@link GeometryCollection}). |
| 143 |
* @param geom |
| 144 |
*/ |
| 145 |
public UnaryUnionOp(Geometry geom) |
| 146 |
{ |
| 147 |
extract(geom); |
| 148 |
} |
| 149 |
|
| 150 |
private void extract(Collection geoms) |
| 151 |
{ |
| 152 |
extracter = InputExtracter.extract(geoms); |
| 153 |
} |
| 154 |
|
| 155 |
private void extract(Geometry geom) |
| 156 |
{ |
| 157 |
extracter = InputExtracter.extract(geom); |
| 158 |
} |
| 159 |
|
| 160 |
/** |
| 161 |
* Gets the union of the input geometries. |
| 162 |
* <p> |
| 163 |
* The result of empty input is determined as follows: |
| 164 |
* <ol> |
| 165 |
* <li>If the input is empty and a dimension can be |
| 166 |
* determined (i.e. an empty geometry is present), |
| 167 |
* an empty atomic geometry of that dimension is returned. |
| 168 |
* <li>If no input geometries were provided but a {@link GeometryFactory} was provided, |
| 169 |
* an empty {@link GeometryCollection} is returned. |
| 170 |
* <li>Otherwise, the return value is <code>null</code>. |
| 171 |
* </ol> |
| 172 |
* |
| 173 |
* @return a Geometry containing the union, |
| 174 |
* or an empty atomic geometry, or an empty GEOMETRYCOLLECTION, |
| 175 |
* or <code>null</code> if no GeometryFactory was provided |
| 176 |
*/ |
| 177 |
public Geometry union() |
| 178 |
{ |
| 179 |
if (geomFact == null) |
| 180 |
geomFact = extracter.getFactory(); |
| 181 |
|
| 182 |
|
| 183 |
if (geomFact == null) { |
| 184 |
return null; |
| 185 |
} |
| 186 |
|
| 187 |
|
| 188 |
if (extracter.isEmpty()) { |
| 189 |
return geomFact.createEmpty( extracter.getDimension() ); |
| 190 |
} |
| 191 |
List points = extracter.getExtract(0); |
| 192 |
List lines = extracter.getExtract(1); |
| 193 |
List polygons = extracter.getExtract(2); |
| 194 |
|
| 195 |
/** |
| 196 |
* For points and lines, only a single union operation is |
| 197 |
* required, since the OGC model allows self-intersecting |
| 198 |
* MultiPoint and MultiLineStrings. |
| 199 |
* This is not the case for polygons, so Cascaded Union is required. |
| 200 |
*/ |
| 201 |
Geometry unionPoints = null; |
| 202 |
if (points.size() > 0) { |
| 203 |
Geometry ptGeom = geomFact.buildGeometry(points); |
| 204 |
unionPoints = unionNoOpt(ptGeom); |
| 205 |
} |
| 206 |
|
| 207 |
Geometry unionLines = null; |
| 208 |
if (lines.size() > 0) { |
| 209 |
Geometry lineGeom = geomFact.buildGeometry(lines); |
| 210 |
unionLines = unionNoOpt(lineGeom); |
| 211 |
} |
| 212 |
|
| 213 |
Geometry unionPolygons = null; |
| 214 |
if (polygons.size() > 0) { |
| 215 |
unionPolygons = CascadedPolygonUnion.union(polygons); |
| 216 |
} |
| 217 |
|
| 218 |
/** |
| 219 |
* Performing two unions is somewhat inefficient, |
| 220 |
* but is mitigated by unioning lines and points first |
| 221 |
*/ |
| 222 |
Geometry unionLA = unionWithNull(unionLines, unionPolygons); |
| 223 |
Geometry union = null; |
| 224 |
if (unionPoints == null) |
| 225 |
union = unionLA; |
| 226 |
else if (unionLA == null) |
| 227 |
union = unionPoints; |
| 228 |
else |
| 229 |
union = PointGeometryUnion.union((Puntal) unionPoints, unionLA); |
| 230 |
|
| 231 |
if (union == null) |
| 232 |
return geomFact.createGeometryCollection(); |
| 233 |
|
| 234 |
return union; |
| 235 |
} |
| 236 |
|
| 237 |
/** |
| 238 |
* Computes the union of two geometries, |
| 239 |
* either of both of which may be null. |
| 240 |
* |
| 241 |
* @param g0 a Geometry |
| 242 |
* @param g1 a Geometry |
| 243 |
* @return the union of the input(s) |
| 244 |
* or null if both inputs are null |
| 245 |
*/ |
| 246 |
private Geometry unionWithNull(Geometry g0, Geometry g1) |
| 247 |
{ |
| 248 |
if (g0 == null && g1 == null) |
| 249 |
return null; |
| 250 |
|
| 251 |
if (g1 == null) |
| 252 |
return g0; |
| 253 |
if (g0 == null) |
| 254 |
return g1; |
| 255 |
|
| 256 |
return g0.union(g1); |
| 257 |
} |
| 258 |
|
| 259 |
/** |
| 260 |
* Computes a unary union with no extra optimization, |
| 261 |
* and no short-circuiting. |
| 262 |
* Due to the way the overlay operations |
| 263 |
* are implemented, this is still efficient in the case of linear |
| 264 |
* and puntal geometries. |
| 265 |
* Uses robust version of overlay operation |
| 266 |
* to ensure identical behaviour to the <tt>union(Geometry)</tt> operation. |
| 267 |
* |
| 268 |
* @param g0 a geometry |
| 269 |
* @return the union of the input geometry |
| 270 |
*/ |
| 271 |
private Geometry unionNoOpt(Geometry g0) |
| 272 |
{ |
| 273 |
Geometry empty = geomFact.createPoint(); |
| 274 |
return SnapIfNeededOverlayOp.overlayOp(g0, empty, OverlayOp.UNION); |
| 275 |
} |
| 276 |
|
| 277 |
} |
| 278 |
|