Package org.locationtech.udig.tools.geometry.split

Source Code of org.locationtech.udig.tools.geometry.split.SplitUtil

/* uDig - User Friendly Desktop Internet GIS
* http://udig.refractions.net
* (c) 2010, Vienna City
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Axios BSD
* License v1.0 (http://udig.refractions.net/files/asd3-v10.html).
*/
package org.locationtech.udig.tools.geometry.split;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

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.operation.polygonize.Polygonizer;

import org.locationtech.udig.tools.geometry.internal.util.GeometryList;
import org.locationtech.udig.tools.geometry.internal.util.GeometrySet;

/**
*
* Useful functions for working with split, like calculate if split is possible,
* getting intersections between split line and boundary, etc
*
* @author Aritz Davila (www.axios.es)
* @author Mauricio Pazos (www.axios.es)
* @since 1.2.0
*/
public final class SplitUtil {

    /**
     * Get the interior rings of the polygon, and add to the list the rings that
     * intersects with the segment.
     *
     * @param segment
     *            line segment.
     * @param polygon
     *            the polygon to be analyzed.
     * @return A list with rings that intersects with the provided segment.
     */
    public static List<LinearRing> addRingThatIntersects(LineString segment,
      Polygon polygon) {

  List<LinearRing> rings = new GeometryList<LinearRing>();
  for (int j = 0; j < polygon.getNumInteriorRing(); j++) {

      // get an interior ring.
      LinearRing interiorRing = (LinearRing) polygon.getInteriorRingN(j);

      // add the rings that intersects with the segment.
      if (interiorRing.intersects(segment)) {
    rings.add(interiorRing);
      }
  }

  return rings;
    }

    /**
     * Calculate the distance of the intersectionCoord.
     *
     * @param intersectionCoord
     *            The coordinate which will calculate the distance.
     * @param firstSegmentCoord
     *            Coordinate from the segment.
     * @param secondSegmentCoord
     *            Coordinate from the segment.
     * @return The distance between intersectionCoord and the segment.
     */
    public static double calculateDistanceFromFirst(
      final Coordinate intersectionCoord,
      final Coordinate firstSegmentCoord,
      final Coordinate secondSegmentCoord) {

  return firstSegmentCoord.distance(intersectionCoord);
    }

    /**
     * Will find the number of intersections starting from a coordinate, and
     * return the total number.
     *
     * @param posFirstCoord
     *            Position to start with.
     * @param coordinates
     *            Line coordinates.
     * @param boundary
     *            Boundary geometry to intersect with.
     * @return The total number of intersections between split line and boundary
     *         given an start point.
     */
    public static int countIntersectionsFromPosition(final int posFirstCoord,
      final Coordinate[] coordinates, final Geometry boundary) {

  Set<Geometry> intersectionList = new GeometrySet<Geometry>();
  for (int i = posFirstCoord; i < coordinates.length - 1; i++) {

      Geometry intersection = intersection(coordinates, i, boundary,
        false);

      if (intersection instanceof Point
        || intersection instanceof MultiPoint
        || intersection instanceof GeometryCollection) {

    for (int j = 0; j < intersection.getNumGeometries(); j++) {

        Geometry intersectionPart = intersection.getGeometryN(j);
        if (!intersectionList.contains(intersectionPart)) {
      intersectionList.add(intersectionPart);
        }
    }
      }
  }
  return intersectionList.size();
    }

    public static int countIntersectionsFromPosition(final int posFirstCoord,
      final Coordinate[] coordinates, final Geometry boundary,
      final Coordinate skipCoord) {

  Set<Geometry> intersectionList = new GeometrySet<Geometry>();
  for (int i = posFirstCoord; i < coordinates.length - 1; i++) {

      Geometry intersection = intersection(coordinates, i, boundary,
        false);

      if (intersection instanceof Point
        || intersection instanceof MultiPoint
        || intersection instanceof GeometryCollection) {

    for (int j = 0; j < intersection.getNumGeometries(); j++) {

        Geometry intersectionPart = intersection.getGeometryN(j);
        if (!intersectionList.contains(intersectionPart)
          && (!intersectionPart.getCoordinate().equals(
            skipCoord))) {
      intersectionList.add(intersectionPart);
        }
    }
      }
  }
  return intersectionList.size();
    }

    /**
     * Returns the intersection geometry of a segment with the given geometry.
     *
     * @param coordinates
     *            Line coordinates.
     * @param i
     *            Position to get coordinates.
     * @param boundary
     *            Polygon boundary to intersect with.
     * @param startsFromEnd
     *            Starts creating the segment i and i-1.
     * @return The intersection geometry of a segment with the given geometry.
     */
    public static Geometry intersection(final Coordinate[] coordinates,
      final int i, final Geometry boundary, final boolean startsFromEnd) {

  // make a segment with the last coordinate of split line and the
  // following coordinate.
  Coordinate[] segmentCoordinates = new Coordinate[2];
  segmentCoordinates[0] = coordinates[i];
  segmentCoordinates[1] = (startsFromEnd) ? coordinates[i - 1]
    : coordinates[i + 1];

  LineString segment = boundary.getFactory().createLineString(
    segmentCoordinates);

  Geometry intersection = boundary.intersection(segment);

  assert intersection instanceof MultiPoint
    || intersection instanceof Point
    || intersection instanceof GeometryCollection
    || intersection instanceof LineString : "point must be point"; //$NON-NLS-1$

  return intersection;
    }

    /**
     * Check if all coordinates lies outside the polygon.
     *
     * @param coordinates
     *            Coordinates to be checked.
     * @param polygonGeometry
     *            The polygon geometry.
     * @return True if all coordinates are outside the polygon, false otherwise.
     */
    public static boolean areAllCoordinatesOutside(
      final Coordinate[] coordinates, final Geometry polygonGeometry) {

  final GeometryFactory geomFactory = polygonGeometry.getFactory();

  boolean allOutside = true;
  for (int i = 0; i < coordinates.length; i++) {

      Point point = geomFactory.createPoint(coordinates[i]);
      // check the points is inside the polygon.
      // if some one is inside, will return.
      if (polygonGeometry.contains(point)) {
    allOutside = false;
    break;
      }
  }
  return allOutside;
    }

    /**
     * Seeks the first coordinate of split line that lies outside of polygon.
     *
     * @param splitLine
     *            The split line.
     * @param polygonGeometry
     *            The polygon which the line will use.
     * @return the first coordinate founded outside the polygon. If it doesn't
     *         find, return -1.
     */
    public static int findPositionOfFirstCoordinateOutside(
      LineString splitLine, Geometry polygonGeometry) {

  Coordinate[] lineCoordinates = splitLine.getCoordinates();
  GeometryFactory fc = splitLine.getFactory();
  int position = -1;
  for (int i = 0; i < lineCoordinates.length; i++) {

      boolean valid = isCoordOutSidePolygon(polygonGeometry,
        lineCoordinates, i, fc);
      if (valid) {
    return i;
      }
  }
  return position;
    }

    /**
     * Finds the coordinates of the split line and go through it. Start from the
     * last coordinate and seeks the first coordinate of split line that lies
     * outside of polygon.
     *
     * @param splitLine
     *            The split line.
     * @param polygonGeometry
     *            The polygon which the line will use.
     * @return the first coordinate founded outside the polygon. If it doesn't
     *         find, return -1.
     */
    public static int findLastCoordinateOutside(LineString splitLine,
      Geometry polygonGeometry) {

  Coordinate[] lineCoordinates = splitLine.getCoordinates();
  GeometryFactory fc = splitLine.getFactory();
  int position = -1;
  for (int i = lineCoordinates.length - 1; 0 <= i; i--) {

      boolean valid = isCoordOutSidePolygon(polygonGeometry,
        lineCoordinates, i, fc);
      if (valid) {
    return i;
      }
  }
  return position;
    }

    /**
     * Return true if the lineCoord touches the boundary or intersects the hole
     * area
     *
     * @param polygonGeometry
     * @param lineCoords
     * @param fc
     * @return true if the segment is out side
     */
    private static boolean isCoordOutSidePolygon(
      final Geometry polygonGeometry, final Coordinate[] lineCoords,
      final int indexInLineCoords, final GeometryFactory fc) {

  Point point = fc.createPoint(lineCoords[indexInLineCoords]);
  if (polygonGeometry.disjoint(point)) {
      return true;
  }
  if (polygonGeometry.touches(point)) {

      if (indexInLineCoords < lineCoords.length - 1) {
    Coordinate[] firstSeg = new Coordinate[] {
      lineCoords[indexInLineCoords],
      lineCoords[indexInLineCoords + 1] };
    LineString firstSegment = fc.createLineString(firstSeg);

    return containsLineString(polygonGeometry
      .intersection(firstSegment));
      }

  }
  return false;
    }

    /**
     * Check if the geometry has {@link LineString} or {@link MultiLineString}
     * geometry type.
     *
     * @param geometry
     * @return True if intersection returns a {@link LineString} or
     *         {@link MultiLineString} geometry type.
     */
    public static boolean containsLineString(Geometry geometry) {

  for (int i = 0; i < geometry.getNumGeometries(); i++) {

      Geometry geomPart = geometry.getGeometryN(i);
      if (geomPart instanceof LineString
        || geomPart instanceof MultiLineString) {
    return true;
      }

  }
  return false;
    }

    /**
     * Check if the given line is a closed line or if it has a
     * self-intersections, that means it will have a ring in the middle of it.
     *
     * @param splitLine
     *            The split line.
     * @return True if it forms a ring.
     */
    public static boolean isClosedLine(LineString splitLine) {

  Geometry multiLines = splitLine.union();

  Polygonizer polygonizer = new Polygonizer();
  polygonizer.add(multiLines);
  Collection<?> polyCollection = polygonizer.getPolygons();

  return polyCollection.size() != 0;
    }

    /**
     * When the splitLines are a MultiLineString they could have many geometries
     * and those could be nodded. This algorithm will create one geometry from
     * nodded geometries with its coordinate CCW oriented. If all the geometries
     * aren't nodded, it will return a multiGeometry with the nodded ones as a
     * single geometry and the others as they are.
     *
     * Ex: There could be 4 line geometries, making a continuous line. Then, the
     * algorithm will join all the geometries together, change its orientation
     * to CCW and return only 1 geometry.
     *
     * @param splitLines
     *            A multiLineString containing the splitlines.
     * @return If all the geometries are nodded, returns a single LineString, if
     *         they aren't, returns a multiLineString.
     */
    public static Geometry buildLineUnion(Geometry splitLines) {

  // try to "merge" the lines, this means if there is multigGeometry but
  // each geometry is nodded with the next one, create a lineString or
  // multilineString with nodded lines.

  List<Geometry> resultGeoms = new ArrayList<Geometry>();
  List<Coordinate> mergedCoordinates = new ArrayList<Coordinate>();
  GeometryFactory gf = splitLines.getFactory();
  for (int i = 0; i < splitLines.getNumGeometries(); i++) {

      // get the coordinates of the actual geometry.
      Coordinate[] partCoord = splitLines.getGeometryN(i)
        .getCoordinates();

      if (!mergedCoordinates.isEmpty()) {
    // compare the last added coordinate in mergedCoordinates with
    // the first coordinate from the part, if they are the same, it
    // means the line is nodded.
    Coordinate lastAddedCoord = mergedCoordinates
      .get(mergedCoordinates.size() - 1);
    if (lastAddedCoord.equals2D(partCoord[0])) {
        // add
        mergedCoordinates = addCoordinates(partCoord,
          mergedCoordinates, 1);
    } else {
        // they are not nodded or the nodding has ended.
        // create the geometry and start seeking again for more
        // nodded coordinates.
        resultGeoms = addMergedGeometry(mergedCoordinates,
          resultGeoms, gf);
        mergedCoordinates.clear();
        // add the partCoord as the first piece of line.
        mergedCoordinates = addCoordinates(partCoord,
          mergedCoordinates, 0);
    }
      } else {
    // add the first piece of line.
    mergedCoordinates = addCoordinates(partCoord,
      mergedCoordinates, 0);
      }
  }
  // if mergedCoordinate contains any item, create the resultant geometry.
  if (mergedCoordinates.size() != 0) {
      resultGeoms = addMergedGeometry(mergedCoordinates, resultGeoms, gf);
  }

  return gf.buildGeometry(resultGeoms);
    }

    /**
     * Get the coordinates and convert CCW if needed, then create the geometry,
     * add to the list and return it.
     *
     * @param mergedCoordinates
     *            The coordinates of the new geometry.
     * @param resultGeoms
     *            The list with the current processed geometries.
     * @param gf
     *            GeometryFactory.
     * @return The list of geometries plus this last one geometry.
     */
    private static List<Geometry> addMergedGeometry(
      List<Coordinate> mergedCoordinates, List<Geometry> resultGeoms,
      GeometryFactory gf) {

  Coordinate[] mergedArray = mergedCoordinates
    .toArray(new Coordinate[mergedCoordinates.size()]);

  if (mergedArray.length > 2 && !isColinear(mergedArray)) {

      // close the ring
      Coordinate[] closed = RingUtil.builRing(mergedArray);
      if (!CGAlgorithms.isCCW(closed)) {

    Geometry reverse = reverseLineString(closed, gf);

    assert CGAlgorithms.isCCW(reverse.getCoordinates()) : "It should be CCW. Actual SplitLine: " + reverse; //$NON-NLS-1$

    reverse = reverseLineString(mergedArray, gf);

    resultGeoms.add(reverse);
      } else {
    resultGeoms.add(gf.createLineString(mergedArray));
      }
  } else {
      resultGeoms.add(gf.createLineString(mergedArray));
  }

  return resultGeoms;
    }

    /**
     * Check if the given line is colinear.
     *
     * @param lineCoord
     *            Coordinates of the splitLine.
     * @return True if it is colinear.
     */
    public static boolean isColinear(Coordinate[] lineCoord) {

  boolean isColinear = true;
  for (int i = 0; i < lineCoord.length - 2; i++) {

      if (CGAlgorithms.computeOrientation(lineCoord[i], lineCoord[i + 1],
        lineCoord[i + 2]) != 0) {
    isColinear = false;
    break;
      }
  }
  return isColinear;
    }

    /**
     * Add coordinates to the list.
     *
     * @param partCoord
     *            The coordinates of the line.
     * @param mergedCoordinates
     *            The list with the nodded coordinates.
     * @param startPosition
     *            Position from where to start.
     * @return The list filled with the coordinates from the line.
     */
    private static List<Coordinate> addCoordinates(Coordinate[] partCoord,
      List<Coordinate> mergedCoordinates, int startPosition) {

  for (int j = startPosition; j < partCoord.length; j++) {
      mergedCoordinates.add(partCoord[j]);
  }

  return mergedCoordinates;
    }

    /**
     * Reverse the given coordinates, and return the resultant lineString.
     *
     * @param intersectionCoordinates
     *            Coordinates from a lineString.
     * @param gf
     *            Geometry factory.
     * @return The geometry built.
     */
    private static Geometry reverseLineString(
      Coordinate[] intersectionCoordinates, GeometryFactory gf) {

  intersectionCoordinates = CoordinateArrays
    .copyDeep(intersectionCoordinates);
  CoordinateArrays.reverse(intersectionCoordinates);

  return gf.createLineString(intersectionCoordinates);
    }

    /**
     * Copies the coordinates starting form posFirstCoord to posLastCoord.
     *
     * @param posFirstCoord
     *            Start position.
     * @param posLastCoord
     *            End position.
     * @param sourceCoords
     *            Coordinates to copy.
     * @return New array of coordinates.
     */
    public static Coordinate[] createCoordinateArrayWithoutDuplicated(
      int posFirstCoord, int posLastCoord, final Coordinate[] sourceCoords) {

  Coordinate[] newCoords = new Coordinate[posLastCoord - posFirstCoord
    + 1];

  newCoords = Arrays.copyOfRange(sourceCoords, posFirstCoord,
    posLastCoord + 1);

  newCoords = CoordinateArrays.removeRepeatedPoints(newCoords);

  return newCoords;
    }

    /**
     * Inserts the coordinate in the array of coordinates
     *
     * @param insertPosition
     * @param newCoord
     * @param coordArray
     *
     * @return a new array of coordinates with the newCoord in the position
     */
    public static Coordinate[] replaceCoordinate(final int insertPosition,
      final Coordinate newCoord, final Coordinate[] coordArray) {

  ArrayList<Coordinate> workingArray = new ArrayList<Coordinate>(
    Arrays.asList(coordArray));
  workingArray.set(insertPosition, newCoord);

  return workingArray.toArray(new Coordinate[workingArray.size()]);
    }

    /**
     * Make a new {@link LineString} which contains the specified vertex
     *
     * @param line
     *            the original vertex
     * @param vertex
     *            the vertex to insert
     * @return a new {@link LineString} with the indeed vertex
     */
    public static LineString insertVertexInLine(LineString line,
      Coordinate vertex) {

  Coordinate[] lineCoords = line.getCoordinates();
  if (CoordinateArrays.indexOf(vertex, lineCoords) > 0) {
      return line;
  }
  Coordinate[] newLineCoords = insertVertexInLine(lineCoords, vertex);

  LineString newLine = line.getFactory().createLineString(newLineCoords);

  return newLine;
    }

    /**
     * Create a new coordinate array inserting in order the provided vertex. The
     * order is determined by the distance of each coordinate respect the first
     * coordinate.
     *
     * @param line
     * @param vertex
     * @return an new coordinate with the provided vertex
     */
    public static Coordinate[] insertVertexInLine(Coordinate[] line,
      Coordinate vertex) {

  Coordinate[] newLine = new Coordinate[line.length + 1];
  final double vertexDistance = line[0].distance(vertex);

  int found = -1;
  int j = -1;
  for (int i = 0; i < line.length; i++) {

      // searches the position of new coordinate
      double currentDistance = line[0].distance(line[i]);
      if (currentDistance <= vertexDistance) {
    newLine[++j] = line[i];
      } else {
    found = i;
    break;
      }
  }
  if (found != -1) {
      // add the vertex and the rest of coordinates in the new segment
      newLine[++j] = vertex;
      for (int i = found; i < line.length; i++) {
    newLine[++j] = line[i];
      }

  } else {
      // vertex is the last coordinate
      newLine[newLine.length - 1] = vertex;
  }
  return newLine;

    }

    /**
     * Merge the first and the second line in one
     *
     * @param firstLine
     * @param secondLine
     * @return a new Coordinate list that contains the first and the second set
     *         of coordinates
     */
    public static Coordinate[] mergeCoordinate(final Coordinate[] firstLine,
      final Coordinate[] secondLine) {

  List<Coordinate> merge = new LinkedList<Coordinate>();

  for (int i = 0; i < firstLine.length; i++) {

      if (!merge.contains(firstLine[i])) {

    merge.add(firstLine[i]);
      }
  }
  for (int i = 0; i < secondLine.length; i++) {

      if (!merge.contains(secondLine[i])) {

    merge.add(secondLine[i]);
      }
  }

  return merge.toArray(new Coordinate[merge.size()]);
    }
   
    /**
     * Check if it is a valid intersection between the original geometry and the
     * split line.
     *
     * Test polygons and multiPolygons.
     *
     * 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 and part of split line must intersect with the polygon.
     * -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 geometryToSplit The possible geometry to be split. It should be LineString, MultilineString, Polygon or MultiPolygon.
     *
     * @return True if the split operation will produce a valid resultant
     *         geometry. False in other case.
     */
   
    public static boolean canSplitPolygon(Geometry geometryToSplit, UsefulSplitLineBuilder splitLineBuilder){


        assert geometryToSplit instanceof Polygon || geometryToSplit instanceof MultiPolygon;
       
        LineString originalSplitLine = splitLineBuilder.getOriginalSplitLine();
        // Get the boundary
        Geometry boundary = geometryToSplit.getBoundary();
        // check if the line is contained inside the geometry to be split.
        if (geometryToSplit.contains(originalSplitLine) && !boundary.intersects(originalSplitLine)) {

            // here we could have the sample of closed lines.
            return SplitUtil.isClosedLine(originalSplitLine);
        }

        // special case, closed split lines.
        if (SplitUtil.isClosedLine(originalSplitLine)) {

            return validateClosedLine(originalSplitLine, geometryToSplit);

        } else {
   
            for (int i = 0; i < boundary.getNumGeometries(); i++) {

            Geometry eachBoundary = boundary.getGeometryN(i);
            Geometry intersectionPonts = eachBoundary.intersection(originalSplitLine);
            if (intersectionPonts instanceof LineString || intersectionPonts instanceof MultiLineString) {
 
                continue; // the intersection is a line over the boundary, so it is not valid for split operation
            }
            assert intersectionPonts instanceof MultiPoint || intersectionPonts instanceof Point || intersectionPonts instanceof GeometryCollection : "the intersection with the boundary must be point or multypoint"; //$NON-NLS-1$
 
            if (intersectionPonts.getNumGeometries() >=2 ) {
              // checks if it is possible to build an useful split line
                for (int j = 0; j < geometryToSplit.getNumGeometries(); j++) {
 
                Polygon currentPolygon = (Polygon) geometryToSplit.getGeometryN(j);
   
                AdaptedPolygon adaptedPolygon = new AdaptedPolygon(currentPolygon);
                Geometry usefulSplitLines = splitLineBuilder.createUsefulSplitLine(originalSplitLine, adaptedPolygon);
   
                if (usefulSplitLines != null) {
                    return true; // there is a part of  split line which is useful to cut the polygon
                }
                }
            }
            }
        }
        return false;
 
    }
   
    /**
     * Validates if the given closed split line is valid for the split operation. It must fulfill
     * the next condition: the intersection between the polygons created from the rings (split line
     * ring) and the original geometry must be polygon or multiPolygon.
     *
     * @param splitLine Closed Split line.
     * @param originalGeometry Geometry to split.
     * @return True if it is a valid input for the closed line algorithm.
     */
    private static boolean validateClosedLine( final LineString splitLine, final Geometry originalGeometry ) {

        Geometry multiLines = splitLine.union();

        Polygonizer polygonizer = new Polygonizer();
        polygonizer.add(multiLines);
        Collection<Geometry> polyCollection = polygonizer.getPolygons();

        // if any of the pieces are inside the geometry, that means the closed
        // line will end up forming new polygons, it'll produce an output.
        for( Geometry pieces : polyCollection ) {

            if ((originalGeometry.contains(pieces) || originalGeometry.intersects(pieces))
                    && ((originalGeometry.intersection(pieces) instanceof Polygon) || (originalGeometry
                            .intersection(pieces) instanceof MultiPolygon))) {
                return true;
            }
        }

        return false;
    }       

}
TOP

Related Classes of org.locationtech.udig.tools.geometry.split.SplitUtil

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.