Package org.geotools.geometry.iso.operation.overlay

Source Code of org.geotools.geometry.iso.operation.overlay.OverlayOp

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*   
*    (C) 2001-2006  Vivid Solutions
*    (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*   
*    This library is free software; 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 org.geotools.geometry.iso.operation.overlay;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.geotools.geometry.iso.UnsupportedDimensionException;
import org.geotools.geometry.iso.aggregate.AggregateFactoryImpl;
import org.geotools.geometry.iso.operation.GeometryGraphOperation;
import org.geotools.geometry.iso.root.GeometryImpl;
import org.geotools.geometry.iso.topograph2D.Coordinate;
import org.geotools.geometry.iso.topograph2D.Depth;
import org.geotools.geometry.iso.topograph2D.DirectedEdge;
import org.geotools.geometry.iso.topograph2D.DirectedEdgeStar;
import org.geotools.geometry.iso.topograph2D.Edge;
import org.geotools.geometry.iso.topograph2D.EdgeList;
import org.geotools.geometry.iso.topograph2D.Label;
import org.geotools.geometry.iso.topograph2D.Location;
import org.geotools.geometry.iso.topograph2D.Node;
import org.geotools.geometry.iso.topograph2D.PlanarGraph;
import org.geotools.geometry.iso.topograph2D.Position;
import org.geotools.geometry.iso.util.Assert;
import org.geotools.geometry.iso.util.algorithm2D.PointLocator;
import org.opengis.geometry.Geometry;
import org.opengis.geometry.primitive.OrientableCurve;
import org.opengis.geometry.primitive.OrientableSurface;
import org.opengis.geometry.primitive.Point;
import org.opengis.geometry.primitive.Primitive;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

/**
* Computes the overlay of two {@link Geometry}s. The overlay can be used to
* determine any boolean combination of the geometries.
*
*
*
*
* @source $URL$
*/
public class OverlayOp extends GeometryGraphOperation {

  /**
   * The spatial functions supported by this class. These operations implement
   * various boolean combinations of the resultants of the overlay.
   */
  public static final int INTERSECTION = 1;

  public static final int UNION = 2;

  public static final int DIFFERENCE = 3;

  public static final int SYMDIFFERENCE = 4;

  /**
   *
   * @param geom0
   * @param geom1
   * @param opCode
   * @return
   * @throws UnsupportedDimensionException
   */
  public static GeometryImpl overlayOp(GeometryImpl geom0,
      GeometryImpl geom1, int opCode)
      throws UnsupportedDimensionException {
    OverlayOp gov = new OverlayOp(geom0, geom1);
    GeometryImpl geomOv = gov.getResultGeometry(opCode);
    return geomOv;
  }

  /**
   *
   * @param label
   * @param opCode
   * @return
   */
  public static boolean isResultOfOp(Label label, int opCode) {
    int loc0 = label.getLocation(0);
    int loc1 = label.getLocation(1);
    return isResultOfOp(loc0, loc1, opCode);
  }

  /**
   * This method will handle arguments of Location.NONE correctly
   *
   * @param loc0
   * @param loc1
   * @param opCode
   * @return
   */
  public static boolean isResultOfOp(int loc0, int loc1, int opCode) {
    if (loc0 == Location.BOUNDARY)
      loc0 = Location.INTERIOR;
    if (loc1 == Location.BOUNDARY)
      loc1 = Location.INTERIOR;
    switch (opCode) {
    case INTERSECTION:
      return loc0 == Location.INTERIOR && loc1 == Location.INTERIOR;
    case UNION:
      return loc0 == Location.INTERIOR || loc1 == Location.INTERIOR;
    case DIFFERENCE:
      return loc0 == Location.INTERIOR && loc1 != Location.INTERIOR;
    case SYMDIFFERENCE:
      return (loc0 == Location.INTERIOR && loc1 != Location.INTERIOR)
          || (loc0 != Location.INTERIOR && loc1 == Location.INTERIOR);
    }
    return false;
  }

  private final PointLocator ptLocator = new PointLocator();

  //private FeatGeomFactoryImpl geomFeatFactory;
  private CoordinateReferenceSystem crs;

  private GeometryImpl resultGeom;

  private PlanarGraph graph;

  private EdgeList edgeList = new EdgeList();

  private List<OrientableSurface> resultPolyList = new ArrayList<OrientableSurface>();

  private List<OrientableCurve> resultLineList = new ArrayList<OrientableCurve>();

  private List<Point> resultPointList = new ArrayList<Point>();

  /**
   * Initializes a new Overlay Operation between two geometric objects
   *
   * @param g0 First geometric object
   * @param g1 Second geometric object
   *
   * @throws UnsupportedDimensionException
   */
  public OverlayOp(GeometryImpl g0, GeometryImpl g1)
      throws UnsupportedDimensionException {
    super(g0, g1);
    graph = new PlanarGraph(new OverlayNodeFactory());
    /**
     * Use factory of primary geometry. Note that this does NOT handle
     * mixed-precision arguments where the second arg has greater precision
     * than the first.
     */
    //this.geomFeatFactory = g0.getFeatGeometryFactory();
    this.crs = g0.getCoordinateReferenceSystem();
  }

  /**
   * Computes and returns the resulting geometry according
   * to the function code parameter.
   *
   * @param funcCode Function code
   * @return Result geometry
   */
  public GeometryImpl getResultGeometry(int funcCode) {
    computeOverlay(funcCode);
    return resultGeom;
  }

  /**
   *
   * @return
   */
  public PlanarGraph getGraph() {
    return graph;
  }

  /**
   * Compute the overlay according to the given operation code parameter
   *
   * @param opCode Operation code
   */
  private void computeOverlay(int opCode) {

    // copy points from input Geometries.
    // This ensures that any Point geometries
    // in the input are considered for inclusion in the result set
    this.copyPoints(0);
    this.copyPoints(1);

    // node the input Geometries
    this.arg[0].computeSelfNodes(li, false);
    this.arg[1].computeSelfNodes(li, false);

    // compute intersections between edges of the two input geometries
    this.arg[0].computeEdgeIntersections(arg[1], li, true);

    List baseSplitEdges = new ArrayList();
    this.arg[0].computeSplitEdges(baseSplitEdges);
    this.arg[1].computeSplitEdges(baseSplitEdges);

    // add the noded edges to this result graph
    insertUniqueEdges(baseSplitEdges);

    // Labels the Edges
    this.computeLabelsFromDepths();

    this.replaceCollapsedEdges();

    // debugging only
    // NodingValidator nv = new NodingValidator(edgeList.getEdges());
    // nv.checkValid();

    this.graph.addEdges(this.edgeList.getEdges());
    this.computeLabelling();

    this.labelIncompleteNodes();

    /**
     * The ordering of building the result Geometries is important. Areas
     * must be built before lines, which must be built before points. This
     * is so that lines which are covered by areas are not included
     * explicitly, and similarly for points.
     */

    findResultAreaEdges(opCode);
    cancelDuplicateResultEdges();
    PolygonBuilder polyBuilder = new PolygonBuilder(crs, cga);
    polyBuilder.add(this.graph);

    this.resultPolyList = polyBuilder.getPolygons();

    LineBuilder lineBuilder = new LineBuilder(this, crs,
        ptLocator);
    this.resultLineList = lineBuilder.build(opCode);

    PointBuilder pointBuilder = new PointBuilder(this, crs,
        ptLocator);
    this.resultPointList = pointBuilder.build(opCode);

    // gather the results from all calculations into a single Geometry for
    // the result set
    this.resultGeom = this.computeGeometry(resultPointList, resultLineList,
        resultPolyList);
  }

  /**
   *
   * @param edges
   */
  private void insertUniqueEdges(List edges) {
    for (Iterator i = edges.iterator(); i.hasNext();) {
      Edge e = (Edge) i.next();
      this.insertUniqueEdge(e);
    }
  }

  /**
   * Insert an edge from one of the noded input graphs. Checks edges that are
   * inserted to see if an identical edge already exists. If so, the edge is
   * not inserted, but its label is merged with the existing edge.
   */
  protected void insertUniqueEdge(Edge e) {
    // <FIX> MD 8 Oct 03 speed up identical edge lookup
    // fast lookup
    Edge existingEdge = edgeList.findEqualEdge(e);

    // If an identical edge already exists, simply update its label
    if (existingEdge != null) {
      Label existingLabel = existingEdge.getLabel();

      Label labelToMerge = e.getLabel();
      // check if new edge is in reverse direction to existing edge
      // if so, must flip the label before merging it
      if (!existingEdge.isPointwiseEqual(e)) {
        labelToMerge = new Label(e.getLabel());
        labelToMerge.flip();
      }
      Depth depth = existingEdge.getDepth();
      // if this is the first duplicate found for this edge, initialize
      // the depths
      // /*
      if (depth.isNull()) {
        depth.add(existingLabel);
      }
      // */
      depth.add(labelToMerge);
      existingLabel.merge(labelToMerge);
      // Debug.print("inserted edge: "); Debug.println(e);
      // Debug.print("existing edge: "); Debug.println(existingEdge);

    } else { // no matching existing edge was found
      // add this new edge to the list of edges in this graph
      // e.setName(name + edges.size());
      // e.getDepth().add(e.getLabel());
      edgeList.add(e);
    }
  }

  /**
   * If either of the GeometryLocations for the existing label is exactly
   * opposite to the one in the labelToMerge, this indicates a dimensional
   * collapse has happened. In this case, convert the label for that Geometry
   * to a Line label
   */
  /*
   * NOT NEEDED? private void checkDimensionalCollapse(Label labelToMerge,
   * Label existingLabel) { if (existingLabel.isArea() &&
   * labelToMerge.isArea()) { for (int i = 0; i < 2; i++) { if (!
   * labelToMerge.isNull(i) && labelToMerge.getLocation(i, Position.LEFT) ==
   * existingLabel.getLocation(i, Position.RIGHT) &&
   * labelToMerge.getLocation(i, Position.RIGHT) ==
   * existingLabel.getLocation(i, Position.LEFT) ) { existingLabel.toLine(i); } } } }
   */
  /**
   * Update the labels for edges according to their depths. For each edge, the
   * depths are first normalized. Then, if the depths for the edge are equal,
   * this edge must have collapsed into a line edge. If the depths are not
   * equal, update the label with the locations corresponding to the depths
   * (i.e. a depth of 0 corresponds to a Location of EXTERIOR, a depth of 1
   * corresponds to INTERIOR)
   */
  private void computeLabelsFromDepths() {
    for (Iterator it = edgeList.iterator(); it.hasNext();) {
      Edge e = (Edge) it.next();
      Label lbl = e.getLabel();
      Depth depth = e.getDepth();
      /**
       * Only check edges for which there were duplicates, since these are
       * the only ones which might be the result of dimensional collapses.
       */
      if (!depth.isNull()) {
        depth.normalize();
        for (int i = 0; i < 2; i++) {
          if (!lbl.isNull(i) && lbl.isArea() && !depth.isNull(i)) {
            /**
             * if the depths are equal, this edge is the result of
             * the dimensional collapse of two or more edges. It has
             * the same location on both sides of the edge, so it
             * has collapsed to a line.
             */
            if (depth.getDelta(i) == 0) {
              lbl.toLine(i);
            } else {
              /**
               * This edge may be the result of a dimensional
               * collapse, but it still has different locations on
               * both sides. The label of the edge must be updated
               * to reflect the resultant side locations indicated
               * by the depth values.
               */
              Assert
                  .isTrue(!depth.isNull(i, Position.LEFT),
                      "depth of LEFT side has not been initialized");
              lbl.setLocation(i, Position.LEFT, depth
                  .getLocation(i, Position.LEFT));
              Assert
                  .isTrue(!depth.isNull(i, Position.RIGHT),
                      "depth of RIGHT side has not been initialized");
              lbl.setLocation(i, Position.RIGHT, depth
                  .getLocation(i, Position.RIGHT));
            }
          }
        }
      }
    }
  }

  /**
   * If edges which have undergone dimensional collapse are found, replace
   * them with a new edge which is a L edge
   */
  private void replaceCollapsedEdges() {
    List newEdges = new ArrayList();
    for (Iterator it = edgeList.iterator(); it.hasNext();) {
      Edge e = (Edge) it.next();
      if (e.isCollapsed()) {
        // Debug.print(e);
        it.remove();
        newEdges.add(e.getCollapsedEdge());
      }
    }
    edgeList.addAll(newEdges);
  }

  /**
   * Copy all nodes from an arg geometry into this graph. The node label in
   * the arg geometry overrides any previously computed label for that
   * argIndex. (E.g. a node may be an intersection node with a previously
   * computed label of BOUNDARY, but in the original arg Geometry it is
   * actually in the interior due to the Boundary Determination Rule)
   */
  private void copyPoints(int argIndex) {
    for (Iterator i = arg[argIndex].getNodeIterator(); i.hasNext();) {
      Node graphNode = (Node) i.next();
      Node newNode = graph.addNode(graphNode.getCoordinate());
      newNode.setLabel(argIndex, graphNode.getLabel().getLocation(
          argIndex));
    }
  }

  /**
   * Compute initial labelling for all DirectedEdges at each node. In this
   * step, DirectedEdges will acquire a complete labelling (i.e. one with
   * labels for both Geometries) only if they are incident on a node which has
   * edges for both Geometries
   */
  private void computeLabelling() {
    for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext();) {
      Node node = (Node) nodeit.next();
      // if (node.getCoordinate().equals(new Coordinate(222, 100)) )
      // Debug.addWatch(node.getEdges());
      node.getEdges().computeLabelling(arg);
    }
    mergeSymLabels();
    updateNodeLabelling();
  }

  /**
   * For nodes which have edges from only one Geometry incident on them, the
   * previous step will have left their dirEdges with no labelling for the
   * other Geometry. However, the sym dirEdge may have a labelling for the
   * other Geometry, so merge the two labels.
   */
  private void mergeSymLabels() {
    for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext();) {
      Node node = (Node) nodeit.next();
      ((DirectedEdgeStar) node.getEdges()).mergeSymLabels();
      // node.print(System.out);
    }
  }

  private void updateNodeLabelling() {
    // update the labels for nodes
    // The label for a node is updated from the edges incident on it
    // (Note that a node may have already been labelled
    // because it is a point in one of the input geometries)
    for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext();) {
      Node node = (Node) nodeit.next();
      Label lbl = ((DirectedEdgeStar) node.getEdges()).getLabel();
      node.getLabel().merge(lbl);
    }
  }

  /**
   * Incomplete nodes are nodes whose labels are incomplete. (e.g. the
   * location for one Geometry is null). These are either isolated nodes, or
   * nodes which have edges from only a single Geometry incident on them.
   *
   * Isolated nodes are found because nodes in one graph which don't intersect
   * nodes in the other are not completely labelled by the initial process of
   * adding nodes to the nodeList. To complete the labelling we need to check
   * for nodes that lie in the interior of edges, and in the interior of
   * areas.
   * <p>
   * When each node labelling is completed, the labelling of the incident
   * edges is updated, to complete their labelling as well.
   */
  private void labelIncompleteNodes() {
    for (Iterator ni = graph.getNodes().iterator(); ni.hasNext();) {
      Node n = (Node) ni.next();
      Label label = n.getLabel();
      if (n.isIsolated()) {
        if (label.isNull(0))
          labelIncompleteNode(n, 0);
        else
          labelIncompleteNode(n, 1);
      }
      // now update the labelling for the DirectedEdges incident on this
      // node
      ((DirectedEdgeStar) n.getEdges()).updateLabelling(label);
      // n.print(System.out);
    }
  }

  /**
   * Label an isolated node with its relationship to the target geometry.
   */
  private void labelIncompleteNode(Node n, int targetIndex) {
    int loc = ptLocator.locate(n.getCoordinate(), arg[targetIndex]
        .getGeometry());
    n.getLabel().setLocation(targetIndex, loc);
  }

  /**
   * Find all edges whose label indicates that they are in the result area(s),
   * according to the operation being performed. Since we want polygon shells
   * to be oriented CW, choose dirEdges with the interior of the result on the
   * RHS. Mark them as being in the result. Interior Area edges are the result
   * of dimensional collapses. They do not form part of the result area
   * boundary.
   */
  private void findResultAreaEdges(int opCode) {
    for (Iterator it = graph.getEdgeEnds().iterator(); it.hasNext();) {
      DirectedEdge de = (DirectedEdge) it.next();
      // mark all dirEdges with the appropriate label
      Label label = de.getLabel();
      if (label.isArea()
          && !de.isInteriorAreaEdge()
          && isResultOfOp(label.getLocation(0, Position.RIGHT), label
              .getLocation(1, Position.RIGHT), opCode)) {
        de.setInResult(true);
        // Debug.print("in result "); Debug.println(de);
      }
    }
  }

  /**
   * If both a dirEdge and its sym are marked as being in the result, cancel
   * them out.
   */
  private void cancelDuplicateResultEdges() {
    // remove any dirEdges whose sym is also included
    // (they "cancel each other out")
    for (Iterator it = graph.getEdgeEnds().iterator(); it.hasNext();) {
      DirectedEdge de = (DirectedEdge) it.next();
      DirectedEdge sym = de.getSym();
      if (de.isInResult() && sym.isInResult()) {
        de.setInResult(false);
        sym.setInResult(false);
        // Debug.print("cancelled "); Debug.println(de);
        // Debug.println(sym);
      }
    }
  }

  /**
   * This method is used to decide if a point node should be included in the
   * result or not.
   *
   * @return true if the coord point is covered by a result Line or Area
   *         geometry
   */
  public boolean isCoveredByLA(Coordinate coord) {
    if (isCovered(coord, this.resultLineList))
      return true;
    if (isCovered(coord, this.resultPolyList))
      return true;
    return false;
  }

  /**
   * This method is used to decide if an L edge should be included in the
   * result or not.
   *
   * @return true if the coord point is covered by a result Area geometry
   */
  public boolean isCoveredByA(Coordinate coord) {
    if (isCovered(coord, this.resultPolyList))
      return true;
    return false;
  }

  /**
   * @return true if the coord is located in the interior or boundary of a
   *         geometry in the list.
   */
  private boolean isCovered(Coordinate coord, List geomList) {
    for (Iterator it = geomList.iterator(); it.hasNext();) {
      GeometryImpl geom = (GeometryImpl) it.next();
      int loc = this.ptLocator.locate(coord, geom);
      if (loc != Location.EXTERIOR)
        return true;
    }
    return false;
  }

  private GeometryImpl computeGeometry(List<Point> resultPointList,
      List<OrientableCurve> resultLineList, List<OrientableSurface> resultPolyList) {

    List geomList = new ArrayList();

    // element geometries of the result are always in the order P,L,A
//    geomList.addAll(resultPointList);
//    geomList.addAll(resultLineList);
//    geomList.addAll(resultPolyList);

    // build the most specific geometry possible
    //FeatGeomFactoryImpl gf = new FeatGeomFactoryImpl(crs);
    return createGeometry(
        resultPolyList, resultLineList, resultPointList);
  }

  /**
   * Creates a new Geometry object appropriate to the input Primitives. The
   * method will return a Primitive object, if one list contains only one
   * element and the rest is empty. In all other cases, that is that exist
   * more than one Primitive in the lists, the method will return a Complex
   * object.
   *
   * @param aSurfaces
   *            List of Surfaces
   * @param aCurves
   *            List of Curves
   * @param aPoints
   *            List of Points
   * @return a Geometry instance.
   * That is a Point/Curve/Surface if the parameters only contain one point or one curve or one surface.
   * It is a MultiPoint/MultiCurve/MultiSurface if the parameters contain one list with more than two entries and the other two lists are empty.
   * Or it is a MultiPrimitive if the parameters contain a mixture of points, curves and surfaces.
   */
  public GeometryImpl createGeometry(List<OrientableSurface> aSurfaces,
      List<OrientableCurve> aCurves, List<Point> aPoints) {

    int nS = aSurfaces.size();
    int nC = aCurves.size();
    int nP = aPoints.size();
    AggregateFactoryImpl aggregateFactory = new AggregateFactoryImpl(crs);
   
    if (nS + nC + nP == 0)
      // Return null if the sets are empty
      return null;
      //throw new IllegalArgumentException("All Sets are empty");

   
    if (nS == 0) {
     
      if (nC == 0) {
       
        // Surfaces empty, Curves empty, Points not empty
        if (nP == 1) {
         
          // POINT
          return (GeometryImpl) aPoints.get(0);
         
        } else {
         
          // MULTIPOINT
          return (GeometryImpl) aggregateFactory.createMultiPoint(new HashSet(aPoints));
         
        }
      } else if (nP == 0) {
       
        // Surfaces empty, Curves not empty, Points empty
        if (nC == 1) {
         
          // CURVE
         
          return (GeometryImpl) aCurves.get(0);
        } else {
         
          // MULTICURVE
          return (GeometryImpl) aggregateFactory.createMultiCurve(new HashSet(aCurves));
        }
      }

    } else {
     
      if (nC == 0 && nP == 0) {
       
        if (nS == 1) {
         
          // SURFACE
          return (GeometryImpl) aSurfaces.get(0);
         
        } else {
         
          // MULTISURFACE
          return (GeometryImpl) aggregateFactory.createMultiSurface(new HashSet(aSurfaces));
         
        }
       
      }

    }
   
    // All other cases: MULTIPRIMITIVE
    Set<Primitive> tPrimitives = new HashSet<Primitive>();
    tPrimitives.addAll(aSurfaces);
    tPrimitives.addAll(aCurves);
    tPrimitives.addAll(aPoints);
   
    return (GeometryImpl) aggregateFactory.createMultiPrimitive(tPrimitives);

 
 
}
TOP

Related Classes of org.geotools.geometry.iso.operation.overlay.OverlayOp

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.