/*
* 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.geomgraph;
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.LinearRing;
import com.revolsys.geometry.model.Location;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.Polygon;
import com.revolsys.geometry.model.TopologyException;
import com.revolsys.geometry.util.Assert;
/**
* @version 1.7
*/
public abstract class EdgeRing {
private final List<DirectedEdge> edges = new ArrayList<>(); // the
// edges for this EdgeRing
protected GeometryFactory geometryFactory;
private final List<EdgeRing> holes = new ArrayList<>(); // a list of
// DirectedEdges
// making up
// this EdgeRing
private boolean isHole;
private final Label label = new Label(Location.NONE); // label stores the
// locations of each
// geometry on the face
// surrounded by this
// ring
private int maxNodeDegree = -1;
private final List<Point> pts = new ArrayList<>();
private LinearRing ring; // the ring created for this EdgeRing
// is its containing shell
private EdgeRing shell; // if non-null, the ring is a hole and this EdgeRing
// EdgeRings
// which
// are holes in this EdgeRing
protected DirectedEdge startDe; // the directed edge which starts the list of
public EdgeRing(final DirectedEdge start, final GeometryFactory geometryFactory) {
this.geometryFactory = geometryFactory;
computePoints(start);
computeRing();
}
public void addHole(final EdgeRing ring) {
this.holes.add(ring);
}
protected void addPoints(final Edge edge, final boolean isForward, final boolean isFirstEdge) {
final int numPoints = edge.getVertexCount();
if (isForward) {
int startIndex = 1;
if (isFirstEdge) {
startIndex = 0;
}
for (int i = startIndex; i < numPoints; i++) {
this.pts.add(edge.getPoint(i));
}
} else { // is backward
int startIndex = numPoints - 2;
if (isFirstEdge) {
startIndex = numPoints - 1;
}
for (int i = startIndex; i >= 0; i--) {
this.pts.add(edge.getPoint(i));
}
}
}
private void computeMaxNodeDegree() {
this.maxNodeDegree = 0;
DirectedEdge de = this.startDe;
do {
final Node node = de.getNode();
final int degree = ((DirectedEdgeStar)node.getEdges()).getOutgoingDegree(this);
if (degree > this.maxNodeDegree) {
this.maxNodeDegree = degree;
}
de = getNext(de);
} while (de != this.startDe);
this.maxNodeDegree *= 2;
}
/**
* Collect all the points from the DirectedEdges of this ring into a contiguous list
*/
protected void computePoints(final DirectedEdge start) {
this.startDe = start;
DirectedEdge de = start;
boolean isFirstEdge = true;
do {
// Assert.isTrue(de != null, "found null Directed Edge");
if (de == null) {
throw new TopologyException("Found null DirectedEdge");
}
if (de.getEdgeRing() == this) {
throw new TopologyException(
"Directed Edge visited twice during ring-building at " + de.getCoordinate());
}
this.edges.add(de);
final Label label = de.getLabel();
Assert.isTrue(label.isArea());
mergeLabel(label);
addPoints(de.getEdge(), de.isForward(), isFirstEdge);
isFirstEdge = false;
setEdgeRing(de, this);
de = getNext(de);
} while (de != this.startDe);
}
/**
* Compute a LinearRing from the point list previously collected.
* Test if the ring is a hole (i.e. if it is CCW) and set the hole flag
* accordingly.
*/
public void computeRing() {
if (this.ring == null) {
this.ring = this.geometryFactory.linearRing(this.pts);
this.isHole = this.ring.isCounterClockwise();
}
}
/**
* This method will cause the ring to be computed.
* It will also check any holes, if they have been assigned.
*/
public boolean containsPoint(final Point p) {
final LinearRing shell = getLinearRing();
final BoundingBox env = shell.getBoundingBox();
if (!env.covers(p)) {
return false;
}
if (!shell.isPointInRing(p)) {
return false;
}
for (final EdgeRing hole : this.holes) {
if (hole.containsPoint(p)) {
return false;
}
}
return true;
}
public Point getCoordinate(final int i) {
return this.pts.get(i);
}
/**
* Returns the list of DirectedEdges that make up this EdgeRing
*/
public List<DirectedEdge> getEdges() {
return this.edges;
}
public Label getLabel() {
return this.label;
}
public LinearRing getLinearRing() {
return this.ring;
}
public int getMaxNodeDegree() {
if (this.maxNodeDegree < 0) {
computeMaxNodeDegree();
}
return this.maxNodeDegree;
}
abstract public DirectedEdge getNext(DirectedEdge de);
public EdgeRing getShell() {
return this.shell;
}
public boolean isHole() {
// computePoints();
return this.isHole;
}
public boolean isIsolated() {
return this.label.getGeometryCount() == 1;
}
public boolean isShell() {
return this.shell == null;
}
protected void mergeLabel(final Label deLabel) {
mergeLabel(deLabel, 0);
mergeLabel(deLabel, 1);
}
/**
* Merge the RHS label from a DirectedEdge into the label for this EdgeRing.
* The DirectedEdge label may be null. This is acceptable - it results
* from a node which is NOT an intersection node between the Geometries
* (e.g. the end node of a LinearRing). In this case the DirectedEdge label
* does not contribute any information to the overall labelling, and is simply skipped.
*/
protected void mergeLabel(final Label deLabel, final int geomIndex) {
final Location loc = deLabel.getLocation(geomIndex, Position.RIGHT);
// no information to be had from this label
if (loc == Location.NONE) {
return;
}
// if there is no current RHS value, set it
if (this.label.getLocation(geomIndex) == Location.NONE) {
this.label.setLocation(geomIndex, loc);
return;
}
}
abstract public void setEdgeRing(DirectedEdge de, EdgeRing er);
public void setInResult() {
DirectedEdge de = this.startDe;
do {
de.getEdge().setInResult(true);
de = de.getNext();
} while (de != this.startDe);
}
public void setShell(final EdgeRing shell) {
this.shell = shell;
if (shell != null) {
shell.addHole(this);
}
}
public Polygon toPolygon(final GeometryFactory geometryFactory) {
final List<LinearRing> rings = new ArrayList<>();
rings.add(getLinearRing());
for (int i = 0; i < this.holes.size(); i++) {
final LinearRing ring = this.holes.get(i).getLinearRing();
rings.add(ring);
}
final Polygon poly = geometryFactory.polygon(rings);
return poly;
}
@Override
public String toString() {
if (this.ring == null) {
return this.pts.toString();
} else {
return this.ring.toString();
}
}
}