/* Spatial Operations & Editing Tools for uDig * * Axios Engineering under a funding contract with: * Wien Government * * http://wien.gov.at * http://www.axios.es * * (C) 2009, Vienna City - Municipal Department of Automated Data Processing, * Information and Communications Technologies. * Vienna City 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.List; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; 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.MultiPolygon; import com.vividsolutions.jts.geom.Polygon; import es.axios.lib.geometry.util.GeometryUtil; /** * <p> * * <pre> * Strategy class that will add a vertex to a geometry border. * The following conditions must be fulfilled: * -The border must touches with a geometry that has been split. * -The same split line must intersect the * * Resume: * -Get the border of the geometry. * -Analyze the border and get the intersection between * a geometry and a line, and add that point as vertex to the geometry. * -Do the same for each hole, if the geometry has holes. * </pre> * * </p> * * @author Mauricio Pazos (www.axios.es) * @author Aritz Davila (www.axios.es) */ public final class VertexStrategy { /** * <p> * * <pre> * Responsible of creating a new geometry that will be the given geometry * with an extra vertex. That vertex will be the point where the given * geometry and the line intersect. * * </pre> * * </p> * * @param geomToAddVertex * The geometry that will be modified. * @param line * The split line. * @param geomNeighbor * The neighbor geometry list, those one had been modified by the * split operation. * @return The geometry with the vertex added. */ public static Geometry addIntersectionVertex(Geometry geomToAddVertex, Geometry line, List<Geometry> geomNeighbor) { assert line instanceof LineString || line instanceof MultiLineString : "Split line must be a line."; //$NON-NLS-1$ assert geomToAddVertex instanceof Polygon || geomToAddVertex instanceof LineString || geomToAddVertex instanceof MultiPolygon || geomToAddVertex instanceof MultiLineString : "Input geometry has unexpected class."; //$NON-NLS-1$ Geometry result = null; if (geomToAddVertex instanceof Polygon || geomToAddVertex instanceof MultiPolygon) { Polygon polygon; // cast to polygon, we know that geomToAddVertex only has one // geometry. if (geomToAddVertex instanceof MultiPolygon) { MultiPolygon mpolygon = (MultiPolygon) geomToAddVertex; polygon = (Polygon) mpolygon.getGeometryN(0); } else { polygon = (Polygon) geomToAddVertex; } result = addIntersectionVertexToPolygon(polygon, line, geomNeighbor); } else if (geomToAddVertex instanceof LineString || geomToAddVertex instanceof MultiLineString) { result = addIntersectionVertexToLine(geomToAddVertex, line); } return result; } /** * <p> * * <pre> * Responsible of creating a new geometry that will be the given geometry * with an extra vertex. That vertex will be the point where the passed * geometry and the line intersects. * * Get the coordinates of the exterior boundary of the geometry to add vertex. * The coordinates are returned in an sorted collection. Start * creating a LineSegment between the first and the second coordinates, then * check if that lineSegment intersect with our line. * If it doesn't intersect, add the coordinate to the list, and if it intersects * add to the list. * * Get the coordinates of the interior boundary (holes) if it has. * With each hole do the same as done with the exterior boundary. * * </pre> * * </p> * * @param polygonToAddVertex * The polygon geometry. * @param line * The split line. * @param geomNeighborList * The list with neighbor geometries. * @return the geomToAddVertex with the intersection vertex added. */ private static Geometry addIntersectionVertexToPolygon( Geometry polygonToAddVertex, Geometry line, List<Geometry> geomNeighborList) { Geometry result = null; Geometry[] holes = null; LinearRing[] holesRing = null; List<Coordinate> shellCoordinates = null; List<LinearRing> holesList = null; GeometryFactory fc = polygonToAddVertex.getFactory(); // cast to polygon and get the exterior boundary. Polygon polygonGeom = (Polygon) polygonToAddVertex; Geometry boundary = polygonGeom.getExteriorRing(); Coordinate[] shellClosed = null; Coordinate[] boundaryCoordinates = boundary.getCoordinates(); // for each neighbor for (Geometry neighbor : geomNeighborList) { if (boundary.touches(neighbor)) { // store the modified boundaryCoordinates for add more vertex if // there are. shellCoordinates = addIntersection(boundaryCoordinates, line, fc, neighbor); boundaryCoordinates = shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]); shellClosed = boundaryCoordinates; } else { shellClosed = boundaryCoordinates; } } holes = getInteriorHoles(polygonGeom); // if not null, the geometry has holes. if (holes.length > 0) { holesList = addIntersectionToHoles(holes, line, fc, geomNeighborList); holesRing = holesList.toArray(new LinearRing[holesList.size()]); } // form a closed linearRing. shellClosed = GeometryUtil.closeGeometry(shellClosed); LinearRing shellRing = fc.createLinearRing(shellClosed); result = fc.createPolygon(shellRing, holesRing); return result; } /** * <p> * * <pre> * Responsible of creating a new geometry that will be the given geometry * with an extra vertex. That vertex will be the point where the passed * geometry and the line intersects. * * Get the coordinates of the LineString to add vertex. * The coordinates are returned in an sorted collection. Start * creating a LineSegment between the first and the second coordinates, then * check if that lineSegment intersect with our line. * If it doesn't intersect, add the coordinate to the list, and if it intersects * add to the list. * * </pre> * * </p> * * @param lineToAddVertex * The line to add vertex. * @param line * The split line. * @return the geomToAddVertex with the intersection vertex added. */ private static Geometry addIntersectionVertexToLine(Geometry lineToAddVertex, Geometry line) { Geometry result = null; List<Coordinate> shellCoordinates = null; GeometryFactory fc = lineToAddVertex.getFactory(); Geometry boundary = lineToAddVertex.getBoundary(); Coordinate[] boundaryCoordinates = boundary.getCoordinates(); // add the intersection vertex to the shellCoordinates. shellCoordinates = addIntersection(boundaryCoordinates, line, fc, line); Coordinate[] shellClosed = shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]); shellClosed = GeometryUtil.closeGeometry(shellClosed); result = fc.createLineString(shellClosed); return result; } /** * For each hole, get it and if it touches with the neighbor geometry add * the intersection vertex if exist to the hole boundary. Then add this hole * to the holeRing list. * * @param holes * Array of holes. * @param line * The split line. * @param fc * Geometry factory. * @param geomNeighborList * The list with neighbor geometries. * @return A list containing the modified holes. */ private static List<LinearRing> addIntersectionToHoles( Geometry[] holes, Geometry line, GeometryFactory fc, List<Geometry> geomNeighborList) { List<LinearRing> holesRing = new ArrayList<LinearRing>(); List<Coordinate> holeCoordinates = null; for (int i = 0; i < holes.length; i++) { Geometry eachHole = fc.createLinearRing(holes[i].getCoordinates()); Coordinate[] boundaryCoordinates = eachHole.getCoordinates(); Coordinate[] holeClosed = null; for (Geometry neighbor : geomNeighborList) { // if it touch, add the intersection vertex. if (eachHole.touches(neighbor)) { // store the modified boundaryCoordinates for add more // vertex if there are. holeCoordinates = addIntersection(boundaryCoordinates, line, fc, neighbor); boundaryCoordinates = holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]); boundaryCoordinates = GeometryUtil.closeGeometry(boundaryCoordinates); holeClosed = boundaryCoordinates; } else { holeClosed = boundaryCoordinates; } } holesRing.add(fc.createLinearRing(holeClosed)); } return holesRing; } /** * Go through boundaryCoordinates and add a coordinate were the split line * intersect with the boundaryCoordinates. * * @param boundaryCoordinates * The boundary of a geometry, could be the exterior or interior * boundary. * @param line * The split line. * @param fc * The geometry factory. * @param neighbor * The neighbor. The new point will touch this geometry. * @return An array list with the coordinates. */ private static List<Coordinate> addIntersection(Coordinate[] boundaryCoordinates, Geometry line, GeometryFactory fc, Geometry neighbor) { List<Coordinate> resultCoordinates = new ArrayList<Coordinate>(); int i = 0; resultCoordinates.add(boundaryCoordinates[i]); for (i = 0; i < boundaryCoordinates.length - 1; i++) { // Create the line segment between one coordinate and the next one. Coordinate[] segmentCoordinates = new Coordinate[2]; segmentCoordinates[0] = boundaryCoordinates[i]; segmentCoordinates[1] = boundaryCoordinates[i + 1]; LineString segment = fc.createLineString(segmentCoordinates); // if intersect, this is the vertex we need to add, so add it to the // result coordinates list. if (segment.intersects(line)) { Geometry point = segment.intersection(line); Geometry[] sorted = sortPoints(point, segment); // add all the points. for (int j = 0; j < sorted.length; j++) { resultCoordinates = addPoint(segment, neighbor, sorted[j], resultCoordinates); } } // Don't add existing vertex twice if (!resultCoordinates.contains(boundaryCoordinates[i + 1])) { resultCoordinates.add(boundaryCoordinates[i + 1]); } } return resultCoordinates; } /** * Will return the coordinates sorted, that relation depends on the * lineString direction and the coordinates. Calculate which coordinate is * the nearest to the beginning of the segment until the array is sorted. * * @param point * @param segment * @return An array of points, all of them sorted respect the segment * orientation. */ private static Geometry[] sortPoints(Geometry point, LineString segment) { Geometry[] sorted = new Geometry[point.getNumGeometries()]; // fill the array for (int i = 0; i < point.getNumGeometries(); i++) { sorted[i] = point.getGeometryN(i); } Geometry temp = null; double dist1; double dist2; // sort the array for (int i = sorted.length - 1; i > 0; i--) { for (int j = 0; j < i; j++) { Coordinate firstLineCoord = segment.getCoordinateN(0); Coordinate secondLineCoord = segment.getCoordinateN(1); Coordinate firstPoint = sorted[j].getCoordinate(); Coordinate secondPoint = sorted[j + 1].getCoordinate(); // if the segment isn't vertical // calculate the distance of each point respect the beginning of // the segment. if ((firstLineCoord.x - secondLineCoord.x) != 0) { dist1 = firstLineCoord.x - firstPoint.x; dist2 = firstLineCoord.x - secondPoint.x; } else { dist1 = firstLineCoord.y - firstPoint.y; dist2 = firstLineCoord.y - secondPoint.y; } if (Math.abs(dist1) > Math.abs(dist2)) { temp = sorted[j]; sorted[j] = sorted[j + 1]; sorted[j + 1] = temp; } } } return sorted; } /** * Add a vertex to the coordinate list if the segment intersect with the * neighbor and if the segment with the neighbor intersection has a * dimension bigger than 0. * * @param segment * The analyzed segment. * @param neighbor * The neighbor boundary. * @param point * The intersection point between the segment and the boundary. * @param resultCoordinates * The list with the geomToAddVertex coordinates. * @return The list with geomToAddVertex coordinates with/without the added * vertex. */ private static List<Coordinate> addPoint( Geometry segment, Geometry neighbor, Geometry point, List<Coordinate> resultCoordinates) { // if the intersection dimension is 1, the segment and // the neighbor boundary are touching. if (segment.intersects(neighbor) && (segment.intersection(neighbor).getDimension() > 0)) { Coordinate coord = point.getCoordinate(); // Don't add existing vertex twice if (!resultCoordinates.contains(coord)) { resultCoordinates.add(coord); } } return resultCoordinates; } /** * If the geometry has holes, will return and array of geometries with all * the holes. If it hasn't holes will return an empty array. * * * @param geomToAddVertex * The geometry to analyze * @return Array with holes if there are, otherwise an empty array. */ private static Geometry[] getInteriorHoles(Geometry geomToAddVertex) { Geometry[] holes = new Geometry[0]; // get the interior rings. Polygon polygon = (Polygon) geomToAddVertex; holes = new Geometry[polygon.getNumInteriorRing()]; for (int j = 0; j < polygon.getNumInteriorRing(); j++) { Geometry interiorRing = polygon.getInteriorRingN(j); holes[j] = interiorRing; } return holes; } }