/*
* 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.revolsys.geometry.operation.polygonize;
import java.util.ArrayList;
import java.util.List;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.LinearRing;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.PointList;
import com.revolsys.geometry.model.Polygon;
import com.revolsys.geometry.model.coordinates.CoordinatesUtil;
import com.revolsys.geometry.planargraph.DirectedEdge;
/**
* Represents a ring of {@link PolygonizeDirectedEdge}s which form
* a ring of a polygon. The ring may be either an outer shell or a hole.
*
* @version 1.7
*/
class EdgeRing {
private static void addEdge(final LineString coords, final boolean isForward,
final PointList coordList) {
if (isForward) {
for (int i = 0; i < coords.getVertexCount(); i++) {
coordList.add(coords.getPoint(i), false);
}
} else {
for (int i = coords.getVertexCount() - 1; i >= 0; i--) {
coordList.add(coords.getPoint(i), false);
}
}
}
/**
* Find the innermost enclosing shell EdgeRing containing the argument EdgeRing, if any.
* The innermost enclosing ring is the <i>smallest</i> enclosing ring.
* The algorithm used depends on the fact that:
* <br>
* ring A contains ring B iff envelope(ring A) contains envelope(ring B)
* <br>
* This routine is only safe to use if the chosen point of the hole
* is known to be properly contained in a shell
* (which is guaranteed to be the case if the hole does not touch its shell)
*
* @return containing EdgeRing, if there is one
* or null if no containing EdgeRing is found
*/
public static EdgeRing findEdgeRingContaining(final EdgeRing testEr,
final List<EdgeRing> shellList) {
final LinearRing testRing = testEr.getRing();
final BoundingBox testEnv = testRing.getBoundingBox();
Point testPt = testRing.getPoint(0);
if (testPt == null) {
return null;
} else {
EdgeRing minShell = null;
BoundingBox minShellEnv = null;
for (final EdgeRing tryShell : shellList) {
final LinearRing tryShellRing = tryShell.getRing();
final BoundingBox tryShellEnv = tryShellRing.getBoundingBox();
// the hole envelope cannot equal the shell envelope
// (also guards against testing rings against themselves)
if (tryShellEnv.equals(testEnv)) {
continue;
}
// hole must be contained in shell
if (!tryShellEnv.covers(testEnv)) {
continue;
}
testPt = CoordinatesUtil.pointNotInList(testRing, tryShellRing);
boolean isContained = false;
if (tryShellRing.isPointInRing(testPt)) {
isContained = true;
}
// check if this new containing ring is smaller than the current minimum
// ring
if (isContained) {
if (minShell == null || minShellEnv.covers(tryShellEnv)) {
minShell = tryShell;
minShellEnv = minShell.getRing().getBoundingBox();
}
}
}
return minShell;
}
}
/**
* Tests whether a given point is in an array of points.
* Uses a value-based test.
*
* @param pt a {@link Coordinates} for the test point
* @param pts an array of {@link Coordinates}s to test
* @return <code>true</code> if the point is in the array
*
* @deprecated
*/
@Deprecated
public static boolean isInList(final Point pt, final Point[] pts) {
for (final Point pt2 : pts) {
if (pt.equals(pt2)) {
return true;
}
}
return false;
}
/**
* Finds a point in a list of points which is not contained in another list of points
* @param testPts the {@link Coordinates}s to test
* @param pts an array of {@link Coordinates}s to test the input points against
* @return a {@link Coordinates} from <code>testPts</code> which is not in <code>pts</code>,
* or null if there is no coordinate not in the list
*
* @deprecated Use CoordinateArrays.ptNotInList instead
*/
@Deprecated
public static Point ptNotInList(final Point[] testPts, final Point[] pts) {
for (final Point testPt2 : testPts) {
final Point testPt = testPt2;
if (!isInList(testPt, pts)) {
return testPt;
}
}
return null;
}
private final List<DirectedEdge> deList = new ArrayList<>();
private final GeometryFactory factory;
private List<LinearRing> holes;
// cache the following data for efficiency
private LinearRing ring = null;
private Point[] ringPts = null;
public EdgeRing(final GeometryFactory factory) {
this.factory = factory;
}
/**
* Adds a {@link DirectedEdge} which is known to form part of this ring.
* @param de the {@link DirectedEdge} to add.
*/
public void add(final DirectedEdge de) {
this.deList.add(de);
}
/**
* Adds a hole to the polygon formed by this ring.
* @param hole the {@link LinearRing} forming the hole.
*/
public void addHole(final LinearRing hole) {
if (this.holes == null) {
this.holes = new ArrayList<>();
}
this.holes.add(hole);
}
/**
* Computes the list of coordinates which are contained in this ring.
* The coordinatea are computed once only and cached.
*
* @return an array of the {@link Coordinates}s in this ring
*/
private Point[] getCoordinates() {
if (this.ringPts == null) {
final PointList coordList = new PointList();
for (final DirectedEdge de : this.deList) {
final PolygonizeEdge edge = (PolygonizeEdge)de.getEdge();
addEdge(edge.getLine(), de.getEdgeDirection(), coordList);
}
this.ringPts = coordList.toPointArray();
}
return this.ringPts;
}
/**
* Gets the coordinates for this ring as a {@link LineString}.
* Used to return the coordinates in this ring
* as a valid geometry, when it has been detected that the ring is topologically
* invalid.
* @return a {@link LineString} containing the coordinates in this ring
*/
public LineString getLineString() {
getCoordinates();
return this.factory.lineString(this.ringPts);
}
/**
* Computes the {@link Polygon} formed by this ring and any contained holes.
*
* @return the {@link Polygon} formed by this ring and its holes.
*/
public Polygon getPolygon() {
final List<LinearRing> rings = new ArrayList<>();
rings.add(this.ring);
if (this.holes != null) {
rings.addAll(this.holes);
}
final Polygon poly = this.factory.polygon(rings);
return poly;
}
/**
* Returns this ring as a {@link LinearRing}, or null if an Exception occurs while
* creating it (such as a topology problem). Details of problems are written to
* standard output.
*/
public LinearRing getRing() {
if (this.ring != null) {
return this.ring;
}
getCoordinates();
if (this.ringPts.length < 3) {
System.out.println(this.ringPts);
}
try {
this.ring = this.factory.linearRing(this.ringPts);
} catch (final Exception ex) {
System.out.println(this.ringPts);
}
return this.ring;
}
/**
* Tests whether this ring is a hole.
* Due to the way the edges in the polyongization graph are linked,
* a ring is a hole if it is oriented counter-clockwise.
* @return <code>true</code> if this ring is a hole
*/
public boolean isHole() {
final LinearRing ring = getRing();
return ring.isCounterClockwise();
}
/**
* Tests if the {@link LinearRing} ring formed by this edge ring is topologically valid.
*
* @return true if the ring is valid
*/
public boolean isValid() {
getCoordinates();
if (this.ringPts.length <= 3) {
return false;
}
getRing();
return this.ring.isValid();
}
@Override
public String toString() {
if (this.ring == null) {
return this.ringPts.toString();
} else {
return this.ring.toString();
}
}
}