/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-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.referencing.operation.builder;
import org.opengis.geometry.DirectPosition;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Generates TIN with respect to delaunay criterion. It
* means that there are no alien vertices in the circumcircle of each
* triangle. The algorithm that is used is also known as incremental insertion.
*
* @since 2.4
* @source $URL$
* @version $Id$
* @author Jan Jezek
*/
class TriangulationFactory {
/** The list of TINTriangles in the TIN. */
private List <TINTriangle> triangles;
/**
* Constructs the TriangulationFactory.
*
* @param quad of location to be triangulated.
* @param pt Array of points for triangulation.
* @throws TriangulationException when the vertices are outside of the specified quad.
*/
protected TriangulationFactory(Quadrilateral quad, DirectPosition[] pt)
throws TriangulationException {
List <DirectPosition> vertices = new ArrayList<DirectPosition>();
for (int i = 0; i < pt.length; i++) {
vertices.add(pt[i]);
}
if (quad.containsAll(vertices) == false) {
throw new TriangulationException("Point is outside triangles");
}
this.triangles = quad.getTriangles();
for (Iterator<DirectPosition> i = vertices.iterator(); i.hasNext();) {
DirectPosition vertex = (DirectPosition) i.next();
insertPoint(vertex);
}
}
/**
* Set a List of points for triangulation
*
* @return TIN as list of triangles.
*/
public List <TINTriangle> getTriangulation() {
return triangles;
}
/**
* This is the test loop, that starts to tests of triangles until
* the result of insertation of triangle evokes changes in TIN.
*
* @param ChangedTriangles List of changed triangles
*
* @throws TriangulationException TriangulationException
*/
protected void recursiveDelaunayTest(List ChangedTriangles)
throws TriangulationException {
int i = ChangedTriangles.size();
while (i != 0) {
triangles.removeAll(ChangedTriangles);
ChangedTriangles = insertTriangles(ChangedTriangles);
i = ChangedTriangles.size();
}
}
/**
* Decides whether to insert triangle directly or go throught
* delaunay test.
*
* @param trian if triangles to be inserted
*
* @return List of changed triangles
*
* @throws TriangulationException TriangulationException
*/
protected List insertTriangles(List trian) throws TriangulationException {
List ChangedTriangles = new ArrayList();
for (Iterator i = trian.iterator(); i.hasNext();) {
TINTriangle trig = (TINTriangle) i.next();
if (trig.getAdjacentTriangles().size() <= 2) {
// this triangles were generate by splitting one of a boundary triangle
triangles.add(trig);
} else {
ChangedTriangles.addAll(delaunayCircleTest(trig));
}
}
return ChangedTriangles;
}
/**
* Tests whether there is a alien vertex in the circumcircle of
* triangle. When there is, the diagonal of quad made by these triangles
* changes.
*
* @param triangle to be tested
*
* @return List of changed triangles
*
* @throws TriangulationException DOCUMENT ME!
*/
private List delaunayCircleTest(TINTriangle triangle)
throws TriangulationException {
List changedTriangles = new ArrayList();
Iterator j = triangle.getAdjacentTriangles().iterator();
int ct = changedTriangles.size();
while (j.hasNext() && (changedTriangles.size() == ct)) {
TINTriangle adjacent = (TINTriangle) j.next();
List NewTriangles = new ArrayList();
// The delaunay test
if (triangle.getCircumCicle().contains(adjacent.p1)
|| triangle.getCircumCicle().contains(adjacent.p0)
|| triangle.getCircumCicle().contains(adjacent.p2)) {
triangles.remove(triangle);
triangles.remove(adjacent);
NewTriangles.addAll(alternativeTriangles(triangle, adjacent));
triangles.addAll(NewTriangles);
changedTriangles = NewTriangles;
} else if (!triangles.contains(triangle)) {
triangles.add(triangle);
}
}
return changedTriangles;
}
/**
* Accommodate new vertex into the existing triangles.
*
* @param newVertex new vertex
*
* @throws TriangulationException when {@code newVertex} is outside triangles
*/
public void insertPoint(DirectPosition newVertex)
throws TriangulationException {
TINTriangle triangleContainingNewVertex = triangleContains(newVertex);
if (triangleContainingNewVertex == null) {
throw new TriangulationException("Point is outside triangles");
}
triangles.remove(triangleContainingNewVertex);
recursiveDelaunayTest(triangleContainingNewVertex.subTriangles(
newVertex));
}
/**
* Changes the diagonal of the quad generated from two
* adjacent triangles.
*
* @param ABC triangle sharing an edge with BCD
* @param BCD triangle sharing an edge with ABC
*
* @return triangles ABD and ADC, or null if ABCD is not convex
*
* @throws TriangulationException if {@code ABC} and {@code BCD} are not adjacent
*/
private List alternativeTriangles(TINTriangle ABC, TINTriangle BCD)
throws TriangulationException {
ArrayList ABCvertices = new ArrayList();
ArrayList BCDvertices = new ArrayList();
ABCvertices.add(ABC.p0);
ABCvertices.add(ABC.p1);
ABCvertices.add(ABC.p2);
BCDvertices.add(BCD.p0);
BCDvertices.add(BCD.p1);
BCDvertices.add(BCD.p2);
ArrayList sharedVertices = new ArrayList();
ArrayList unsharedVertices = new ArrayList();
// Finds shared and unshared vertices
for (Iterator i = ABCvertices.iterator(); i.hasNext();) {
DirectPosition vertex = (DirectPosition) i.next();
if (!BCDvertices.contains(vertex)) {
unsharedVertices.add(vertex);
} else if (BCDvertices.contains(vertex)) {
sharedVertices.add(vertex);
BCDvertices.remove(vertex);
} else {
throw new TriangulationException("should never reach here");
}
}
unsharedVertices.addAll(BCDvertices);
if (sharedVertices.size() < 2) {
throw new TriangulationException(
"Unable to make alternative triangles");
}
// remove Adjacent from original triangles
ABC.removeAdjacent(BCD);
BCD.removeAdjacent(ABC);
// new triangles are generated
TINTriangle trigA = new TINTriangle((DirectPosition) sharedVertices.get(
0), (DirectPosition) unsharedVertices.get(0),
(DirectPosition) unsharedVertices.get(1));
TINTriangle trigB = new TINTriangle((DirectPosition) unsharedVertices
.get(0), (DirectPosition) unsharedVertices.get(1),
(DirectPosition) sharedVertices.get(1));
// Adjacent triangles are added
trigA.addAdjacentTriangle(trigB);
trigB.addAdjacentTriangle(trigA);
trigA.tryToAddAdjacent(BCD.getAdjacentTriangles());
trigA.tryToAddAdjacent(ABC.getAdjacentTriangles());
trigB.tryToAddAdjacent(BCD.getAdjacentTriangles());
trigB.tryToAddAdjacent(ABC.getAdjacentTriangles());
List list = new ArrayList();
list.add(trigA);
list.add(trigB);
// Adjacent triangles of adjacent triangles are modified.
for (Iterator i = ABC.getAdjacentTriangles().iterator(); i.hasNext();) {
TINTriangle trig = (TINTriangle) i.next();
trig.removeAdjacent(ABC);
trig.tryToAddAdjacent(list);
}
for (Iterator i = BCD.getAdjacentTriangles().iterator(); i.hasNext();) {
TINTriangle trig = (TINTriangle) i.next();
trig.removeAdjacent(BCD);
trig.tryToAddAdjacent(list);
}
return list;
}
/**
* Returns the TINTriangle that contains the p Coordinate.
*
* @param p The Coordinate to be tested
*
* @return the triangle containing p, or null if there is no triangle that
* contains p
*/
private TINTriangle triangleContains(DirectPosition p) {
for (Iterator i = triangles.iterator(); i.hasNext();) {
TINTriangle triangle = (TINTriangle) i.next();
if (triangle.containsOrIsVertex(p)) {
return triangle;
}
}
return null;
}
}