/* Spatial Operations & Editing Tools for uDig * * Axios Engineering under a funding contract with: * Diputación Foral de Gipuzkoa, Ordenación Territorial * * http://b5m.gipuzkoa.net * http://www.axios.es * * (C) 2006, Diputación Foral de Gipuzkoa, Ordenación Territorial (DFG-OT). * DFG-OT agrees to license under Lesser General Public License (LGPL). * * 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; version 2.1 of the License. * * 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. */ package es.axios.lib.geometry.split; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import com.vividsolutions.jts.algorithm.CGAlgorithms; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateArrays; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geomgraph.DirectedEdge; import es.axios.lib.geometry.split.RingExtractor.ResultRingExtractor; import es.axios.udig.ui.commons.internal.i18n.Messages; /** * Performs a split of a LineString, MultiLineString, Polygon or MultiPolygon * using a provided LineString as cutting edge. * * @author Mauricio Pazos (www.axios.es) * @author Aritz Davila (www.axios.es) * @since 1.1.0 */ public class SplitStrategy { private static final Logger LOGGER = Logger.getLogger(SplitStrategy.class.getName()); /** The split line. */ private final LineString splitLine; /** Valid strategies for split geometries. */ private final Map<Class<?>, SpecificSplitOp> strategies; /** * Constructor for splitStrategy. * * @param splitLine * The split line. */ public SplitStrategy(final LineString splitLine) { assert splitLine != null : "should not be null"; //$NON-NLS-1$ this.splitLine = splitLine; this.strategies = new HashMap<Class<?>, SplitStrategy.SpecificSplitOp>( 4); this.strategies.put(LineString.class, new LineStringSplitter()); this.strategies.put(MultiLineString.class, new MultiLineStringSplitter()); this.strategies.put(Polygon.class, new PolygonSplitter()); this.strategies.put(MultiPolygon.class, new MultiPolygonSplitter()); } /** * @return the original split line */ public LineString getSplitLine() { return this.splitLine; } /** * @param geomToSplit * @return A list of the resultant geometries. */ public List<Geometry> split(final Geometry geomToSplit) { assert !((geomToSplit instanceof Point) || (geomToSplit instanceof MultiPoint)) : "cannot split point or multipoint"; //$NON-NLS-1$ Class<?> geometryClass = geomToSplit.getClass(); SpecificSplitOp splitOp = findSplitStrategy(geometryClass); UsefulSplitLineBuilder usefulSplitLineBuilder = UsefulSplitLineBuilder.newInstance(getSplitLine()); LOGGER.fine("SplitStrategy, geomToSplit: " + geomToSplit.toText()); //$NON-NLS-1$ LOGGER.fine("SplitStrategy, split line: " + usefulSplitLineBuilder.getOriginalSplitLine().toText()); //$NON-NLS-1$ splitOp.setSplitLine(usefulSplitLineBuilder); List<Geometry> splitResult = splitOp.split(geomToSplit); return splitResult; } /** * Check if it is a valid intersection between the original geometry and the * split line. * * Get the boundary of the polygon geometry and intersects it with the split * line. A valid split operation must fulfill the next: * * <pre> * * -The split line must intersect the polygon boundary at least at 2 * points. * -The line comes from outside the feature, intersects on one point a boundary (interior-exterior), * the line must have at least one point inside the feature, and then intersects again * the same boundary(interior-exterior). * </pre> * * @param geomToSplit * is a LineString, MultilineString, Polygon or MultiPolygon * @param splitInMapCrs * a LineString * @return True if it can split the originalGeometry. */ public boolean canSplit(Geometry geomToSplit) { assert geomToSplit != null : "the Geometry to split musn't be null"; //$NON-NLS-1$ Class<?> geometryClass = geomToSplit.getClass(); SpecificSplitOp splitOp = findSplitStrategy(geometryClass); UsefulSplitLineBuilder usefulSplitLineBuilder = UsefulSplitLineBuilder.newInstance(getSplitLine()); LOGGER.fine("SplitStrategy, geomToSplit: " + geomToSplit.toText()); //$NON-NLS-1$ LOGGER.fine("SplitStrategy, split line: " + usefulSplitLineBuilder.getOriginalSplitLine().toText()); //$NON-NLS-1$ splitOp.setSplitLine(usefulSplitLineBuilder); boolean bool = splitOp.canSplit(geomToSplit); return bool; } /** * Depending on the class of the Geometry to be split, found the * correspondent class responsible of doing the split operation. * * @param geomToSplitClass * Class of the geometry to be split. * @return The class responsible of doing split. */ private SpecificSplitOp findSplitStrategy(Class<?> geomToSplitClass) { assert strategies.containsKey(geomToSplitClass) : Messages.SplitStrategy_illegal_geometry + geomToSplitClass; return strategies.get(geomToSplitClass); } /** * Strategy object for split geometry, subclasses are targeted towards * specific kinds of geometry. * <p> * The GeometryCollectionSplitter will hold onto a LineString provided by * the user, and use it to break provided geometry one by one. */ private static interface SpecificSplitOp { /** * LineString used for splitting; as provided by the user. * * @param splitLine * LineString used by the split method to break up geometry * one by one. */ public void setSplitLine(final UsefulSplitLineBuilder splitLine); public boolean canSplit(Geometry geomToSplit); /** * Split the provided Geometry using the LineString provided by the * user. * * @param geomToSplit * Geometry to split using the user supplied lineString * @return A list of geometries containing the result geometries. */ public List<Geometry> split(Geometry geomToSplit); /** * Get the split line. * * @return The split line. */ public UsefulSplitLineBuilder getSplitLine(); } /** * Hold onto a LineString for use by subclasses. * */ private static abstract class AbstractSplitter implements SpecificSplitOp { private UsefulSplitLineBuilder usefulSplitLineBuilder; /** * Sets the split line. * * @param splitter * split line. */ public void setSplitLine(final UsefulSplitLineBuilder splitLine) { this.usefulSplitLineBuilder = splitLine; } /** * Return a copy of split line (defensive copy) * * @return The split line. */ public UsefulSplitLineBuilder getSplitLine() { return this.usefulSplitLineBuilder; } } /** * User the lineString provided by the user to break up lineStrings one by * one. */ private static class LineStringSplitter extends AbstractSplitter { /** * Split method used for lineStrings. * * @param geomToSplit * the {@link LineString} to be split. * @return the resultant geometry. */ public List<Geometry> split(Geometry geomToSplit) { LineString lineString = (LineString) geomToSplit; LineString splitLine = getSplitLine().getOriginalSplitLine(); Geometry result = lineString.difference(splitLine); List<Geometry> listLines = new ArrayList<Geometry>(); // for each geometry, add to the list. for (int i = 0; i < result.getNumGeometries(); i++) { listLines.add(result.getGeometryN(i)); } return listLines; } public boolean canSplit(Geometry lineString) { if (!(lineString instanceof LineString)){ throw new IllegalArgumentException("LineString is spected"); //$NON-NLS-1$ } LineString splitLine = getSplitLine().getOriginalSplitLine(); return splitLine.intersects(lineString) ; } } // end LineStringSplitter class /** * Strategy object for splitting (a geometry collection). * <p> * The GeometryCollectionSplitter will hold onto the SpecificcSplitOp in * order to hold onto the LineString provided by the user for splitting. * */ private static abstract class AbstractGeometryCollectionSplitter implements SpecificSplitOp { /** Used to split a single geometry */ private SpecificSplitOp splitter; private UsefulSplitLineBuilder splitLineBuilder; /** * Constructor. * * @param spliterOperation * Specific split operation class. */ private AbstractGeometryCollectionSplitter(SpecificSplitOp spliterOperation) { this.splitter = spliterOperation; } /** * Update the singlePartSplitter with the provided LineString. * * @param splitLine * LineString used by split method to split provided geometry * one by one */ public final void setSplitLine(UsefulSplitLineBuilder splitLine) { this.splitLineBuilder = splitLine; this.splitter.setSplitLine(splitLine); } /** * @return The split line. */ public UsefulSplitLineBuilder getSplitLine() { return this.splitLineBuilder; } /** * Split method for polygons and multiPolygons. * * @param geomToSplit * * @return The resultant geometries. */ public final List<Geometry> split(final Geometry geomToSplit) { final GeometryCollection coll = (GeometryCollection) geomToSplit; final int numParts = coll.getNumGeometries(); List<Geometry> result = new ArrayList<Geometry>(); for (int partN = 0; partN < numParts; partN++) { Geometry simplePartN = coll.getGeometryN(partN); // for multiGeometry, the geometry that is intersected, split // it, the one that isn't, add without changes. if (this.splitter.canSplit(simplePartN)) { List<Geometry> splittedPart = this.splitter.split(simplePartN); result.addAll(splittedPart); } else { result.add(simplePartN); } } return result; } public boolean canSplit(Geometry geomToSplit) { if( !((geomToSplit instanceof MultiPolygon) || (geomToSplit instanceof MultiLineString)) ){ throw new IllegalArgumentException("MultiPolygon or MultiLineString geometry is expected" ); //$NON-NLS-1$ } final GeometryCollection coll = (GeometryCollection) geomToSplit; final int numParts = coll.getNumGeometries(); for (int partN = 0; partN < numParts; partN++) { Geometry currentGeometry = coll.getGeometryN(partN); if (this.splitter.canSplit(currentGeometry)) { return true; } } return false; } /** * Build a geometry collection with the provided parts. * * @param gf * Provided geometry factory. * @param parts * Split parts. * @return The geometry collection with the geometries after being * split. */ protected abstract GeometryCollection buildFromParts(GeometryFactory gf, List<?> parts); } // end AbstractGeometryCollectionSplitter class /** * Class responsible for doing split of MultiLineString geometries. */ private static class MultiLineStringSplitter extends AbstractGeometryCollectionSplitter { /** * Default constructor. */ public MultiLineStringSplitter() { super(new LineStringSplitter()); } @Override protected GeometryCollection buildFromParts(GeometryFactory gf, List<?> parts) { LineString[] lines = parts.toArray(new LineString[parts.size()]); MultiLineString result = gf.createMultiLineString(lines); return result; } public boolean canSplit(Geometry multiLine) { if (!(multiLine instanceof MultiLineString)){ throw new IllegalArgumentException("LineString is spected"); //$NON-NLS-1$ } // lines doesn't needed to be checked. LineString splitLine = getSplitLine().getOriginalSplitLine(); return splitLine.intersects(multiLine) ; } } /** * Class responsible for doing split of MultiPolygon geometries. */ private static class MultiPolygonSplitter extends AbstractGeometryCollectionSplitter { /** * Default constructor. */ public MultiPolygonSplitter() { super(new PolygonSplitter()); } @Override protected GeometryCollection buildFromParts(GeometryFactory gf, List<?> parts) { Polygon[] polygons = parts.toArray(new Polygon[parts.size()]); MultiPolygon result = gf.createMultiPolygon(polygons); return result; } // public boolean canSplit(Geometry mutiPolygon) { // if(!(mutiPolygon instanceof MultiPolygon)){ // throw new IllegalArgumentException("MultiPolygon geometry is expected"); //$NON-NLS-1$ // } // UsefulSplitLineBuilder splitBuilder = this.getSplitLine(); // // return SplitUtil.canSplitPolygon(mutiPolygon, splitBuilder); // } } /** * Responsible for splitting a single polygon; polygon may be split into * several parts (or a hole may be formed). * * Polygon Strategy: * <ul> * <li>1- Build a graph with all the edges and nodes from the intersection * between the polygon and the line * <li>2- Go through the graph, building the new polygons. * <li>2.1- Get a non-visited edge from the intersection with the line, and * go through it. * <li>2.2- Get the next edges, if exist an intersection with other edges, * take the one with less angle, CW direction. If not, take the next. Add * the edge. * <li>2.3- Do the same with the non-visited shell edges and holes. The next * edges will be calculated with the less angles in CCW direction. * <li>2.4- Analysis. Once all the rings are created, check that all * intersection edges (those edges belong to 2 features) are visited twice. * </ul> * */ private static class PolygonSplitter extends AbstractSplitter { private List<LinkedHashSet<SplitEdge>> interiorRings = new ArrayList<LinkedHashSet<SplitEdge>>(); private GeometryFactory geometryFactory; /** * Split the provided geometry. * * @param geomToSplit * Polygon geometry to split. * * @return split geometry or null. */ public List<Geometry> split(Geometry geomToSplit) { assert geomToSplit instanceof Polygon; final Polygon polygon = (Polygon) geomToSplit; final List<Geometry> splitPolygon; LineString splitLine = getSplitLine().getOriginalSplitLine(); if (SplitUtil.isClosedLine(splitLine)) { splitPolygon = splitPolygonClosedLines(polygon); } else { splitPolygon = splitPolygon(polygon); } return splitPolygon; } /** * Split the provided polygon. * * @param aPolygon * The source polygon to split. * @return Return a single polygon or multiPolygon depending on how the * split went. */ private List<Geometry> splitPolygon(final Polygon aPolygon) { this.geometryFactory = aPolygon.getFactory(); final UsefulSplitLineBuilder splitLine = getSplitLine(); SplitGraphBuilder graphBuilder = new SplitGraphBuilder(aPolygon, splitLine); graphBuilder.build(); final Graph graph = graphBuilder.getResultantGraph(); // fill the list with all the directedEdge that are forward // direction. List<DirectedEdge> directedEdgeList = new LinkedList<DirectedEdge>(); Iterator<?> it = graph.getEdgeEnds().iterator(); while (it.hasNext()) { DirectedEdge de = (DirectedEdge)it.next(); if (de.isForward()) { directedEdgeList.add(de); } } List<LinkedHashSet<SplitEdge>> allTheRings = new ArrayList<LinkedHashSet<SplitEdge>>(); // Get a non-visited edge from the intersection, and go through // it with building rings. Get an edge from the shell. // After that, if still exist and edge from the holes that wasn't // visited, go through it. List<LinkedHashSet<SplitEdge>> ringList = new ArrayList<LinkedHashSet<SplitEdge>>(); ringList = findRing(directedEdgeList); allTheRings.addAll(ringList); // analyze the graph assuring all the interior edges has been taken // into account twice. ringList = checkRings(directedEdgeList); allTheRings.addAll(ringList); // get the non-split edges. List<LinearRing> nonSplitHoles = getNonSplitHoles( graphBuilder); // Build the resultant polygon. List<Geometry> resultingPolygons = buildSimplePolygons(allTheRings, nonSplitHoles); return resultingPolygons; } /** * Get the non split holes. The interior rings are calculated while * finding rings, and also while construction the graph. * * @param graph * The split graph. * @return A list with the holes that weren't modified. */ private List<LinearRing> getNonSplitHoles( SplitGraphBuilder graph) { final List<LinearRing> nonSplitHoles = new ArrayList<LinearRing>(); for (LinkedHashSet<SplitEdge> edgeList : this.interiorRings) { List<LinearRing> rings = buildLinearRing(edgeList); nonSplitHoles.addAll(rings); } Set<LinearRing> holes = graph.getNonSplitRings(); for (LinearRing hole : holes) { nonSplitHoles.add(hole); } return nonSplitHoles; } /** * Go through the graph and find the created rings, those rings will be * the new features. Start finding a ring from an intersection edge if * it isn't visited. Then go through the shell edges. At this point, the * edges that form the new polygons are created, go through the hole * edges that weren't visited, those holes will be interior rings that * aren't modified. At the end, all the shell edges will be covered. * * @param directedEdgeList * List with all the edges from the graph that are forward. * @return A list containing rings, those rings will be the new * pre-polygons. */ private List<LinkedHashSet<SplitEdge>> findRing(List<DirectedEdge> directedEdgeList) { List<LinkedHashSet<SplitEdge>> edgeList = new ArrayList<LinkedHashSet<SplitEdge>>(); // first, find the rings provided by the intersection edges. edgeList =findIntersectionRings(directedEdgeList,edgeList); // go through shell DirectEdge edgeList=findShellRings(directedEdgeList,edgeList); // then, find the rings taken into account only the holeEdges. storeHolesRings(directedEdgeList); return edgeList; } /** * Store the rings obtained from the holes edges in the this.interiorRings list. * * @param directedEdgeList */ private void storeHolesRings( List<DirectedEdge> directedEdgeList ) { for (DirectedEdge de : directedEdgeList) { DirectedEdge startEdge = de; SplitEdge edge = (SplitEdge) startEdge.getEdge(); // get one of the holesEdge but calculate the angle seeking // which edge is nearest at CCW direction. if (!edge.isVisited() && edge.isHoleEdge()) { // check that the edge is only a hole // edge and not intersection-hole edge. if (!isOnlyHoleEdge(edge, directedEdgeList)) { // continue and pick other edge. continue; } LinkedHashSet<SplitEdge> interiorRings = builtRing(startEdge, CGAlgorithms.COUNTERCLOCKWISE); this.interiorRings.add(interiorRings); } } } /** * Find the shell rings. * * @param directedEdgeList * @param edgeList * @return The edgeList with the "shell rings" */ private List<LinkedHashSet<SplitEdge>> findShellRings( List<DirectedEdge> directedEdgeList, List<LinkedHashSet<SplitEdge>> edgeList) { for (DirectedEdge de : directedEdgeList) { DirectedEdge startEdge = de; SplitEdge edge = (SplitEdge) startEdge.getEdge(); // Starts from a shell edge if (!edge.isVisited() && edge.isShellEdge()) { edgeList.add(builtRing(startEdge, CGAlgorithms.COUNTERCLOCKWISE)); } } return edgeList; } /** * Find the rings provided by the intersection edges. * * @param directedEdgeList * @param edgeList * @return The edgeList with the "intersection rings" */ private List<LinkedHashSet<SplitEdge>> findIntersectionRings( List<DirectedEdge> directedEdgeList, List<LinkedHashSet<SplitEdge>> edgeList) { for (DirectedEdge de : directedEdgeList) { DirectedEdge startEdge = de; SplitEdge edge = (SplitEdge) startEdge.getEdge(); // Get one of the intersection edges, and find ring if (!edge.isVisited() && edge.isIntersectionEdge()) { // check that the edge is only an intersection // edge and not intersection-hole edge. if (!isOnlyIntersectionEdge(edge, directedEdgeList)) { // continue and pick other edge. continue; } // get the ring. LinkedHashSet<SplitEdge> result = builtRing(startEdge, CGAlgorithms.CLOCKWISE); // It will return null if while building the ring, not all // the edges are an intersection edges. if (result != null) { edgeList.add(result); } } } return edgeList; } /** * Check if the given edge is unique edge, that means that there isn't * an intersection edge with the same coordinates. * * @param edge * Edge to find. * @param directedEdgeList * List where to find the edge. * @return True if it is unique edge. */ private boolean isOnlyHoleEdge(SplitEdge edge, List<DirectedEdge> directedEdgeList) { // compare with the intersection edges. // go through the DirectEdge for (DirectedEdge direct : directedEdgeList) { SplitEdge possibleEdge = (SplitEdge) direct.getEdge(); if (possibleEdge.isIntersectionEdge()) { if (sameCoordinates(edge.getCoordinates(), possibleEdge.getCoordinates())) { return false; } } } return true; } /** * Check if the given edge is unique edge, that means that there isn't a * hole edge with the same coordinates. * * @param edge * Edge to find. * @param directedEdgeList * List where to find the edge. * @return True if only exist this intersection edge, any hole edge has * the same coordinates. */ private boolean isOnlyIntersectionEdge(SplitEdge edge, List<DirectedEdge> directedEdgeList) { // compare with the holes edges. // go through the DirectEdge for (DirectedEdge direct : directedEdgeList) { SplitEdge possibleHole = (SplitEdge) direct.getEdge(); if (possibleHole.isHoleEdge()) { if (sameCoordinates(edge.getCoordinates(), possibleHole.getCoordinates())) { return false; } } } return true; } /** * Assure that all the intersection rings have been taken into account * twice. Each edge belong to 2 features, so it must be visited twice. * If it was not visited twice, visit it again. * * @param directedEdgeList * List with all the edges from the graph that are forward. * @return A list containing rings, those rings will be the new * polygons. */ private List<LinkedHashSet<SplitEdge>> checkRings( List<DirectedEdge> directedEdgeList) { List<LinkedHashSet<SplitEdge>> edgeList = new ArrayList<LinkedHashSet<SplitEdge>>(); // go through the DirectEdge for (DirectedEdge de : directedEdgeList) { DirectedEdge startEdge = de; SplitEdge edge = (SplitEdge) startEdge.getEdge(); // interior edges must be visited twice, assure all of them // were processed 2 times. if (!edge.isTwiceVisited() && edge.isIntersectionEdge()) { // if the intersection edge is also a hole edge (there are 2 // edges, one belong to an intersection edge and the other // to the hole, but both of them have the same // coordinates.), do nothing with it. if (!intersectionAndHoleSame(directedEdgeList)) { // get the right direction. // test with CW direction int direction = testDirection(startEdge, CGAlgorithms.CLOCKWISE); edgeList.add(builtRing(startEdge, direction)); } } } return edgeList; } /** * Seek out if there exist an intersection edge that is exactly as a * hole edge. * * @param directedEdgeList * The list with the edges. * @return True if exist an intersect edge that also is a hole edge. */ private boolean intersectionAndHoleSame( List<DirectedEdge> directedEdgeList) { // go through the DirectEdge for (DirectedEdge de : directedEdgeList) { SplitEdge edge = (SplitEdge) de.getEdge(); if (edge.isIntersectionEdge()) { // compare with the holes edges. // go through the DirectEdge for (DirectedEdge direct : directedEdgeList) { SplitEdge possibleHoleOrShell = (SplitEdge) direct .getEdge(); if (possibleHoleOrShell.isHoleEdge() || possibleHoleOrShell.isShellEdge()) { if (sameCoordinates(edge.getCoordinates(), possibleHoleOrShell.getCoordinates())) { return true; } } } } } return false; } /** * check the coordinates are the same, that means, if the coordinate 1 * equals to the coordinate 2 from the hole, then, the coordinate 2 must * equal the coordinate 1 from the hole. * * @param intersection * Array containing coordinates from the intersection. * @param hole * Array containing coordinates from the hole. * @return True if a coordinate from the intersection or the hole exists * on the other side (hole/intersection). */ private boolean sameCoordinates(Coordinate[] intersection, Coordinate[] hole) { // check the coordinates are the same, that means, if the coordinate // 1 equals to the coordinate 2 from the hole, then, the coordinate // 2 must equal the coordinate 1 from the hole. // the next code could be merged into one single 'if', but this way // is easier to read it. if (intersection[0].equals(hole[0]) && intersection[1].equals(hole[1])) { return true; } if (intersection[1].equals(hole[0]) && intersection[0].equals(hole[1])) { return true; } if (intersection[0].equals(hole[1]) && intersection[1].equals(hole[0])) { return true; } if (intersection[1].equals(hole[1]) && intersection[0].equals(hole[0])) { return true; } return false; } /** * Get the next edges, if exists an intersection with other edges, take * the one with less angle in the given direction. If not, take the * next. Add the edge. * * @param startEdge * The directedEdge from which start. * @param direction * CCW or CW direction. * @return A list with a ring of edges. */ private LinkedHashSet<SplitEdge> builtRing( final DirectedEdge startEdge, final int direction) { DirectedEdge currentDirectedEdge = startEdge; DirectedEdge nextDirectedEdge = null; LinkedHashSet<SplitEdge> ring = new LinkedHashSet<SplitEdge>(); while (!edgesAreEqual((SplitEdge) startEdge.getEdge(), nextDirectedEdge)) { SplitEdge currentEdge = (SplitEdge) currentDirectedEdge .getEdge(); ring.add(currentEdge); currentEdge.countVisited(); DirectedEdge sym = currentDirectedEdge.getSym(); SplitGraphNode endNode = (SplitGraphNode) sym.getNode(); SplitEdgeStar nodeEdges = (SplitEdgeStar) endNode.getEdges(); nextDirectedEdge = nodeEdges.findClosestEdgeInDirection(sym, direction); assert nextDirectedEdge != null; currentDirectedEdge = nextDirectedEdge; } return ring; } /** * This function will test if the given direction will form new rings, * or will form repeated rings. It depends on the line direction, so * first test with CW direction, if the new ring isn't repeated, thats * ok. * * @param startEdge * The directedEdge from which start. * @param direction * CCW or CW direction. * @return The correct direction to form the new ring. */ private int testDirection(final DirectedEdge startEdge, final int direction) { DirectedEdge currentDirectedEdge = startEdge; DirectedEdge nextDirectedEdge = null; int finalDirection = direction; while (!edgesAreEqual((SplitEdge) startEdge.getEdge(), nextDirectedEdge)) { SplitEdge currentEdge = (SplitEdge) currentDirectedEdge .getEdge(); // if thats true, it means that ring already exist, so return // the opposite direction. if (currentEdge.isTwiceVisited()) { finalDirection = (direction == CGAlgorithms.CLOCKWISE) ? CGAlgorithms.COUNTERCLOCKWISE : CGAlgorithms.CLOCKWISE; break; } DirectedEdge sym = currentDirectedEdge.getSym(); SplitGraphNode endNode = (SplitGraphNode) sym.getNode(); SplitEdgeStar nodeEdges = (SplitEdgeStar) endNode.getEdges(); nextDirectedEdge = nodeEdges.findClosestEdgeInDirection(sym, direction); assert nextDirectedEdge != null; currentDirectedEdge = nextDirectedEdge; } return finalDirection; } /** * Check if those edges are equal. * * @param startEdge * Start directedEdge * @param nextDirectedEdge * The next one that could be the same as the start. * @return True if they are equal. */ private boolean edgesAreEqual(SplitEdge startEdge, DirectedEdge nextDirectedEdge) { if (nextDirectedEdge == null) { return false; } return startEdge.equalsCoordinates(nextDirectedEdge.getEdge()); } /** * With the provided rings and the non-split holes, build polygons. * * @param allRings * The ring of the polygon/s. * @param nonSplitHoles * Non-split holes. * @return A list with all the built polygons. */ private List<Geometry> buildSimplePolygons( List<LinkedHashSet<SplitEdge>> allRings, List<LinearRing> nonSplitHoles) { List<Geometry> polygons = new ArrayList<Geometry>(allRings.size()); for (LinkedHashSet<SplitEdge> edgeList : allRings) { // boolean polyIsHole = false; Polygon poly = buildPolygon(edgeList); Geometry result = poly; for (LinearRing holeRing : nonSplitHoles) { // if it contain a ring, make the difference for creating a // hole. if (holeRing.within(poly)) { Geometry hole = this.geometryFactory.createPolygon( holeRing, null); result = result.difference(hole); } } // don't add repeated polygons. boolean repeated = false; for (Geometry insertedPol : polygons) { if (result.equals(insertedPol)) { repeated = true; } } if (!repeated) { // add each polygon. There could be more than one result // only after applying difference with a hole. for (int i = 0; i < result.getNumGeometries(); i++) { polygons.add(result.getGeometryN(i)); } } } return polygons; } /** * Build a polygon from a edgeList. This edgeList is a ring of edges. * * @param edgeList * A ring of edges. * @param gf * The geometry factory. * @return The resultant polygon. */ private Polygon buildPolygon(LinkedHashSet<SplitEdge> edgeList) { List<Coordinate> coords = new LinkedList<Coordinate>(); Coordinate[] lastCoordinates = null; for (SplitEdge edge : edgeList) { Coordinate[] coordinates = edge.getCoordinates(); if (lastCoordinates != null) { Coordinate endPoint = lastCoordinates[lastCoordinates.length - 1]; Coordinate startPoint = coordinates[0]; if (!endPoint.equals2D(startPoint)) { coordinates = CoordinateArrays.copyDeep(coordinates); CoordinateArrays.reverse(coordinates); } } lastCoordinates = coordinates; for (int i = 0; i < coordinates.length; i++) { Coordinate coord = coordinates[i]; coords.add(coord); } } // shell coordinates. Coordinate[] shellCoords = new Coordinate[coords.size()]; coords.toArray(shellCoords); shellCoords = CoordinateArrays.removeRepeatedPoints(shellCoords); // hole/s rings. List<LinearRing> holeList = new ArrayList<LinearRing>(); LinearRing[] ring; // even if it hasn't self-intersections, do the first so // LinearRing[] result is filled with values. do { ring = extractInteriorRing(shellCoords); // result[0] is the shell. // result[1] if exists, is a hole. if (ring[1] != null) { holeList.add(ring[1]); } shellCoords = new Coordinate[ring[0].getCoordinates().length]; shellCoords = ring[0].getCoordinates(); } while (hasSelfIntersection(shellCoords)); Polygon poly; if (holeList.isEmpty()) { poly = this.geometryFactory.createPolygon(ring[0], (LinearRing[]) null); } else { LinearRing[] holes = holeList.toArray(new LinearRing[holeList .size()]); poly = this.geometryFactory.createPolygon(ring[0], holes); } return poly; } /** * Check the coordinates seeking for self-intersection, that means, seek * for repeated coordinates. * * @param shellCoords * Shell coordinates off the polygon. * @return True if it has self-intersection. */ private boolean hasSelfIntersection(Coordinate[] shellCoords) { // check if it has self-intersections for (int i = 1; i < shellCoords.length; i++) { Coordinate actualCoord = shellCoords[i]; for (int j = i + 1; j < shellCoords.length - 1; j++) { Coordinate selfIntersectionCoord = shellCoords[j]; if (actualCoord.equals2D(selfIntersectionCoord)) { // here we have a self intersection coordinate. return true; } } } return false; } /** * Go through the shell coordinates seeking for self-intersections and * extract them. Then those extracted rings will be inserted as holes of * the polygon. * * @param shellCoords * Shell coordinates off the polygon. * @return 2 linearRing, one will be the shell and the other a hole if * exists. */ private LinearRing[] extractInteriorRing(Coordinate[] shellCoords) { // check for self-intersection // the shell coordinates can't have self-intersection, if a // self-intersection is found, the coordinates between then will // form an interior ring. // take into account that the first and last coordinate are the // same. int selfIntersectionStart = -1; int selfIntersectionEnd = -1; for (int i = 1; i < shellCoords.length && selfIntersectionStart == -1; i++) { Coordinate actualCoord = shellCoords[i]; for (int j = i + 1; j < shellCoords.length - 1; j++) { Coordinate selfIntersectionCoord = shellCoords[j]; if (actualCoord.equals2D(selfIntersectionCoord)) { // here we have a self intersection coordinate. selfIntersectionStart = i; selfIntersectionEnd = j; break; } } } LinearRing[] shellAndHole = new LinearRing[2]; List<Coordinate> shell = new LinkedList<Coordinate>(); List<Coordinate> hole = new LinkedList<Coordinate>(); for (int j = 0; j < shellCoords.length; j++) { // add the actual shell coordinate shell.add(shellCoords[j]); if (j == selfIntersectionStart) { // retrieve the interior ring. for (int i = selfIntersectionStart; i <= selfIntersectionEnd; i++) { hole.add(shellCoords[i]); } // move the cursor to continue after the // self-intersection coordinate j = selfIntersectionEnd; } } shellAndHole[0] = this.geometryFactory.createLinearRing(shell .toArray(new Coordinate[shell.size()])); if (hole.size() != 0) { shellAndHole[1] = this.geometryFactory.createLinearRing(hole .toArray(new Coordinate[hole.size()])); } else { shellAndHole[1] = null; } return shellAndHole; } /** * From those interior rings (holes), build linearRings. Those rings * will be the new split features, or an existent ring that has not been * modified. * * @param edgeList * List of splitEdges. * @return The linearRing of the holes. */ private List<LinearRing> buildLinearRing( LinkedHashSet<SplitEdge> edgeList) { List<Coordinate> coords = new ArrayList<Coordinate>(); Coordinate[] lastCoordinates = null; for (SplitEdge edge : edgeList) { Coordinate[] coordinates = edge.getCoordinates(); if (lastCoordinates != null) { Coordinate endPoint = lastCoordinates[lastCoordinates.length - 1]; Coordinate startPoint = coordinates[0]; if (!endPoint.equals2D(startPoint)) { coordinates = CoordinateArrays.copyDeep(coordinates); CoordinateArrays.reverse(coordinates); } } lastCoordinates = coordinates; for (int i = 0; i < coordinates.length; i++) { Coordinate coord = coordinates[i]; coords.add(coord); } } Coordinate[] shellCoords = new Coordinate[coords.size()]; coords.toArray(shellCoords); shellCoords = CoordinateArrays.removeRepeatedPoints(shellCoords); // hole/s rings. List<LinearRing> holeList = new ArrayList<LinearRing>(); LinearRing[] result; // even if it hasn't self-intersections, do the first so // LinearRing[] result is filled with values. do { // shellCoords could have nested rings(self-intersected), so // extract them. // find which one has self-intersections. result = extractInteriorRing(shellCoords); if (hasSelfIntersection(result[0].getCoordinates())) { // the remaining hole is on result[0] // result[1] if exists, is a nested hole, but now is // separate. if (result[1] != null) { holeList.add(result[1]); } // result[0] is the remaining hole. shellCoords = new Coordinate[result[0].getCoordinates().length]; shellCoords = result[0].getCoordinates(); } else if (result[1] != null && hasSelfIntersection(result[1].getCoordinates())) { // the remaining hole is on result[1] // result[0] if exists, is a nested hole, but now is // separate. if (result[0] != null) { holeList.add(result[0]); } // result[1] is the remaining hole. shellCoords = new Coordinate[result[1].getCoordinates().length]; shellCoords = result[1].getCoordinates(); } else { // no one has intersections, so fill the shellCoords with // result[0] or result[1], it doesn't matter. shellCoords = new Coordinate[result[0].getCoordinates().length]; shellCoords = result[0].getCoordinates(); } } while (hasSelfIntersection(shellCoords)); // add the last holes, result[0] and result[1] if exists. holeList.add(result[0]); if (result[1] != null) { holeList.add(result[1]); } return holeList; } /** * <p> * * <pre> * Split operation for polygons using closed lines. * First extract the rings and the remaining line from the * closed line with the aid of the {@link RingExtractor}. * Second make the operation using {@link SplitClosedLines}. * </pre> * * </p> * * @param polygon * Polygon that will suffer the split operation. * @return List with the resultant polygons. */ private List<Geometry> splitPolygonClosedLines(final Polygon polygon) { LineString splitLine = getSplitLine().getOriginalSplitLine(); RingExtractor ringExtractor = new RingExtractor(splitLine); ResultRingExtractor data = ringExtractor.processExtraction(); SplitClosedLines closedLines = new SplitClosedLines(data); return closedLines.runClosedLineSplit(polygon); } public boolean canSplit(Geometry polygon) { if(!(polygon instanceof Polygon)){ throw new IllegalArgumentException("Polygon geometry is expected"); //$NON-NLS-1$ } UsefulSplitLineBuilder splitBuilder = this.getSplitLine(); return SplitUtil.canSplitPolygon(polygon, splitBuilder); } } }