/*
* Copyright (c) 2016 Vivid Solutions.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/
package org.locationtech.jts.operation.union;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.Puntal;
import org.locationtech.jts.geom.util.GeometryExtracter;
import org.locationtech.jts.operation.linemerge.LineMerger;
import org.locationtech.jts.operation.overlay.OverlayOp;
import org.locationtech.jts.operation.overlay.snap.SnapIfNeededOverlayOp;
/**
* Unions a <code>Collection</code> of {@link Geometry}s or a single Geometry
* (which may be a {@link GeoometryCollection}) together.
* By using this special-purpose operation over a collection of geometries
* it is possible to take advantage of various optimizations to improve performance.
* Heterogeneous {@link GeometryCollection}s are fully supported.
* <p>
* The result obeys the following contract:
* <ul>
* <li>Unioning a set of {@link Polygon}s has the effect of
* merging the areas (i.e. the same effect as
* iteratively unioning all individual polygons together).
*
* <li>Unioning a set of {@link LineString}s has the effect of <b>noding</b>
* and <b>dissolving</b> the input linework.
* In this context "fully noded" means that there will be
* an endpoint or node in the result
* for every endpoint or line segment crossing in the input.
* "Dissolved" means that any duplicate (i.e. coincident) line segments or portions
* of line segments will be reduced to a single line segment in the result.
* This is consistent with the semantics of the
* {@link Geometry#union(Geometry)} operation.
* If <b>merged</b> linework is required, the {@link LineMerger} class can be used.
*
* <li>Unioning a set of {@link Point}s has the effect of merging
* all identical points (producing a set with no duplicates).
* </ul>
*
* <tt>UnaryUnion</tt> always operates on the individual components of MultiGeometries.
* So it is possible to use it to "clean" invalid self-intersecting MultiPolygons
* (although the polygon components must all still be individually valid.)
*
* @author mbdavis
*
*/
public class UnaryUnionOp
{
/**
* Computes the geometric union of a {@link Collection}
* of {@link Geometry}s.
*
* @param geoms a collection of geometries
* @return the union of the geometries,
* or <code>null</code> if the input is empty
*/
public static Geometry union(Collection geoms)
{
UnaryUnionOp op = new UnaryUnionOp(geoms);
return op.union();
}
/**
* Computes the geometric union of a {@link Collection}
* of {@link Geometry}s.
*
* If no input geometries were provided but a {@link GeometryFactory} was provided,
* an empty {@link GeometryCollection} is returned.
*
* @param geoms a collection of geometries
* @param geomFact the geometry factory to use if the collection is empty
* @return the union of the geometries,
* or an empty GEOMETRYCOLLECTION
*/
public static Geometry union(Collection geoms, GeometryFactory geomFact)
{
UnaryUnionOp op = new UnaryUnionOp(geoms, geomFact);
return op.union();
}
/**
* Constructs a unary union operation for a {@link Geometry}
* (which may be a {@link GeometryCollection}).
*
* @param geom a geometry to union
* @return the union of the elements of the geometry
* or an empty GEOMETRYCOLLECTION
*/
public static Geometry union(Geometry geom)
{
UnaryUnionOp op = new UnaryUnionOp(geom);
return op.union();
}
private List polygons = new ArrayList();
private List lines = new ArrayList();
private List points = new ArrayList();
private GeometryFactory geomFact = null;
/**
* Constructs a unary union operation for a {@link Collection}
* of {@link Geometry}s.
*
* @param geoms a collection of geometries
* @param geomFact the geometry factory to use if the collection is empty
*/
public UnaryUnionOp(Collection geoms, GeometryFactory geomFact)
{
this.geomFact = geomFact;
extract(geoms);
}
/**
* Constructs a unary union operation for a {@link Collection}
* of {@link Geometry}s, using the {@link GeometryFactory}
* of the input geometries.
*
* @param geoms a collection of geometries
*/
public UnaryUnionOp(Collection geoms)
{
extract(geoms);
}
/**
* Constructs a unary union operation for a {@link Geometry}
* (which may be a {@link GeometryCollection}).
* @param geom
*/
public UnaryUnionOp(Geometry geom)
{
extract(geom);
}
private void extract(Collection geoms)
{
for (Iterator i = geoms.iterator(); i.hasNext();) {
Geometry geom = (Geometry) i.next();
extract(geom);
}
}
private void extract(Geometry geom)
{
if (geomFact == null)
geomFact = geom.getFactory();
/*
PolygonExtracter.getPolygons(geom, polygons);
LineStringExtracter.getLines(geom, lines);
PointExtracter.getPoints(geom, points);
*/
GeometryExtracter.extract(geom, Polygon.class, polygons);
GeometryExtracter.extract(geom, LineString.class, lines);
GeometryExtracter.extract(geom, Point.class, points);
}
/**
* Gets the union of the input geometries.
* If no input geometries were provided but a {@link GeometryFactory} was provided,
* an empty {@link GeometryCollection} is returned.
* Otherwise, the return value is <code>null</code>.
*
* @return a Geometry containing the union,
* or an empty GEOMETRYCOLLECTION if no geometries were provided in the input,
* or <code>null</code> if no GeometryFactory was provided
*/
public Geometry union()
{
if (geomFact == null) {
return null;
}
/**
* For points and lines, only a single union operation is
* required, since the OGC model allows self-intersecting
* MultiPoint and MultiLineStrings.
* This is not the case for polygons, so Cascaded Union is required.
*/
Geometry unionPoints = null;
if (points.size() > 0) {
Geometry ptGeom = geomFact.buildGeometry(points);
unionPoints = unionNoOpt(ptGeom);
}
Geometry unionLines = null;
if (lines.size() > 0) {
Geometry lineGeom = geomFact.buildGeometry(lines);
unionLines = unionNoOpt(lineGeom);
}
Geometry unionPolygons = null;
if (polygons.size() > 0) {
unionPolygons = CascadedPolygonUnion.union(polygons);
}
/**
* Performing two unions is somewhat inefficient,
* but is mitigated by unioning lines and points first
*/
Geometry unionLA = unionWithNull(unionLines, unionPolygons);
Geometry union = null;
if (unionPoints == null)
union = unionLA;
else if (unionLA == null)
union = unionPoints;
else
union = PointGeometryUnion.union((Puntal) unionPoints, unionLA);
if (union == null)
return geomFact.createGeometryCollection(null);
return union;
}
/**
* Computes the union of two geometries,
* either of both of which may be null.
*
* @param g0 a Geometry
* @param g1 a Geometry
* @return the union of the input(s)
* or null if both inputs are null
*/
private Geometry unionWithNull(Geometry g0, Geometry g1)
{
if (g0 == null && g1 == null)
return null;
if (g1 == null)
return g0;
if (g0 == null)
return g1;
return g0.union(g1);
}
/**
* Computes a unary union with no extra optimization,
* and no short-circuiting.
* Due to the way the overlay operations
* are implemented, this is still efficient in the case of linear
* and puntal geometries.
* Uses robust version of overlay operation
* to ensure identical behaviour to the <tt>union(Geometry)</tt> operation.
*
* @param g0 a geometry
* @return the union of the input geometry
*/
private Geometry unionNoOpt(Geometry g0)
{
Geometry empty = geomFact.createPoint((Coordinate) null);
return SnapIfNeededOverlayOp.overlayOp(g0, empty, OverlayOp.UNION);
}
}