All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vividsolutions.jts.operation.valid.IsValidOp Maven / Gradle / Ivy

The newest version!


/*
 * The JTS Topology Suite is a collection of Java classes that
 * implement the fundamental operations required to validate a given
 * geo-spatial data set to a known topological specification.
 *
 * Copyright (C) 2001 Vivid Solutions
 *
 * 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; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For more information, contact:
 *
 *     Vivid Solutions
 *     Suite #1A
 *     2328 Government Street
 *     Victoria BC  V8T 5G5
 *     Canada
 *
 *     (250)385-6040
 *     www.vividsolutions.com
 */
package com.vividsolutions.jts.operation.valid;

import java.util.*;
import com.vividsolutions.jts.algorithm.*;
import com.vividsolutions.jts.geomgraph.*;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.util.*;

/**
 * Implements the algorithms required to compute the isValid() method
 * for {@link Geometry}s.
 * See the documentation for the various geometry types for a specification of validity.
 *
 * @version 1.7
 */
public class IsValidOp
{
	/**
	 * Tests whether a {@link Geometry} is valid.
	 * @param geom the Geometry to test
	 * @return true if the geometry is valid
	 */
	public static boolean isValid(Geometry geom)
	{
    IsValidOp isValidOp = new IsValidOp(geom);
    return isValidOp.isValid();
	}
	
  /**
   * Checks whether a coordinate is valid for processing.
   * Coordinates are valid iff their x and y ordinates are in the
   * range of the floating point representation.
   *
   * @param coord the coordinate to validate
   * @return true if the coordinate is valid
   */
  public static boolean isValid(Coordinate coord)
  {
    if (Double.isNaN(coord.x)) return false;
    if (Double.isInfinite(coord.x)) return false;
    if (Double.isNaN(coord.y)) return false;
    if (Double.isInfinite(coord.y)) return false;
    return true;
  }
  /**
   * Find a point from the list of testCoords
   * that is NOT a node in the edge for the list of searchCoords
   *
   * @return the point found, or null if none found
   */
  public static Coordinate findPtNotNode(
                          Coordinate[] testCoords,
                          LinearRing searchRing,
                          GeometryGraph graph)
  {
    // find edge corresponding to searchRing.
    Edge searchEdge = graph.findEdge(searchRing);
    // find a point in the testCoords which is not a node of the searchRing
    EdgeIntersectionList eiList = searchEdge.getEdgeIntersectionList();
    // somewhat inefficient - is there a better way? (Use a node map, for instance?)
    for (int i = 0 ; i < testCoords.length; i++) {
      Coordinate pt = testCoords[i];
      if (! eiList.isIntersection(pt))
        return pt;
    }
    return null;
  }

  private Geometry parentGeometry;  // the base Geometry to be validated
  /**
   * If the following condition is TRUE JTS will validate inverted shells and exverted holes
   * (the ESRI SDE model)
   */
  private boolean isSelfTouchingRingFormingHoleValid = false;
  private TopologyValidationError validErr;

  public IsValidOp(Geometry parentGeometry)
  {
    this.parentGeometry = parentGeometry;
  }

  /**
   * Sets whether polygons using Self-Touching Rings to form
   * holes are reported as valid.
   * If this flag is set, the following Self-Touching conditions
   * are treated as being valid:
   * 
    *
  • the shell ring self-touches to create a hole touching the shell *
  • a hole ring self-touches to create two holes touching at a point *
*

* The default (following the OGC SFS standard) * is that this condition is not valid (false). *

* This does not affect whether Self-Touching Rings * disconnecting the polygon interior are considered valid * (these are considered to be invalid under the SFS, and many other * spatial models as well). * This includes "bow-tie" shells, * which self-touch at a single point causing the interior to * be disconnected, * and "C-shaped" holes which self-touch at a single point causing an island to be formed. * * @param isValid states whether geometry with this condition is valid */ public void setSelfTouchingRingFormingHoleValid(boolean isValid) { isSelfTouchingRingFormingHoleValid = isValid; } /** * Computes the validity of the geometry, * and returns true if it is valid. * * @return true if the geometry is valid */ public boolean isValid() { checkValid(parentGeometry); return validErr == null; } /** * Computes the validity of the geometry, * and if not valid returns the validation error for the geometry, * or null if the geometry is valid. * * @return the validation error, if the geometry is invalid * or null if the geometry is valid */ public TopologyValidationError getValidationError() { checkValid(parentGeometry); return validErr; } private void checkValid(Geometry g) { validErr = null; // empty geometries are always valid! if (g.isEmpty()) return; if (g instanceof Point) checkValid((Point) g); else if (g instanceof MultiPoint) checkValid((MultiPoint) g); // LineString also handles LinearRings else if (g instanceof LinearRing) checkValid( (LinearRing) g); else if (g instanceof LineString) checkValid( (LineString) g); else if (g instanceof Polygon) checkValid( (Polygon) g); else if (g instanceof MultiPolygon) checkValid( (MultiPolygon) g); else if (g instanceof GeometryCollection) checkValid( (GeometryCollection) g); else throw new UnsupportedOperationException(g.getClass().getName()); } /** * Checks validity of a Point. */ private void checkValid(Point g) { checkInvalidCoordinates(g.getCoordinates()); } /** * Checks validity of a MultiPoint. */ private void checkValid(MultiPoint g) { checkInvalidCoordinates(g.getCoordinates()); } /** * Checks validity of a LineString. Almost anything goes for linestrings! */ private void checkValid(LineString g) { checkInvalidCoordinates(g.getCoordinates()); if (validErr != null) return; GeometryGraph graph = new GeometryGraph(0, g); checkTooFewPoints(graph); } /** * Checks validity of a LinearRing. */ private void checkValid(LinearRing g) { checkInvalidCoordinates(g.getCoordinates()); if (validErr != null) return; checkClosedRing(g); if (validErr != null) return; GeometryGraph graph = new GeometryGraph(0, g); checkTooFewPoints(graph); if (validErr != null) return; LineIntersector li = new RobustLineIntersector(); graph.computeSelfNodes(li, true); checkNoSelfIntersectingRings(graph); } /** * Checks the validity of a polygon. * Sets the validErr flag. */ private void checkValid(Polygon g) { checkInvalidCoordinates(g); if (validErr != null) return; checkClosedRings(g); if (validErr != null) return; GeometryGraph graph = new GeometryGraph(0, g); checkTooFewPoints(graph); if (validErr != null) return; checkConsistentArea(graph); if (validErr != null) return; if (! isSelfTouchingRingFormingHoleValid) { checkNoSelfIntersectingRings(graph); if (validErr != null) return; } checkHolesInShell(g, graph); if (validErr != null) return; //SLOWcheckHolesNotNested(g); checkHolesNotNested(g, graph); if (validErr != null) return; checkConnectedInteriors(graph); } private void checkValid(MultiPolygon g) { for (int i = 0; i < g.getNumGeometries(); i++) { Polygon p = (Polygon) g.getGeometryN(i); checkInvalidCoordinates(p); if (validErr != null) return; checkClosedRings(p); if (validErr != null) return; } GeometryGraph graph = new GeometryGraph(0, g); checkTooFewPoints(graph); if (validErr != null) return; checkConsistentArea(graph); if (validErr != null) return; if (! isSelfTouchingRingFormingHoleValid) { checkNoSelfIntersectingRings(graph); if (validErr != null) return; } for (int i = 0; i < g.getNumGeometries(); i++) { Polygon p = (Polygon) g.getGeometryN(i); checkHolesInShell(p, graph); if (validErr != null) return; } for (int i = 0; i < g.getNumGeometries(); i++) { Polygon p = (Polygon) g.getGeometryN(i); checkHolesNotNested(p, graph); if (validErr != null) return; } checkShellsNotNested(g, graph); if (validErr != null) return; checkConnectedInteriors(graph); } private void checkValid(GeometryCollection gc) { for (int i = 0; i < gc.getNumGeometries(); i++) { Geometry g = gc.getGeometryN(i); checkValid(g); if (validErr != null) return; } } private void checkInvalidCoordinates(Coordinate[] coords) { for (int i = 0; i < coords.length; i++) { if (! isValid(coords[i])) { validErr = new TopologyValidationError( TopologyValidationError.INVALID_COORDINATE, coords[i]); return; } } } private void checkInvalidCoordinates(Polygon poly) { checkInvalidCoordinates(poly.getExteriorRing().getCoordinates()); if (validErr != null) return; for (int i = 0; i < poly.getNumInteriorRing(); i++) { checkInvalidCoordinates(poly.getInteriorRingN(i).getCoordinates()); if (validErr != null) return; } } private void checkClosedRings(Polygon poly) { checkClosedRing((LinearRing) poly.getExteriorRing()); if (validErr != null) return; for (int i = 0; i < poly.getNumInteriorRing(); i++) { checkClosedRing((LinearRing) poly.getInteriorRingN(i)); if (validErr != null) return; } } private void checkClosedRing(LinearRing ring) { if (! ring.isClosed() ) { Coordinate pt = null; if (ring.getNumPoints() >= 1) pt = ring.getCoordinateN(0); validErr = new TopologyValidationError( TopologyValidationError.RING_NOT_CLOSED, pt); } } private void checkTooFewPoints(GeometryGraph graph) { if (graph.hasTooFewPoints()) { validErr = new TopologyValidationError( TopologyValidationError.TOO_FEW_POINTS, graph.getInvalidPoint()); return; } } /** * Checks that the arrangement of edges in a polygonal geometry graph * forms a consistent area. * * @param graph * * @see ConsistentAreaTester */ private void checkConsistentArea(GeometryGraph graph) { ConsistentAreaTester cat = new ConsistentAreaTester(graph); boolean isValidArea = cat.isNodeConsistentArea(); if (! isValidArea) { validErr = new TopologyValidationError( TopologyValidationError.SELF_INTERSECTION, cat.getInvalidPoint()); return; } if (cat.hasDuplicateRings()) { validErr = new TopologyValidationError( TopologyValidationError.DUPLICATE_RINGS, cat.getInvalidPoint()); } } /** * Check that there is no ring which self-intersects (except of course at its endpoints). * This is required by OGC topology rules (but not by other models * such as ESRI SDE, which allow inverted shells and exverted holes). * * @param graph the topology graph of the geometry */ private void checkNoSelfIntersectingRings(GeometryGraph graph) { for (Iterator i = graph.getEdgeIterator(); i.hasNext(); ) { Edge e = (Edge) i.next(); checkNoSelfIntersectingRing(e.getEdgeIntersectionList()); if (validErr != null) return; } } /** * Check that a ring does not self-intersect, except at its endpoints. * Algorithm is to count the number of times each node along edge occurs. * If any occur more than once, that must be a self-intersection. */ private void checkNoSelfIntersectingRing(EdgeIntersectionList eiList) { Set nodeSet = new TreeSet(); boolean isFirst = true; for (Iterator i = eiList.iterator(); i.hasNext(); ) { EdgeIntersection ei = (EdgeIntersection) i.next(); if (isFirst) { isFirst = false; continue; } if (nodeSet.contains(ei.coord)) { validErr = new TopologyValidationError( TopologyValidationError.RING_SELF_INTERSECTION, ei.coord); return; } else { nodeSet.add(ei.coord); } } } /** * Tests that each hole is inside the polygon shell. * This routine assumes that the holes have previously been tested * to ensure that all vertices lie on the shell oon the same side of it * (i.e that the hole rings do not cross the shell ring). * In other words, this test is only correct if the ConsistentArea test is passed first. * Given this, a simple point-in-polygon test of a single point in the hole can be used, * provided the point is chosen such that it does not lie on the shell. * * @param p the polygon to be tested for hole inclusion * @param graph a GeometryGraph incorporating the polygon */ private void checkHolesInShell(Polygon p, GeometryGraph graph) { LinearRing shell = (LinearRing) p.getExteriorRing(); //PointInRing pir = new SimplePointInRing(shell); //PointInRing pir = new SIRtreePointInRing(shell); PointInRing pir = new MCPointInRing(shell); for (int i = 0; i < p.getNumInteriorRing(); i++) { LinearRing hole = (LinearRing) p.getInteriorRingN(i); Coordinate holePt = findPtNotNode(hole.getCoordinates(), shell, graph); /** * If no non-node hole vertex can be found, the hole must * split the polygon into disconnected interiors. * This will be caught by a subsequent check. */ if (holePt == null) return; boolean outside = ! pir.isInside(holePt); if ( outside ) { validErr = new TopologyValidationError( TopologyValidationError.HOLE_OUTSIDE_SHELL, holePt); return; } } } /** * Tests that no hole is nested inside another hole. * This routine assumes that the holes are disjoint. * To ensure this, holes have previously been tested * to ensure that: *

    *
  • they do not partially overlap * (checked by checkRelateConsistency) *
  • they are not identical * (checked by checkRelateConsistency) *
*/ private void checkHolesNotNested(Polygon p, GeometryGraph graph) { IndexedNestedRingTester nestedTester = new IndexedNestedRingTester(graph); //SimpleNestedRingTester nestedTester = new SimpleNestedRingTester(arg[0]); //SweeplineNestedRingTester nestedTester = new SweeplineNestedRingTester(arg[0]); for (int i = 0; i < p.getNumInteriorRing(); i++) { LinearRing innerHole = (LinearRing) p.getInteriorRingN(i); nestedTester.add(innerHole); } boolean isNonNested = nestedTester.isNonNested(); if ( ! isNonNested ) { validErr = new TopologyValidationError( TopologyValidationError.NESTED_HOLES, nestedTester.getNestedPoint()); } } /** * Tests that no element polygon is wholly in the interior of another element polygon. *

* Preconditions: *

    *
  • shells do not partially overlap *
  • shells do not touch along an edge *
  • no duplicate rings exist *
* This routine relies on the fact that while polygon shells may touch at one or * more vertices, they cannot touch at ALL vertices. */ private void checkShellsNotNested(MultiPolygon mp, GeometryGraph graph) { for (int i = 0; i < mp.getNumGeometries(); i++) { Polygon p = (Polygon) mp.getGeometryN(i); LinearRing shell = (LinearRing) p.getExteriorRing(); for (int j = 0; j < mp.getNumGeometries(); j++) { if (i == j) continue; Polygon p2 = (Polygon) mp.getGeometryN(j); checkShellNotNested(shell, p2, graph); if (validErr != null) return; } } } /** * Check if a shell is incorrectly nested within a polygon. This is the case * if the shell is inside the polygon shell, but not inside a polygon hole. * (If the shell is inside a polygon hole, the nesting is valid.) *

* The algorithm used relies on the fact that the rings must be properly contained. * E.g. they cannot partially overlap (this has been previously checked by * checkRelateConsistency ) */ private void checkShellNotNested(LinearRing shell, Polygon p, GeometryGraph graph) { Coordinate[] shellPts = shell.getCoordinates(); // test if shell is inside polygon shell LinearRing polyShell = (LinearRing) p.getExteriorRing(); Coordinate[] polyPts = polyShell.getCoordinates(); Coordinate shellPt = findPtNotNode(shellPts, polyShell, graph); // if no point could be found, we can assume that the shell is outside the polygon if (shellPt == null) return; boolean insidePolyShell = CGAlgorithms.isPointInRing(shellPt, polyPts); if (! insidePolyShell) return; // if no holes, this is an error! if (p.getNumInteriorRing() <= 0) { validErr = new TopologyValidationError( TopologyValidationError.NESTED_SHELLS, shellPt); return; } /** * Check if the shell is inside one of the holes. * This is the case if one of the calls to checkShellInsideHole * returns a null coordinate. * Otherwise, the shell is not properly contained in a hole, which is an error. */ Coordinate badNestedPt = null; for (int i = 0; i < p.getNumInteriorRing(); i++) { LinearRing hole = (LinearRing) p.getInteriorRingN(i); badNestedPt = checkShellInsideHole(shell, hole, graph); if (badNestedPt == null) return; } validErr = new TopologyValidationError( TopologyValidationError.NESTED_SHELLS, badNestedPt); } /** * This routine checks to see if a shell is properly contained in a hole. * It assumes that the edges of the shell and hole do not * properly intersect. * * @return null if the shell is properly contained, or * a Coordinate which is not inside the hole if it is not * */ private Coordinate checkShellInsideHole(LinearRing shell, LinearRing hole, GeometryGraph graph) { Coordinate[] shellPts = shell.getCoordinates(); Coordinate[] holePts = hole.getCoordinates(); // TODO: improve performance of this - by sorting pointlists for instance? Coordinate shellPt = findPtNotNode(shellPts, hole, graph); // if point is on shell but not hole, check that the shell is inside the hole if (shellPt != null) { boolean insideHole = CGAlgorithms.isPointInRing(shellPt, holePts); if (! insideHole) { return shellPt; } } Coordinate holePt = findPtNotNode(holePts, shell, graph); // if point is on hole but not shell, check that the hole is outside the shell if (holePt != null) { boolean insideShell = CGAlgorithms.isPointInRing(holePt, shellPts); if (insideShell) { return holePt; } return null; } Assert.shouldNeverReachHere("points in shell and hole appear to be equal"); return null; } private void checkConnectedInteriors(GeometryGraph graph) { ConnectedInteriorTester cit = new ConnectedInteriorTester(graph); if (! cit.isInteriorsConnected()) validErr = new TopologyValidationError( TopologyValidationError.DISCONNECTED_INTERIOR, cit.getCoordinate()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy