/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.operation.union; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.TreeSet; import com.revolsys.geometry.algorithm.PointLocator; import com.revolsys.geometry.graph.linemerge.LineMerger; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryCollection; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Location; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.geometry.model.Punctual; import com.revolsys.geometry.operation.overlay.OverlayOp; import com.revolsys.geometry.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. * {@link Geometry#isHeterogeneousGeometryCollection()}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(final Collection<? extends Geometry> geometries) { return union(geometries, null); } /** * 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 Geometry} is returned. * * @param geoms a collection of geometries * @param geometryFactory the geometry factory to use if the collection is empty * @return the union of the geometries, * or an empty GEOMETRYCOLLECTION */ public static Geometry union(final Collection<? extends Geometry> geometries, GeometryFactory geometryFactory) { final List<Point> points = new ArrayList<>(); final List<LineString> lines = new ArrayList<>(); final List<Polygon> polygons = new ArrayList<>(); for (final Geometry geometry : geometries) { if (geometryFactory == null) { geometryFactory = geometry.getGeometryFactory(); } points.addAll(geometry.getGeometries(Point.class)); lines.addAll(geometry.getGeometries(LineString.class)); polygons.addAll(geometry.getGeometries(Polygon.class)); } return union(geometryFactory, points, lines, polygons); } /** * 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(final Geometry... geometries) { return union(Arrays.asList(geometries)); } /** * Gets the union of the input geometries. * If no input geometries were provided but a {@link GeometryFactory} was provided, * an empty {@link Geometry} 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 */ private static Geometry union(final GeometryFactory geometryFactory, final List<Point> points, final List<LineString> lines, final List<Polygon> polygons) { if (geometryFactory == null) { return null; } else { /** * For points and lines, only a single union operation is * required, since the OGC model allowing self-intersecting * MultiPoint and MultiLineStrings. * This is not the case for polygons, so Cascaded Union is required. */ Punctual unionPoints = null; if (points.size() > 0) { final Punctual punctual = geometryFactory.punctual(points); unionPoints = punctual.union(); } Geometry unionLines = null; if (lines.size() > 0) { final Geometry lineal = geometryFactory.geometry(lines); unionLines = unionNoOpt(geometryFactory, lineal); } 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 union = null; if (unionLines == null) { union = unionPolygons; } else if (unionPolygons == null) { union = unionLines; } else { union = unionPolygons.union(unionLines); } if (unionPoints != null) { if (union == null) { union = unionPoints; } else { union = union(unionPoints, union); } } if (union == null) { return geometryFactory.geometryCollection(); } else { return union; } } } public static Geometry union(final Punctual punctual, final Geometry otherGeom) { final PointLocator locater = new PointLocator(); Set<Geometry> exteriorGeometries = null; for (final Point point : punctual.points()) { final Location loc = locater.locate(point, otherGeom); if (loc == Location.EXTERIOR) { if (exteriorGeometries == null) { exteriorGeometries = new TreeSet<>(); } exteriorGeometries.add(point); } } if (exteriorGeometries == null) { return otherGeom; } else { for (final Geometry geometry : otherGeom.geometries()) { exteriorGeometries.add(geometry); } final GeometryFactory geomFactory = otherGeom.getGeometryFactory(); return geomFactory.geometry(exteriorGeometries); } } /** * 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 geometry a geometry * @return the union of the input geometry */ private static Geometry unionNoOpt(final GeometryFactory geometryFactory, final Geometry geometry) { final Geometry empty = geometryFactory.point(); return SnapIfNeededOverlayOp.overlayOp(geometry, empty, OverlayOp.UNION); } }