/* * 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.polygonize; import java.util.ArrayList; import java.util.Collection; import java.util.List; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.LinearRing; import com.revolsys.geometry.model.Polygon; import com.revolsys.geometry.model.Polygonal; /** * Polygonizes a set of {@link Geometry}s which contain linework that * represents the edges of a planar graph. * All types of Geometry are accepted as input; * the constituent linework is extracted as the edges to be polygonized. * The processed edges must be correctly noded; that is, they must only meet * at their endpoints. The Polygonizer will run on incorrectly noded input * but will not form polygons from non-noded edges, * and will report them as errors. * <p> * The Polygonizer reports the follow kinds of errors: * <ul> * <li><b>Dangles</b> - edges which have one or both ends which are not incident on another edge endpoint * <li><b>Cut Edges</b> - edges which are connected at both ends but which do not form part of polygon * <li><b>Invalid Ring Lines</b> - edges which form rings which are invalid * (e.g. the component lines contain a self-intersection) * </ul> * * @version 1.7 */ public class Polygonizer { private static void assignHolesToShells(final List<EdgeRing> holeList, final List<EdgeRing> shellList) { for (final EdgeRing holeER : holeList) { assignHoleToShell(holeER, shellList); } } private static void assignHoleToShell(final EdgeRing holeER, final List<EdgeRing> shellList) { final EdgeRing shell = EdgeRing.findEdgeRingContaining(holeER, shellList); if (shell != null) { shell.addHole(holeER.getRing()); } } protected List<LineString> cutEdges = new ArrayList<>(); // initialize with empty collections, in case nothing is computed protected Collection<LineString> dangles = new ArrayList<>(); protected PolygonizeGraph graph; protected List<EdgeRing> holeList = null; protected List<LineString> invalidRingLines = new ArrayList<>(); private boolean isCheckingRingsValid = true; protected List<Polygon> polygons = null; protected List<EdgeRing> shellList = null; /** * Construct a new polygonizer with the same {@link GeometryFactory} * as the input {@link Geometry}s */ public Polygonizer() { } /** * Add a {@link Geometry} to the edges to be polygonized. * May be called multiple times. * Any dimension of Geometry may be added; * the constituent linework will be extracted and used * * @param geometry a {@link Geometry} with linework to be polygonized */ public void add(final Geometry geometry) { for (final LineString line : geometry.getGeometryComponents(LineString.class)) { add(line); } } /** * Adds a collection of geometries to the edges to be polygonized. * May be called multiple times. * Any dimension of Geometry may be added; * the constituent linework will be extracted and used. * * @param geometries a list of {@link Geometry}s with linework to be polygonized */ public void add(final Iterable<? extends Geometry> geometries) { for (final Geometry geometry : geometries) { add(geometry); } } /** * Adds a linestring to the graph of polygon edges. * * @param line the {@link LineString} to add */ private void add(final LineString line) { // Construct a new new graph using the factory from the input Geometry if (this.graph == null) { final GeometryFactory geometryFactory = line.getGeometryFactory(); this.graph = new PolygonizeGraph(geometryFactory); } this.graph.addEdge(line); } public void addLineString(LineString lineString) { if (lineString instanceof LinearRing) { lineString = ((LinearRing)lineString).newLineStringEmpty(); } // unioning the linestring with the point makes any self intersections // explicit. final Geometry geometry = lineString.newValidGeometry(); // Add result to polygonizer add(geometry); } public void addPolygon(final Polygon polygon) { for (final LinearRing ring : polygon.rings()) { addLineString(ring); } } private void findShellsAndHoles(final List<EdgeRing> edgeRingList) { this.holeList = new ArrayList<>(); this.shellList = new ArrayList<>(); for (final EdgeRing edgeRing : edgeRingList) { if (edgeRing.isHole()) { this.holeList.add(edgeRing); } else { this.shellList.add(edgeRing); } } } private void findValidRings(final List<EdgeRing> edgeRingList, final List<EdgeRing> validEdgeRingList, final List<LineString> invalidRingList) { for (final EdgeRing edgeRing : edgeRingList) { if (edgeRing.isValid()) { validEdgeRingList.add(edgeRing); } else { invalidRingList.add(edgeRing.getLineString()); } } } /** * Gets the list of cut edges found during polygonization. * @return a collection of the input {@link LineString}s which are cut edges */ public Collection<LineString> getCutEdges() { polygonize(); return this.cutEdges; } /** * Gets the list of dangling lines found during polygonization. * @return a collection of the input {@link LineString}s which are dangles */ public Collection<LineString> getDangles() { polygonize(); return this.dangles; } public GeometryFactory getGeometryFactory() { if (this.graph == null) { return GeometryFactory.DEFAULT_3D; } else { return this.graph.getGeometryFactory(); } } /** * Gets the list of lines forming invalid rings found during polygonization. * @return a collection of the input {@link LineString}s which form invalid rings */ public Collection<LineString> getInvalidRingLines() { polygonize(); return this.invalidRingLines; } public Polygonal getPolygonal() { final List<Polygon> polygons = getPolygons(); final GeometryFactory geometryFactory = getGeometryFactory(); if (polygons.isEmpty()) { return geometryFactory.polygon(); } else { return geometryFactory.polygonal(polygons); } } /** * Gets the list of polygons formed by the polygonization. * @return a collection of {@link Polygon}s */ public List<Polygon> getPolygons() { polygonize(); return this.polygons; } /** * Performs the polygonization, if it has not already been carried out. */ private void polygonize() { // check if already computed if (this.polygons != null) { return; } this.polygons = new ArrayList<>(); // if no geometries were supplied it's possible that graph is null if (this.graph == null) { return; } this.dangles = this.graph.deleteDangles(); this.cutEdges = this.graph.deleteCutEdges(); final List<EdgeRing> edgeRingList = this.graph.getEdgeRings(); // Debug.printTime("Build Edge Rings"); List<EdgeRing> validEdgeRingList = new ArrayList<>(); this.invalidRingLines = new ArrayList<>(); if (this.isCheckingRingsValid) { findValidRings(edgeRingList, validEdgeRingList, this.invalidRingLines); } else { validEdgeRingList = edgeRingList; } // Debug.printTime("Validate Rings"); findShellsAndHoles(validEdgeRingList); assignHolesToShells(this.holeList, this.shellList); // Debug.printTime("Assign Holes"); this.polygons = new ArrayList<>(); for (final EdgeRing edgeRing : this.shellList) { final Polygon polygon = edgeRing.getPolygon(); this.polygons.add(polygon); } } /** * Allows disabling the valid ring checking, * to optimize situations where invalid rings are not expected. * <p> * The default is <code>true</code. * * @param isCheckingRingsValid true if generated rings should be checked for validity */ public void setCheckRingsValid(final boolean isCheckingRingsValid) { this.isCheckingRingsValid = isCheckingRingsValid; } }