/* * 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.topograph2D; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.geotools.geometry.iso.aggregate.MultiPrimitiveImpl; import org.geotools.geometry.iso.complex.CompositeCurveImpl; import org.geotools.geometry.iso.complex.CompositePointImpl; import org.geotools.geometry.iso.complex.CompositeSurfaceImpl; import org.geotools.geometry.iso.coordinate.DirectPositionImpl; import org.geotools.geometry.iso.primitive.CurveBoundaryImpl; import org.geotools.geometry.iso.primitive.CurveImpl; import org.geotools.geometry.iso.primitive.PointImpl; import org.geotools.geometry.iso.primitive.RingImpl; import org.geotools.geometry.iso.primitive.RingImplUnsafe; import org.geotools.geometry.iso.primitive.SurfaceBoundaryImpl; import org.geotools.geometry.iso.primitive.SurfaceImpl; import org.geotools.geometry.iso.root.GeometryImpl; import org.geotools.geometry.iso.topograph2D.index.EdgeSetIntersector; import org.geotools.geometry.iso.topograph2D.index.SegmentIntersector; import org.geotools.geometry.iso.topograph2D.index.SimpleMonotoneChainSweepLineIntersector; import org.geotools.geometry.iso.topograph2D.util.CoordinateArrays; import org.geotools.geometry.iso.util.Assert; import org.geotools.geometry.iso.util.algorithm2D.LineIntersector; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.aggregate.MultiPrimitive; import org.opengis.geometry.aggregate.MultiSurface; import org.opengis.geometry.primitive.Primitive; import org.opengis.geometry.primitive.Ring; import org.opengis.geometry.primitive.Surface; /** * A GeometryGraph is a graph that models a given Geometry * * * * @source $URL$ */ public class GeometryGraph extends PlanarGraph { /** * This method implements the Boundary Determination Rule for determining * whether a component (node or edge) that appears multiple times in * elements of a MultiGeometry is in the boundary or the interior of the * Geometry <br> * The SFS uses the "Mod-2 Rule", which this function implements <br> * An alternative (and possibly more intuitive) rule would be the "At Most * One Rule": isInBoundary = (componentCount == 1) */ public static boolean isInBoundary(int boundaryCount) { // the "Mod-2 Rule" return boundaryCount % 2 == 1; } public static int determineBoundary(int boundaryCount) { return isInBoundary(boundaryCount) ? Location.BOUNDARY : Location.INTERIOR; } private GeometryImpl parentGeom; // the precision model of the Geometry represented by this graph // private PrecisionModel precisionModel = null; // private int SRID; /** * The lineEdgeMap is a map of the linestring components of the * parentGeometry to the edges which are derived from them. This is used to * efficiently perform findEdge queries */ private Map lineEdgeMap = new HashMap(); // private PrecisionModel newPM = null; /** * If this flag is true, the Boundary Determination Rule will used when * deciding whether nodes are in the boundary or not */ private boolean useBoundaryDeterminationRule = true; private int argIndex; // the index of this geometry as an argument to a // spatial function (used for labelling) private Collection boundaryNodes; private boolean hasTooFewPoints = false; private Coordinate invalidPoint = null; private EdgeSetIntersector createEdgeSetIntersector() { // various options for computing intersections, from slowest to fastest // private EdgeSetIntersector esi = new SimpleEdgeSetIntersector(); // private EdgeSetIntersector esi = new MonotoneChainIntersector(); // private EdgeSetIntersector esi = new NonReversingChainIntersector(); // private EdgeSetIntersector esi = new SimpleSweepLineIntersector(); // private EdgeSetIntersector esi = new MCSweepLineIntersector(); // return new SimpleEdgeSetIntersector(); return new SimpleMonotoneChainSweepLineIntersector(); } public GeometryGraph(int argIndex, GeometryImpl parentGeom) { this.argIndex = argIndex; this.parentGeom = parentGeom; if (parentGeom != null) { add(parentGeom); } } // public GeometryGraph(int argIndex, List<GeometryImpl> parentGeomList) { // this.argIndex = argIndex; // this.parentGeom = null; // if (parentGeomList != null) { // this.addGeometryList(parentGeomList); // } // } public boolean hasTooFewPoints() { return hasTooFewPoints; } public Coordinate getInvalidPoint() { return invalidPoint; } public GeometryImpl getGeometry() { return parentGeom; } public Collection getBoundaryNodes() { if (boundaryNodes == null) boundaryNodes = nodes.getBoundaryNodes(argIndex); return boundaryNodes; } public Coordinate[] getBoundaryPoints() { Collection coll = getBoundaryNodes(); Coordinate[] pts = new Coordinate[coll.size()]; int i = 0; for (Iterator it = coll.iterator(); it.hasNext();) { Node node = (Node) it.next(); pts[i++] = (Coordinate) node.getCoordinate().clone(); } return pts; } public Edge findEdge(CurveImpl line) { return (Edge) lineEdgeMap.get(line); } public void computeSplitEdges(List edgelist) { for (Iterator i = edges.iterator(); i.hasNext();) { Edge e = (Edge) i.next(); // SJ: Will add edges of intersections points to "edgelist" - // without labeling in relation to the other geometry (the labeling // in relation to it´s own geometry is applied) e.eiList.addSplitEdges(edgelist); } } /** * Add a {@link GeometryImpl} to this {@link GeometryGraph} * @param g */ private void add(GeometryImpl g) { // TODO auskommentiert; checken! // if (g.isEmpty()) return; // TODO auskommentiert; checken! // check if this Geometry should obey the Boundary Determination Rule // all collections except MultiSurfaces obey the rule if (g instanceof MultiSurface) useBoundaryDeterminationRule = false; /* * The following classes shall be considered for building a geometry graph in 2d/2.5d: * CompositeCurve * CompositeSurface * Curve * CurveBoundary * Point * Ring * Surface * SurfaceBoundary * MultiPrimitive * MultiPoint * MultiCurve * MultiSurface */ if (g instanceof PointImpl) this.addPoint((PointImpl) g); else if (g instanceof CurveImpl) this.addCurve((CurveImpl) g); else if (g instanceof CurveBoundaryImpl) this.addCurveBoundary((CurveBoundaryImpl) g); else if (g instanceof RingImplUnsafe) { this.addRing((RingImplUnsafe) g); } else if (g instanceof RingImpl) { this.addRing((RingImpl) g); } else if (g instanceof SurfaceImpl) this.addSurface((SurfaceImpl) g); else if (g instanceof SurfaceBoundaryImpl) this.addSurfaceBoundary((SurfaceBoundaryImpl) g); else if (g instanceof CompositePointImpl) this.addCompositePoint((CompositePointImpl) g); else if (g instanceof CompositeCurveImpl) this.addCompositeCurve((CompositeCurveImpl) g); else if (g instanceof CompositeSurfaceImpl) this.addCompositeSurface((CompositeSurfaceImpl) g); else if (g instanceof MultiPrimitiveImpl) this.addMultiPrimitive((MultiPrimitiveImpl) g); else throw new UnsupportedOperationException(g.getClass().getName()); } // /** // * Add a list of geometry objects to the geometry graph // * // * @param aList Geometry list // */ // private void addGeometryList(List aList) { // this.addGeometryIterator(aList.iterator()); // } /** * Add a set of geometry objects to the geometry graph * * @param aSet Geometry list */ private void addGeometrySet(Set aSet) { Iterator aIter = aSet.iterator(); while (aIter.hasNext()) { this.add((GeometryImpl) aIter.next()); } } // /** // * Add a iterator of geometry objects to the geometry graph // * // * @param aIter Geometry Iterator // */ // private void addGeometryIterator(Iterator aIter) { // while (aIter.hasNext()) { // this.add((GeometryImpl) aIter.next()); // } // } /** * Adds a MultiPrimitive to the graph by adding all contained Primitives to the graph individually * @param aMultiPrimitive */ private void addMultiPrimitive(MultiPrimitiveImpl aMultiPrimitive) { // Add all Primitives within the MultiPrimitive this.addGeometrySet(aMultiPrimitive.getElements()); } /** * Add a Point to the graph. */ private void addPoint(PointImpl p) { Coordinate coord = new Coordinate(p.getPosition().getCoordinate()); this.insertPoint(argIndex, coord, Location.INTERIOR); } /** * Adds a ring in clockwise orientation * @param aRing */ private void addRing(Ring aRing) { this.addRing(aRing, Location.EXTERIOR, Location.INTERIOR); } /** * Adds a Ring to the graph. The direction of the ring, i.e. whether it´s * clockwise or counter clockwise oriented, will be calculated and * considered. Thus, the direction of the ring does not matter. The left and * right topological location arguments assume that the ring is oriented CW. * If the ring is in the opposite orientation, the left and right locations * must be interchanged. * * @param aRing * A Ring. The direction of the ring (cw or ccw) does not matter * @param cwLeft * Label for the left side of the ring * @param cwRight * Label for the right side of the ring */ private void addRing(Ring aRing, int cwLeft, int cwRight) { List<DirectPosition> tDPList = ((RingImplUnsafe)aRing).asDirectPositions(); Coordinate[] coord = CoordinateArrays.toCoordinateArray(tDPList); // Remove neighboured identical points coord = CoordinateArrays.removeRepeatedPoints(coord); if (coord.length < 3) { hasTooFewPoints = true; invalidPoint = coord[0]; return; } int left = cwLeft; int right = cwRight; // If Ring is counter clockwise oriented interchange the left and right // side for labelling if (cga.isCCW(coord)) { left = cwRight; right = cwLeft; } Edge e = new Edge(coord, new Label(argIndex, Location.BOUNDARY, left, right)); lineEdgeMap.put(aRing, e); insertEdge(e); // insert the endpoint as a node, to mark that it is on the boundary insertPoint(argIndex, coord[0], Location.BOUNDARY); } /** * Adds a Surface to the Geometry graph * * @param aSurface */ private void addSurface(SurfaceImpl aSurface) { this.addSurfaceBoundary(aSurface.getBoundary()); } /** * Adds a CurveBoundary, e.g. the start and end point by which it is defined * @param aCurveBoundary */ private void addCurveBoundary(CurveBoundaryImpl aCurveBoundary) { // Add start and end point which define the CurveBoundary this.addPoint(aCurveBoundary.getStartPoint()); this.addPoint(aCurveBoundary.getEndPoint()); } /** * Adds a SurfaceBoundary to the Geometry graph * * @param aSurfaceBoundary */ private void addSurfaceBoundary(SurfaceBoundaryImpl aSurfaceBoundary) { // Add exterior Ring of the Surface Boundary this.addRing(aSurfaceBoundary.getExterior(), Location.EXTERIOR, Location.INTERIOR); // Add interior Rings of the Surface Boundary Iterator<Ring> tInteriorIter = aSurfaceBoundary.getInteriors() .iterator(); while (tInteriorIter.hasNext()) { this.addRing(tInteriorIter.next(), Location.INTERIOR, Location.EXTERIOR); } } /** * Add a Curve to the graph * @param aCurve */ private void addCurve(CurveImpl aCurve) { List<DirectPosition> directPositions = aCurve .asDirectPositions(); Coordinate[] coordinateArray = CoordinateArrays.toCoordinateArray(directPositions); Coordinate[] coord = CoordinateArrays.removeRepeatedPoints(coordinateArray); if (coord.length < 2) { hasTooFewPoints = true; invalidPoint = coord[0]; return; } // add the edge for the LineString // line edges do not have locations for their left and right sides Edge e = new Edge(coord, new Label(argIndex, Location.INTERIOR)); lineEdgeMap.put(aCurve, e); insertEdge(e); /** * Add the boundary points of the LineString, if any. Even if the * LineString is closed, add both points as if they were endpoints. This * allows for the case that the node already exists and is a boundary * point. */ Assert.isTrue(coord.length >= 2, "found LineString with single point"); insertBoundaryPoint(argIndex, coord[0]); insertBoundaryPoint(argIndex, coord[coord.length - 1]); } /** * Adds the Point of a CompositePoint to the Graph. * Handling as usual Point. * * @param aCompositePoint */ private void addCompositePoint(CompositePointImpl aCompositePoint) { List<Primitive> elements = (List<Primitive>) aCompositePoint.getElements(); if (elements.size() != 1) throw new IllegalArgumentException("CompositePoint has to hold exactly one Point"); // Use addPoint-method this.addPoint((PointImpl) elements.get(0)); } /** * Adds a CompositeCurve to the graph by adding each Curve separately * * @param aCompositeCurve */ private void addCompositeCurve(CompositeCurveImpl aCompositeCurve) { Iterator<? extends Primitive> elements = aCompositeCurve.getElements().iterator(); while (elements.hasNext()) { this.addCurve((CurveImpl) elements.next()); } } /** * Adds a CompositeSurface to the graph by adding each Surface separately * * @param aCompositeSurface */ private void addCompositeSurface(CompositeSurfaceImpl aCompositeSurface) { Iterator<? extends Primitive> elements = aCompositeSurface.getElements().iterator(); while (elements.hasNext()) { this.addSurface((SurfaceImpl) elements.next()); } } /** * Add an Edge computed externally. The label on the Edge is assumed to be * correct. */ public void addEdge(Edge e) { insertEdge(e); Coordinate[] coord = e.getCoordinates(); // insert the endpoint as a node, to mark that it is on the boundary insertPoint(argIndex, coord[0], Location.BOUNDARY); insertPoint(argIndex, coord[coord.length - 1], Location.BOUNDARY); } /** * Add a point computed externally. The point is assumed to be a Point * Geometry part, which has a location of INTERIOR. */ public void addPoint(Coordinate pt) { insertPoint(argIndex, pt, Location.INTERIOR); } /** * Compute self-nodes, taking advantage of the Geometry type to minimize the * number of intersection tests. (E.g. rings are not tested for * self-intersection, since they are assumed to be valid). * * @param li * the LineIntersector to use * @param computeRingSelfNodes * if <false>, intersection checks are optimized to not test * rings for self-intersection * @return the SegmentIntersector used, containing information about the * intersections found */ public SegmentIntersector computeSelfNodes(LineIntersector li, boolean computeRingSelfNodes) { SegmentIntersector si = new SegmentIntersector(li, true, false); // get an instance for an edgeSetIntersector (here: MC Sweepline // Intersector) EdgeSetIntersector esi = this.createEdgeSetIntersector(); // optimized test for Polygons and Rings if (!computeRingSelfNodes && (parentGeom instanceof Ring || parentGeom instanceof Surface || parentGeom instanceof MultiPrimitive)) { // TODO auskommentiert; checken! // && (parentGeom instanceof LinearRing // || parentGeom instanceof Polygon // || parentGeom instanceof MultiPolygon) // Compute Intersections without self-intersections esi.computeIntersections(edges, si, false); } else { // Compute Intersections with self-intersections esi.computeIntersections(edges, si, true); } // System.out.println("SegmentIntersector # tests = " + si.numTests); addSelfIntersectionNodes(argIndex); return si; } /** * * @param g * @param li * @param includeProper * @return */ public SegmentIntersector computeEdgeIntersections(GeometryGraph g, LineIntersector li, boolean includeProper) { SegmentIntersector si = new SegmentIntersector(li, includeProper, true); si.setBoundaryNodes(this.getBoundaryNodes(), g.getBoundaryNodes()); EdgeSetIntersector esi = createEdgeSetIntersector(); esi.computeIntersections(edges, g.edges, si); /* * for (Iterator i = g.edges.iterator(); i.hasNext();) { Edge e = (Edge) * i.next(); Debug.print(e.getEdgeIntersectionList()); } */ return si; } /** * Inserts a single point into the graph * @param argIndex Index of the geometry in relation to all input geometries (starts with 0, 1, ...) * @param coord Coordinate which represents the point * @param onLocation Initial Location of the point */ private void insertPoint(int argIndex, Coordinate coord, int onLocation) { Node n = nodes.addNode(coord); Label lbl = n.getLabel(); if (lbl == null) { n.label = new Label(argIndex, onLocation); } else lbl.setLocation(argIndex, onLocation); } /** * Adds points using the mod-2 rule of SFS. This is used to add the boundary * points of dim-1 geometries (Curves/MultiCurves). According to the SFS, an * endpoint of a Curve is on the boundary iff if it is in the boundaries of * an odd number of Geometries */ private void insertBoundaryPoint(int argIndex, Coordinate coord) { Node n = nodes.addNode(coord); Label lbl = n.getLabel(); // the new point to insert is on a boundary int boundaryCount = 1; // determine the current location for the point (if any) int loc = Location.NONE; if (lbl != null) loc = lbl.getLocation(argIndex, Position.ON); if (loc == Location.BOUNDARY) boundaryCount++; // determine the boundary status of the point according to the Boundary // Determination Rule int newLoc = determineBoundary(boundaryCount); lbl.setLocation(argIndex, newLoc); } private void addSelfIntersectionNodes(int argIndex) { for (Iterator i = edges.iterator(); i.hasNext();) { Edge e = (Edge) i.next(); int eLoc = e.getLabel().getLocation(argIndex); for (Iterator eiIt = e.eiList.iterator(); eiIt.hasNext();) { EdgeIntersection ei = (EdgeIntersection) eiIt.next(); addSelfIntersectionNode(argIndex, ei.coord, eLoc); } } } /** * Add a node for a self-intersection. If the node is a potential boundary * node (e.g. came from an edge which is a boundary) then insert it as a * potential boundary node. Otherwise, just add it as a regular node. */ private void addSelfIntersectionNode(int argIndex, Coordinate coord, int loc) { // if this node is already a boundary node, don't change it if (isBoundaryNode(argIndex, coord)) return; if (loc == Location.BOUNDARY && useBoundaryDeterminationRule) insertBoundaryPoint(argIndex, coord); else insertPoint(argIndex, coord, loc); } }