/* 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 licence 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.udig.ui.editingtools.precisionparallels.internal; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; 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.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geomgraph.Quadrant; import com.vividsolutions.jts.operation.polygonize.Polygonizer; import es.axios.lib.geometry.util.GeometryUtil; import es.axios.udig.ui.editingtools.precisionparallels.internal.QuadrantAnalyzer.AnalyzerPosition; /** * <p> * * <pre> * This class will analyze the orientation of the source geometry and the * orientation of the output geometry, and based on the differences between the * results, will discard some points of the output geometry. * </pre> * * </p> * * @author Aritz Davila (www.axios.es) * @author Mauricio Pazos (www.axios.es) * */ final class OffsetOrientationAnalyzer { private final GeometryFactory gf; private List<Coordinate> inputList; private final List<Coordinate> sourceList; private Map<Integer, List<DataSegmentIntersection>> intersectionData; private List<Coordinate> eliminatedCoords = new ArrayList<Coordinate>(); private Map<Integer, Map<Coordinate, Boolean>> addedCoords = new HashMap<Integer, Map<Coordinate, Boolean>>(); private int intersectionLastPosition; private int badCoordinate; private int totalIntersection; private static final double SMALL_DISTANCE = 1.0E-6; // private static final double DEPRECIATE_VALUE = 6.0E-3; public OffsetOrientationAnalyzer( List<Coordinate> inputList, GeometryFactory gf, List<Coordinate> sourceList, Map<Integer, List<DataSegmentIntersection>> intersectionData) { this.inputList = inputList; this.gf = gf; this.sourceList = sourceList; this.intersectionData = intersectionData; } /** * <pre> * * Given the input geometry, it will be a list off nodded coordinates, which will form * a closed lineString. * It will analyze it, and as result will give a list of geometries, those geometries * will be rings extracted from that previous closed lineString. * * The code bellow works like this way: * -Obtain all the rings from the input geometry. * -Find intersections. * -Use the quadrant operation using the intersection coordinate and the coordinates * of the segments that intersects each other. * -The quadrant operation will return a coordinate that is wrong, so the ring that contain * that coordinate will be discarded. * * </pre> * * @param segmentIntersectsWithOthers * * @return The list of geometry rings. */ public List<Geometry> discardClosed() { assert inputList.size() == sourceList.size() : "size not equal"; //$NON-NLS-1$ int n = inputList.size(); boolean found = false; List<LinearRing> ringList = getRings(); // we suppose that all the rings are OK, so add all of them to the final // list. List<LinearRing> finalRings = new ArrayList<LinearRing>(); finalRings.addAll(ringList); // will store the position and if it's forward or backward. (True is // forward.) DataDirectionUntil[] indexList = new DataDirectionUntil[n]; // go through the result coordinates finding an intersection. for (int i = 0; (i < n - 1); i++) { found = false; Coordinate intersectionCoord = null; // create a segment Coordinate[] segCoord = new Coordinate[2]; segCoord[0] = inputList.get(i); segCoord[1] = (i == n - 1) ? inputList.get(0) : inputList.get(i + 1); LineString segment = gf.createLineString(segCoord); // go forward and find the intersection, intersectionCoord = findIntersectionForward(segment, segCoord, i, n); if (intersectionCoord == null) { // when it's null, check if the point is contained on the list. if (intersectionData.containsKey(i)) { indexList = storeIndex(i, n, indexList, segment, segCoord); } } else { // set the end coordinate position of the segment that // intersects. intersectionLastPosition = (intersectionLastPosition == n - 1) ? 0 : intersectionLastPosition + 1; // go backward until the same coordinate is found. for (int h = i; !found; h = (h == 0) ? n - 1 : (h - 1) % n) { // try to find the same candidateCoord. It will // return true when it founds. found = findIntersectionBackward(segment, intersectionCoord, h, n); } // Compute the imaginary intersection using the source // segments. Coordinate[] firstOriginalSegment = new Coordinate[2]; firstOriginalSegment[0] = sourceList.get(i); firstOriginalSegment[1] = sourceList.get(i + 1); Coordinate[] secondOriginalSegment = new Coordinate[2]; secondOriginalSegment[0] = sourceList.get(intersectionLastPosition); secondOriginalSegment[1] = sourceList.get((intersectionLastPosition == 0 ? n - 1 : intersectionLastPosition - 1)); // get its intersection. Coordinate imaginaryCoord = GeometryUtil.intersection(firstOriginalSegment[0], firstOriginalSegment[1], secondOriginalSegment[0], secondOriginalSegment[1]); assert imaginaryCoord != null : "can't be null"; //$NON-NLS-1$ // using the quadrant analyzer will know which segments are // valid and which aren't valid. if (belongsToSegment(firstOriginalSegment, secondOriginalSegment, imaginaryCoord) && !checkQuadrantClosedLines(i, n, intersectionCoord, imaginaryCoord)) { // the ring which contain the badCoordinate will be // eliminated from the final list. List<LinearRing> deletedRings = getDeletedRings(ringList); // when there are more than 1 ring to be eliminated, that // means that the eliminatedPoint was a point common between // those 2 rings. So now, eliminate the ring that has less // size. double min = Double.MAX_VALUE; LinearRing candidate = null; for (LinearRing ring : deletedRings) { double size = ring.getCoordinates().length; if (size < min) { min = size; candidate = ring; } } finalRings.remove(candidate); } } } List<Geometry> resultList = new ArrayList<Geometry>(); List<Integer> usedIndexes = new ArrayList<Integer>(); for (LinearRing result : finalRings) { // get that ring and modify it Coordinate[] ringCoords = result.getCoordinates(); // this list will take into account the deleted coordinates, so the // second time a ring is modified to not add them again. eliminatedCoords.clear(); addedCoords.clear(); for (int i = 0; i < ringCoords.length; i++) { Coordinate actualCoord = ringCoords[i]; for (int j = 0; j < indexList.length; j++) { DataDirectionUntil data = indexList[j]; if (data != null) { Integer indexKey = j; Boolean forward = data.getIsForwardDirection(); int until = data.getUntilPosition(); // if one of those coordinates equal the actualCoord, // that // means, this rings is the one to be modified. if (actualCoord.equals2D(inputList.get(indexKey)) && !(usedIndexes.contains(indexKey))) { try { result = improveTheRing(i, indexKey, n, ringCoords, forward, until); usedIndexes.add(indexKey); } catch (InvalidDistanceException ide) { // it has encounters difficulties during the // process, so // don't modify the ring and continue. continue; } } } } } resultList.add(result); } return resultList; } /** * Get the rings that could be deleted. * * @param ringList * @return A list of linearRings. */ private List<LinearRing> getDeletedRings(List<LinearRing> ringList) { List<LinearRing> deletedRings = new ArrayList<LinearRing>(); for (LinearRing ring : ringList) { Coordinate eliminateCoord = inputList.get(badCoordinate); Geometry eliminatePoint = gf.createPoint(eliminateCoord); if (ring.intersects(eliminatePoint)) { deletedRings.add(ring); } } return deletedRings; } /** * Modify the ring, taking into account the direction(forward or not), set * the index of the coordinates that will be removed. * * Also, calculate the "intersection coordinate", because from the pointList * will recover one coordinate, this calculated one and the retrieved one, * must be very very similar. If that doesn't happens, it throws an error. * * Finally, when it is decided which coordinates will be deleted * (secondIndex and thirdIndex), they are deleted and in the middle of * those, there is inserted the calculate coordinate or the retrieved one. * * @param i * @param j * @param n * @param ringCoords * @param forward * @param until * @param ringLength * @return The modified geometry. * @throws InvalidDistanceException */ private LinearRing improveTheRing(int i, int j, int n, Coordinate[] ringCoords, Boolean forward, int until) throws InvalidDistanceException { LinearRing result = null; int firstIndex, secondIndex, thirdIndex, fourthIndex, untilDifference = 0, ringLength = ringCoords.length; untilDifference = calculateUntilDifference(forward, until, j, i, n, ringLength); if (forward) { // mount a segment with j and j+1, then intersect with // j+2-j+3 and that intersection must be the same as the // one contained in the mapList. firstIndex = j; secondIndex = (firstIndex == n - 2) ? 0 : j + 1; thirdIndex = until - 1; fourthIndex = (thirdIndex == n - 2) ? 0 : thirdIndex + 1; } else { firstIndex = j + 1; secondIndex = (firstIndex == 0) ? n - 2 : firstIndex - 1; thirdIndex = until + 1; fourthIndex = (thirdIndex == 0) ? n - 2 : thirdIndex - 1; } LineString firstLine = createLineSegment(firstIndex, secondIndex); LineString secondLine = createLineSegment(thirdIndex, fourthIndex); Coordinate intersection = GeometryUtil.intersection(firstLine.getCoordinateN(0), firstLine.getCoordinateN(1), secondLine.getCoordinateN(0), secondLine.getCoordinateN(1)); List<DataSegmentIntersection> pointsThatBelongs = intersectionData.get(j); // when modifying the ring, use the index provided by // 'i' and not 'j'. if (forward) { firstIndex = i; secondIndex = (i == ringLength - 2) ? 0 : i + 1; thirdIndex = untilDifference - 1; fourthIndex = (thirdIndex == ringLength - 2) ? 0 : thirdIndex + 1; } else { firstIndex = i + 1; secondIndex = (firstIndex == 0) ? ringLength - 2 : firstIndex - 1; thirdIndex = untilDifference + 1; fourthIndex = (thirdIndex == 0) ? ringLength - 2 : thirdIndex - 1; } // calculate the closest one using the intersection // coordinate, if it is too short, it is OK. Coordinate coordinateToAdd = getClosestPoint(intersection, pointsThatBelongs); result = modifyRing(coordinateToAdd, firstIndex, secondIndex, thirdIndex, fourthIndex, ringCoords, forward, untilDifference); return result; } private int calculateUntilDifference(boolean forward, int until, int j, int i, int n, int ringLength) { int diff, untilDifference; if (forward) { // diff=0; if (until > j) { diff = until - j; if (i + diff > ringLength - 2) { untilDifference = ringLength - diff - i + 1; } else { untilDifference = i + diff; } } else { diff = n - j; if (i + diff > ringLength - 2) { untilDifference = ringLength - diff - i; } else { untilDifference = i + diff; } } } else { if (j > until) { diff = j - until; // check if untilDiff is negative. if (i - diff < 0) { untilDifference = ringLength - diff + i - 1; } else { untilDifference = i - diff; } } else { diff = n - until; if (i - diff < 0) { untilDifference = ringLength - diff + i; } else { untilDifference = i - diff; } } } return untilDifference; } /** * First check the orientation of 3 coordinates, if it return false, check * again but this time using the quadrant method. If this also return false, * store that index and mark as forward. * * If the first check has failed, analyze the previous 2 segments seeking * for intersections, if there aren't check orientation of the actual * segment but in reverse order, using the last 3 coordinates. If that * return false, do the same but using the quadrant, and if this also return * false, store the index but mark as backward. * * @param i * @param n * @param indexList * @param segment * @param segCoord * @return The map with the stored index and its direction. */ private DataDirectionUntil[] storeIndex(int i, int n, DataDirectionUntil[] indexList, LineString segment, Coordinate[] segCoord) { List<DataSegmentIntersection> dataList = intersectionData.get(i); // will treat the ones with only one item. if (dataList.size() > 1) { return indexList; } for (DataSegmentIntersection data : dataList) { int startSegmentIndex = data.getStartSegmentIndex(); boolean isForward = data.getIsForward(); boolean isValidForStore = false; boolean same; if (isForward) { // TODO maybe here also is need to do something similar like the // END-INDEX?? for (int j = i; j < startSegmentIndex - 1; j++) { same = hasSameOrientation(i, true); // check if it has the same orientation if (!same) { isValidForStore = true; } else { isValidForStore = false; break; } } if (isValidForStore) { indexList[i] = new DataDirectionUntil(isForward, startSegmentIndex + 1); } } else { i = i + 1; int endIndex = startSegmentIndex + 2; if (endIndex == n - 1) { endIndex = 0; } else if (endIndex == 0) { endIndex = 1; } for (int j = i;;) { same = hasSameOrientationReverse(i, true); if (!same) { isValidForStore = true; } else { isValidForStore = false; break; } j = (j == 0) ? n - 2 : j - 1; if (j == endIndex) { break; } } if (isValidForStore) { indexList[i - 1] = new DataDirectionUntil(isForward, startSegmentIndex); } } } return indexList; } /** * From input coordinates, create a lineString. Then, with that lineString * form polygons using the polygonizer, and retrieve its shell that will be * the linearRings needed. * * @return All the rings. */ private List<LinearRing> getRings() { List<LinearRing> ringList = new ArrayList<LinearRing>(); // before create the ring, calculate the orientation of the input list. boolean isCCWinputList = CGAlgorithms.isCCW(inputList.toArray(new Coordinate[inputList.size()])); // create the rings. LineString inputLineString = gf.createLineString(inputList.toArray(new Coordinate[inputList.size()])); Geometry multiLines = inputLineString.union(); Polygonizer polygonizer = new Polygonizer(); polygonizer.add(multiLines); Collection<Polygon> polyCollection = polygonizer.getPolygons(); // add the rings to the ringList. for (Polygon pol : polyCollection) { Coordinate[] polCoord = pol.getExteriorRing().getCoordinates(); boolean isCCWpolygon = CGAlgorithms.isCCW(polCoord); // if they don't have the same orientation, reverse this ring. if (isCCWinputList != isCCWpolygon) { CoordinateArrays.reverse(polCoord); } LinearRing polygonRing = gf.createLinearRing(polCoord); ringList.add(polygonRing); } return ringList; } private LinearRing modifyRing( Coordinate coordinateToAdd, Integer firstIndex, int secondIndex, int thirdIndex, int fourthIndex, Coordinate[] ringCoords, Boolean forward, int untilDifference) { List<Coordinate> createdRing = new ArrayList<Coordinate>(); for (int i = 0; i < ringCoords.length; i++) { if (eliminateCoords(forward, firstIndex, untilDifference, i, ringCoords)) { continue; } // if this coordinate was eliminated before, don't add it again. if (eliminatedCoords.contains(ringCoords[i])) { continue; } // adding the previous change on this ring. if (addedCoords.containsKey(i)) { Map<Coordinate, Boolean> values = addedCoords.get(i); Entry<Coordinate, Boolean> entry = values.entrySet().iterator().next(); Coordinate coordToAdd = entry.getKey(); Boolean whereToAdd = entry.getValue(); if (whereToAdd) { createdRing.add(ringCoords[i]); createdRing.add(coordToAdd); } else { createdRing.add(coordToAdd); createdRing.add(ringCoords[i]); } } else { // add the rest of the points. createdRing.add(ringCoords[i]); } if (i == firstIndex) { Map<Coordinate, Boolean> added = new HashMap<Coordinate, Boolean>(); added.put(coordinateToAdd, forward); addedCoords.put(i, added); if (forward) { // after adding the point of j, add the new calculated // coord. createdRing.add(coordinateToAdd); } else { createdRing.remove(createdRing.size() - 1); createdRing.add(coordinateToAdd); createdRing.add(ringCoords[i]); } } } // with the list, create a linearRing, before doing that, close the // rings. int size = createdRing.size(); if (!createdRing.get(0).equals2D(createdRing.get(size - 1))) { createdRing.add(createdRing.get(0)); } Coordinate[] createdCoords = createdRing.toArray(new Coordinate[createdRing.size()]); LinearRing result = gf.createLinearRing(createdCoords); return result; } private boolean eliminateCoords(boolean forward, int firstIndex, int untilDifference, int i, Coordinate[] ringCoords) { // those coords will be removed. if (forward) { if (firstIndex > untilDifference) { if (i > firstIndex || i < untilDifference) { eliminatedCoords.add(ringCoords[i]); return true; } } else { if (i > firstIndex && i < untilDifference) { eliminatedCoords.add(ringCoords[i]); return true; } } } else { if (firstIndex > untilDifference) { if (i > untilDifference && i < firstIndex) { eliminatedCoords.add(ringCoords[i]); return true; } } else { if (i < firstIndex || i > untilDifference) { eliminatedCoords.add(ringCoords[i]); return true; } } } return false; } private Coordinate getClosestPoint(Coordinate intersection, List<DataSegmentIntersection> pointsThatBelongs) throws InvalidDistanceException { Double min = Double.MAX_VALUE; Coordinate closest = null; for (DataSegmentIntersection point : pointsThatBelongs) { double dist = point.getIntersectionCoordinate().distance(intersection); if (dist < min) { min = dist; closest = point.getIntersectionCoordinate(); } } if (!(min < SMALL_DISTANCE)) { throw new InvalidDistanceException(min.toString()); } return closest; } /** * If the distance of the imaginary intersection with any of those segment * is to little, (intersects says false) we can assume they intersect. * * Taking into account that, and also knowing how many intersection that * segment has, the function will work like that way: * * <pre> * -If there are only 1 intersection, return true. * -If there are 2 intersection, but the distance between the point * and any of the segment is to small, like point would intersect the segments, * will return true. * -If there are 2 intersection, but the distance is not to small, will return false. * </pre> * * @param firstOriginalSegment * @param secondOriginalSegment * @param imaginaryCoord * @return */ private boolean belongsToSegment( Coordinate[] firstOriginalSegment, Coordinate[] secondOriginalSegment, Coordinate imaginaryCoord) { if (totalIntersection == 1) { return true; } LineString firstSegment = gf.createLineString(firstOriginalSegment); LineString secondSegment = gf.createLineString(secondOriginalSegment); Geometry point = gf.createPoint(imaginaryCoord); if (totalIntersection == 2 && (firstSegment.distance(point) < SMALL_DISTANCE || secondSegment.distance(point) < SMALL_DISTANCE)) { return true; } return false; } /** * Check the orientation of a segment (p0-p1) with a point (p2). * * Will check the orientation of 3 points from the input list and compare * with the respective orientation of that 3 points of the source list. * * If the orientations are the same, will check the quadrant of those * segment and return the result. * * @param i * The position of the first point. Will check i, i+1 and i+2. * @param isClosedLine * @return True if the orientation matches. */ private boolean hasSameOrientation(int i, boolean isClosedLine) { int n = inputList.size(); int second = i + 1, third = i + 2; if (isClosedLine) { if (i == n - 1) { second = 1; third = 2; } else if (i == n - 2) { third = 1; } } else { if (i == n - 1) { second = 0; third = 1; } else if (i == n - 2) { third = 0; } } int offsetOrientation = CGAlgorithms.orientationIndex(inputList.get(i), inputList.get(second), inputList .get(third)); int originalOrientation = CGAlgorithms.orientationIndex(sourceList.get(i), sourceList.get(second), sourceList .get(third)); boolean result = offsetOrientation == originalOrientation; if (result) { Coordinate secondCoordInput = inputList.get(second); Coordinate thirdCoordInput = inputList.get(third); Coordinate secondCoordSrc = sourceList.get(second); Coordinate thirdCoordSrc = sourceList.get(third); if (!secondCoordInput.equals2D(thirdCoordInput) && !secondCoordSrc.equals2D(thirdCoordSrc)) { // just in case, check the quadrants are the same. int offsetQuadrant = Quadrant.quadrant(secondCoordInput, thirdCoordInput); int orientationQuadrant = Quadrant.quadrant(secondCoordSrc, thirdCoordSrc); result = offsetQuadrant == orientationQuadrant; } } return result; } /** * Check the orientation of a segment (p0-p1) with a point (p2). But those * segment will be the end-segment of the list. * * Will check the orientation of 3 points from the input list and compare * with the respective orientation of that 3 points of the source list. * * If the orientations are the same, will check the quadrant of those * segment and return the result. * * @param i * Size of the list, so it will check n -1, n-2, and n-3. * @return True if the orientation matches. */ private boolean hasSameOrientationReverse(int i, boolean isClosedLine) { int n = inputList.size(); int second = i - 1, third = i - 2; if (isClosedLine) { if (i == 0) { second = n - 2; third = n - 3; } else if (i == 1) { third = n - 2; } } else { if (i == 0) { second = n - 1; third = n - 2; } else if (i == 1) { third = n - 1; } } int offsetOrientation = CGAlgorithms.orientationIndex(inputList.get(i), inputList.get(second), inputList .get(third)); int originalOrientation = CGAlgorithms.orientationIndex(sourceList.get(i), sourceList.get(second), sourceList .get(third)); boolean result = originalOrientation == offsetOrientation; if (result) { Coordinate secondCoordInput = inputList.get(second); Coordinate thirdCoordInput = inputList.get(third); Coordinate secondCoordSrc = sourceList.get(second); Coordinate thirdCoordSrc = sourceList.get(third); if (!secondCoordInput.equals2D(thirdCoordInput) && !secondCoordSrc.equals2D(thirdCoordSrc)) { int offsetQuadrant = Quadrant.quadrant(secondCoordInput, thirdCoordInput); int orientationQuadrant = Quadrant.quadrant(secondCoordSrc, thirdCoordSrc); result = offsetQuadrant == orientationQuadrant; } } return result; } /** * Giving a segment and an intersectionCoordinate, will create a second * segment with the provided index * * @param segment * @param firstCandidateCoord * @param i * @param n * @return True if it finds the same intersection. */ private boolean findIntersectionBackward(LineString segment, Coordinate firstCandidateCoord, int i, int n) { Coordinate[] segCoord = new Coordinate[2]; segCoord[0] = inputList.get(i); segCoord[1] = (i == 0) ? inputList.get(n - 1) : inputList.get(i - 1); LineString secondSegment = gf.createLineString(segCoord); // test if exist intersection and it matches with the // firstCandidateCoord. // if intersects. if (segment.intersects(secondSegment)) { Geometry resultPoints = segment.intersection(secondSegment); for (int k = 0; k < resultPoints.getNumGeometries(); k++) { Coordinate intersectionCoord = resultPoints.getGeometryN(k).getCoordinate(); if (firstCandidateCoord.equals2D(intersectionCoord)) { return true; } } } return false; } /** * Will find the intersection if exist. If exist, return the coordinate. If * doesn't exist will return null. * * @param segment * @param segCoord * @param j * @param n * @return If found, the intersection coordinate, else null. */ private Coordinate findIntersectionForward(final Geometry segment, final Coordinate[] segCoord, int j, int n) { // the segment that will be tested if intersects. LineString interSegment = null; Coordinate candidateCoord = null, finalCoordinate = null; intersectionLastPosition = -1; // make a complete turn and get with the last intersection that // intersects with the provided segment. int count = 0; for (int h = j;;) { h = (h + 1) % n; // mount a segment. Coordinate[] interCoord = new Coordinate[2]; interCoord[0] = inputList.get(h); interCoord[1] = (h == n - 1) ? inputList.get(0) : inputList.get(h + 1); interSegment = gf.createLineString(interCoord); // get the intersection coordinate if it intersects. candidateCoord = AlgorithmUtils.getIntersectionCoord(segment, interSegment, segCoord); if (candidateCoord != null) { finalCoordinate = new Coordinate(candidateCoord); intersectionLastPosition = h; count++; } if (h == j) { // if it has been checked all the segment and any intersection // has been found, get out the loop and return nothing ?? break; } } this.totalIntersection = count; return finalCoordinate; } /** * <pre> * Analyze the line and discard pieces that are wrong. * * First, find an intersection, when it has been found, using the quadrant algorithm * determine which piece must be eliminated. * </pre> * * @return The line without the wrong pieces. */ public List<Geometry> discardNonClosed() { // if less than 4 coordinates, return it without modify. if (inputList.size() < 4) { List<Geometry> resultList = new ArrayList<Geometry>(); Coordinate[] resultCoord = inputList.toArray(new Coordinate[inputList.size()]); resultList.add(gf.createLineString(resultCoord)); return resultList; } assert inputList.size() == sourceList.size() : "size not equal"; //$NON-NLS-1$ // go through the result coordinates, checking its orientation with the // original orientation. int n = inputList.size(); boolean lastAddedWasIntersection = false; // the list with the resultant coordinates. List<Coordinate> filteredResult = new ArrayList<Coordinate>(); Coordinate intersectCoord = null; Coordinate candidateCoord = null; int start = 0; // check the first 3 coordinates. if (!hasSameOrientation(0, false) && !hasBeginIntersections()) { // they haven't the same orientation. // as AutoCAD do, remove the first segment. start = 1; } // go through the line picking segments of it and analyzing them. for (int i = start; i < n - 1;) { candidateCoord = null; intersectionLastPosition = -1; // get the first segment (i->i+1) or (intersectCoor->i+1) LineString firstSegment = intersectCoord == null ? createLineSegment(i, i + 1) : createLineSegment( intersectCoord, i + 1); // initialize intersectCoord. intersectCoord = null; candidateCoord = findIntersectionWithProvidedSegment(firstSegment, i + 1, n); if (candidateCoord != null) { assert intersectionLastPosition != -1 : "intersection must be found"; //$NON-NLS-1$ // If intersection is found, now must check the quadrant of // the segments between that intersection. boolean sameQuadrant = true; // QUADRANT BETWEEN i ->i+1 & intersectionLastPosition -> // intersectionLastPosition +1 sameQuadrant = checkQuadrantOpenLines(i); if (!sameQuadrant) { // if last added coordinate was an intersection coordinate, // now add only this coordinate. if (lastAddedWasIntersection) { filteredResult.add(candidateCoord); } else { // if not, add the beginning of the segment and the // intersection coordinate. filteredResult.add(inputList.get(i)); filteredResult.add(candidateCoord); } lastAddedWasIntersection = true; // set the intersection coordinate. intersectCoord = new Coordinate(candidateCoord); } else { // when the last added coordinate was an intersection, don't // add the one that belongs to "i" if (!lastAddedWasIntersection) { filteredResult.add(inputList.get(i)); } lastAddedWasIntersection = false; } } else { // when the last added coordinate was an intersection, don't add // the one that belongs to "i" if (!lastAddedWasIntersection) { filteredResult.add(inputList.get(i)); } lastAddedWasIntersection = false; } i = lastAddedWasIntersection ? intersectionLastPosition : i + 1; } // check the orientation of the last segments // if they match, add the last segment. if (hasSameOrientationReverse(n - 1, false) || hasEndIntersections(n)) { filteredResult.add(inputList.get(n - 1)); } List<Geometry> resultList = new ArrayList<Geometry>(); Coordinate[] resultCoord = filteredResult.toArray(new Coordinate[filteredResult.size()]); resultList.add(gf.createLineString(resultCoord)); return resultList; } /** * Run the quadrant analyzer. If everything is OK, it will return true, * false otherwise. * * @param i * @return True if it's OK. */ private boolean checkQuadrantOpenLines(int i) { boolean isOk = false; QuadrantAnalyzer qAnalyzer = new QuadrantAnalyzer(inputList, sourceList, i, intersectionLastPosition); qAnalyzer.analyzeNonClosedLines(); isOk = qAnalyzer.isValid(); // rarely the quadrant algorithm fails. // On those cases, check the orientation of the segments between // the intersection. if (isOk) { boolean sameOrientation = hasSameOrientation(i + 1, false); isOk = sameOrientation; } return isOk; } /** * Analyze running the quadrantAnalyzer which will return true or false. If * return false, some coordinate is wrong; so it will retrieve which one is * and set as badCoordinate. * * @param i * @param size * @param realIntersectionCoord * @param imaginaryCoord * @return */ private boolean checkQuadrantClosedLines( int i, int size, Coordinate realIntersectionCoord, Coordinate imaginaryCoord) { boolean isOk = false; QuadrantAnalyzer qAnalyzer = new QuadrantAnalyzer(inputList, sourceList, i, intersectionLastPosition, size, realIntersectionCoord, imaginaryCoord); qAnalyzer.analyzeClosedLines(); isOk = qAnalyzer.isValid(); if (!isOk) { // retrieve that coordinate, its position. AnalyzerPosition positionIndex = qAnalyzer.getResultIndex(); switch (positionIndex) { case SEGMENT_1_POINT_1: // wrong coordinate = i this.badCoordinate = i; break; case SEGMENT_1_POINT_2: // wrong coordinate = i +1 this.badCoordinate = i + 1; break; case SEGMENT_2_POINT_1: // wrong coordinate = intersectionLast this.badCoordinate = intersectionLastPosition; break; case SEGMENT_2_POINT_2: // wrong coordinate = intersectionLastPosition == 0 ? size - 1 : // intersectionLastPosition - 1; this.badCoordinate = intersectionLastPosition == 0 ? size - 1 : intersectionLastPosition - 1; break; default: break; } } return isOk; } /** * Function called when checking the orientation of the beginning. Will * check if the first and third segments intersects each other. * * @return */ private boolean hasBeginIntersections() { // check the segment 0-1 and segment 2-3 if intersects. LineString firstSegment = createLineSegment(0, 1); LineString secondSegment = createLineSegment(2, 3); Coordinate[] firstSegC = new Coordinate[2]; firstSegC[0] = inputList.get(0); firstSegC[1] = inputList.get(1); Coordinate interCoord = AlgorithmUtils.getIntersectionCoord(firstSegment, secondSegment, firstSegC); return interCoord != null; } /** * Function called when checking the orientation of the ending. Will check * if the last and third to last segments intersects each other. * * @return */ private boolean hasEndIntersections(int n) { // check the segment 0-1 and segment 2-3 if intersects. LineString firstSegment = createLineSegment(n - 1, n - 2); LineString secondSegment = createLineSegment(n - 3, n - 4); Coordinate[] firstSegC = new Coordinate[2]; firstSegC[0] = inputList.get(n - 1); firstSegC[1] = inputList.get(n - 2); Coordinate interCoord = AlgorithmUtils.getIntersectionCoord(firstSegment, secondSegment, firstSegC); return interCoord != null; } /** * Create a new lineSegment using the provided coordinate. * * @param firstCoord * The provided coordinate. * @param second * The position respect the list of the second coordinate. * @return The desired lineString segment. */ private LineString createLineSegment(final Coordinate firstCoord, int second) { Coordinate[] firstSegC = new Coordinate[2]; firstSegC[0] = new Coordinate(firstCoord); firstSegC[1] = inputList.get(second); return gf.createLineString(firstSegC); } /** * Create a LineString segment between first and second coords. * * @param first * The position respect the list of the first coordinate. * @param second * The position respect the list of the second coordinate. * @return The desired lineString segment. */ private LineString createLineSegment(int first, int second) { Coordinate[] firstSegC = new Coordinate[2]; firstSegC[0] = inputList.get(first); firstSegC[1] = inputList.get(second); return gf.createLineString(firstSegC); } /** * Find the first intersection of the provided segment. Return the * intersection coordinate if exist. Also set the intersectionLastPosition. * * @param firstSegment * The segment which will be intersected. * @param start * Start position. * @param n * List size. * @return The intersection coordinate or null. */ private Coordinate findIntersectionWithProvidedSegment(LineString firstSegment, int start, int n) { // the segment that will be tested if intersects. LineString interSegment = null; Coordinate candidateCoord = null, finalCoordinate = null; Coordinate[] firstSegC = new Coordinate[2]; firstSegC[0] = firstSegment.getCoordinateN(0); firstSegC[1] = firstSegment.getCoordinateN(1); // reset the variable. intersectionLastPosition = -1; for (int h = start; h < n - 1; h++) { // mount a segment. interSegment = createLineSegment(h, h + 1); // get the intersection coordinate if exist, otherwise will return // null. candidateCoord = AlgorithmUtils.getIntersectionCoord(firstSegment, interSegment, firstSegC); if (candidateCoord != null) { finalCoordinate = new Coordinate(candidateCoord); intersectionLastPosition = h; } } return finalCoordinate; } }