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

Source Code of org.locationtech.udig.tools.geometry.split.SplitStrategy$PolygonSplitter

/* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2012, Refractions Research Inc.
* (C) 2006, Axios Engineering S.L. (Axios)
* (C) 2006, County Council of Gipuzkoa, Department of Environment and Planning
*
* 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.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 org.locationtech.udig.tools.geometry.split.RingExtractor.ResultRingExtractor;

/**
* 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) :  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);
  }
    }
}
TOP

Related Classes of org.locationtech.udig.tools.geometry.split.SplitStrategy$PolygonSplitter

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.