public class IncrementalTin extends Object implements IIncrementalTin
The Delaunay Triangulation has several desirable properties and is well documented on the Internet. The TIN produced by this class is meets the Delaunay criterion except in cases where round-off errors due to the limits of floating point calculations result in small deviations from the optimum.
There are three major classes of algorithms for creating a Delaunay Triangulation: sweep-line algorithms, divide-and-conquer, and incremental construction. In the incremental algorithm used for this implementation, vertices are added to the TIN one-at-a time. If a vertex lies inside the convex hull of an existing TIN, it is inserted. If the vertex lies to the exterior, the bounds of the TIN is extended to include it. Delaunay optimality is maintained at each step.
This class was designed to handle cases where the input set includes a large number of vertices. In particular, terrain elevation data sets collected using laser devices (lidar) that typically include multiple millions of data points. With such large input sets, performance and memory-management are critical issues.
Naturally, memory use and performance varies by hardware, operating system, and Java Virtual Machine (HVM). In 2015, testing lidar data under Windows 7 on a computer with a 2.9 GHz Intel i7 processor, 8 gigabytes installed memory, 512 kilobytes of L2 cache memory, and Hotspot JVM, this class routinely delivered a processing rate of 1.1 million vertices per second. Time-complexity for samples smaller than 10 million was nearly linear. Memory use averaged 244 bytes per vertex.
About a third of the memory use by this class when running under Hotspot is due to Java object-related overhead rather than actual data. Software environments such as Java and C# provide automatic garbage collection and memory management. Doing so adds a small amount of memory overhead to each object created. Because the data-size of the objects used to build a TIN (vertices and edges) is also small, this overhead is significant. In a sufficiently large Delaunay Triangulation, the number of edges approaches three per vertex. This implementation uses one object per vertex and two per edge. Although the memory overhead for Java varies for different operating systems and Java Virtual Machines (JVMs), the Hotspot JVM for Windows uses 12 bytes per object. Thus for each vertex, it requires (1+3*2)x12 = 84 bytes of overhead.
The process of inserting a vertex within a TIN requires fewer operations than extending the convex hull of that TIN. If a list of vertices is supplied to the initial add routine, the bootstrap process attempts pick the largest starting triangle that it can without excessive processing. Doing so improves performance and stability of the build process.
Inserting a vertex into a TIN depends on identifying the triangle that contains an insertion vertex (if any). This class uses the Stochastic Lawson's Walk algorithm (SLW) that is most efficient when subsequent vertices tend to be spaced close together. Fortunately, this condition is met by many real-world data collection systems. For example, airborne-lidar systems tend to produce a sequence of samples that are closely spaced in terms of horizontal coordinates because they collect measurements using scanning lasers and storing them in the order they are taken.
Other data sources may not be compliant. Randomly generated data points, in particular, may be problematic. For such data, there may be a performance benefit in using the HilbertSort class to pre-order points before insertion so that sequential spatial autocorrelation is provided by the input data.
One way to judge the degree of sequential spacial autocorrelation in a set of vertices is to view the output of the printDiagnostics() method after building a TIN. Under the entry for the SLW statistics, the "average steps to completion" indicates how many comparisons were needed to locate vertices. If this number is larger than 7 or 8, it may be useful to try using the HilbertSort and see if it improves processing times.
Because of the complex relationships between objects in a TIN, Java garbage collection may require an above-average number of passes to clean up memory when an instance of this class goes out-of-scope. The dispose() method can be used to expedite garbage collection. Once the dispose() method is called on a TIN, it cannot be reused. Do not confuse dispose() with clear().
Because of the unusually demanding performance considerations related to the use of this class, object instances are frequently reused and, thus, are subject to change. Consequently, this implementation provides little protection against improper method calls by applications accessing its data. In particular, applications must never modify an object (such as an edge) obtained from instances of this class. Furthermore, they must assume that any addition or removal of vertices to the TIN may change the internal state of any objects previously obtained.
To better understand the re-use strategy, consider that each time a vertex is added to or removed from a TIN, the set of edges that link vertices changes. Some edges may be removed, others added. Testing with lidar data sets indicates that the present implementation re-uses each edge in the collection a average about 7.5 times while the TIN is being constructed. If the application were to treat edges as immutable, it would have to construct new objects each time a vertex was inserted and many of those edge objects would have to be discarded (and garbage collected) before the entire vertex set was processed. Doing so would substantially degrade the performance of this class.
A good review of point location using a stochastic Lawson's walk is provided by Soukal, R.; Málková, Kolingerová (2012) "Walking algorithms for point location in TIN models", Computational Geoscience 16:853-869.
The Bower-Watson algorithm for point insertion is discussed in Cheng, Siu-Wing; Dey, T.; Shewchuk, J. (2013) "Delaunay mesh generation", CRC Press, Boca Raton, FL. This is a challenging book that provides an overview of both 2D and solid TIN models. Jonathan Shewchuk is pretty much the expert on Delaunay Triangulations and his writings were a valuable resource in the creation of this class. You can also read Bowyer's and Watson's original papers both of which famously appeared in the same issue of the same journal in 1981. See Bowyer, A. (1981) "Computing Dirichlet tesselations", The Computer Journal" Vol 24, No 2., p. 162-166. and Watson, D. (1981) "Computing the N-dimensional tesselation with application to Voronoi Diagrams", The Computer Journal" Vol 24, No 2., p. 167-172.
The point-removal algorithm is due to Devillers. See Devillers, O. (2002), "On deletion in delaunay triangulations", International Journal of Computational Geometry & Applications 12.3 p. 123-2005.
The QuadEdge concept is based on the structure popularized by Guibas, L. and Stolfi, J. (1985) "Primitives for the manipulation of subdivisions and the computation of Voronoi diagrams", ACM Transactions on Graphics, 4(2), 1985, p. 75-123.
The logic for adding constraints to the TIN was adapted from Sloan, S.W. (1993) "A Fast Algorithm for Generating Constrained Delaunay Triangulations", Computers & Structures Vol 47. No 3, 1993, p. 441-450.
Constructor and Description |
---|
IncrementalTin()
Constructs an incremental TIN using numerical thresholds appropriate for
the default nominal point spacing of 1 unit.
|
IncrementalTin(double estimatedPointSpacing)
Constructs an incremental TIN using numerical thresholds appropriate for
the specified nominal point spacing.
|
Modifier and Type | Method and Description |
---|---|
boolean |
add(List<Vertex> list,
IMonitorWithCancellation monitor)
Inserts a list of vertices into the collection of vertices managed by the
TIN.
|
boolean |
add(Vertex v)
Insert a vertex into the collection of vertices managed by the TIN.
|
void |
addConstraints(List<IConstraint> constraints,
boolean restoreConformity)
Adds constraints to the TIN.
|
void |
clear()
Clears all internal state data of the TIN, preparing any allocated
resources for re-use.
|
TriangleCount |
countTriangles()
Performs a survey of the TIN to gather statistics about the triangle
formed during its construction.
|
void |
dispose()
Nullifies all internal data and references, preparing the
instance for garbage collection.
|
Iterable<IQuadEdge> |
edges()
Provides a convenience implementation
that can be used with a Java enhanced-loop statement to access the set
of edges that form the structure of the incremental TIN.
|
Rectangle2D |
getBounds()
Gets the bounds of the TIN.
|
IConstraint |
getConstraint(int index)
Gets the constraint associated with the index, or a null if
no such constraint exists.
|
List<IConstraint> |
getConstraints()
Gets a shallow copy of the list of constraints currently
stored in the TIN.
|
Iterator<IQuadEdge> |
getEdgeIterator()
Gets an iterator for stepping through the collection of edges
currently stored in the TIN.
|
List<IQuadEdge> |
getEdges()
Gets a list of edges currently allocated by an instance.
|
IIntegrityCheck |
getIntegrityCheck()
Gets an implementation of the integrity check interface suitable for
the referenced TIN implementation.
|
IConstraint |
getLinearConstraint(IQuadEdge edge)
Gets the linear constraint associated with the edge, if any.
|
int |
getMaximumEdgeAllocationIndex()
Gets the maximum index of the currently allocated edges.
|
IIncrementalTinNavigator |
getNavigator()
Gets a new instance of the IIncrementalTinNavigator interface.
|
INeighborEdgeLocator |
getNeighborEdgeLocator()
Gets a new instance of a neighbor edge locator implementation.
|
INeighborhoodPointsCollector |
getNeighborhoodPointsCollector()
Gets a new instance of a neighborhood points collector.
|
double |
getNominalPointSpacing()
Gets the nominal point spacing used to determine numerical thresholds for
various proximity and inclusion tests.
|
List<IQuadEdge> |
getPerimeter()
Gets a list of edges currently defining the perimeter of the TIN.
|
IConstraint |
getRegionConstraint(IQuadEdge edge)
Gets the region constraint associated with the edge, if any.
|
QuadEdge |
getStartingEdge()
Obtains an arbitrary edge to serve as the start of a search or traversal
operation.
|
int |
getSyntheticVertexCount()
Gets the number of synthetic vertices added to the TIN.
|
Thresholds |
getThresholds()
Gets the Thresholds object that is associated with this instance.
|
List<Vertex> |
getVertices()
Gets a list of vertices currently stored in the TIN.
|
boolean |
isBootstrapped()
Indicates whether the instance contains sufficient information to
represent a TIN.
|
void |
preAllocateEdges(int nVertices)
Allocates a number of vertices roughly sufficient to represent a TIN
containing the specified number of vertices.
|
void |
printDiagnostics(PrintStream ps)
Print statistics and diagnostic information collected during the TIN
construction process.
|
void |
printEdges(PrintStream ps)
Provides a diagnostic print out of the edges comprising the TIN.
|
boolean |
remove(Vertex vRemove)
Removes the specified vertex from the TIN.
|
void |
setResolutionRuleForMergedVertices(VertexMergerGroup.ResolutionRule resolutionRule)
Specifies a rule for interpreting the Z value of a group of vertices that
were merged due to being coincident, or nearly coincident.
|
Vertex |
splitEdge(IQuadEdge eInput,
double zSplit,
boolean restoreConformity)
Split an existing edge into two at the midpoint, using the
specified zSplit value as the z coordinate for the edge.
|
Iterable<SimpleTriangle> |
triangles()
Provides a convenience implementation
that can be used with a Java enhanced-loop statement to access the set
of SimpleTriangles implicit in the structure of the incremental TIN.
|
Iterable<Vertex> |
vertices()
Provides a convenience implementation
that can be used with a Java enhanced-loop statement to access the set
of vertices stored in an incremental TIN.
|
public IncrementalTin()
public IncrementalTin(double estimatedPointSpacing)
Lidar applications sometimes refer to the point-spacing concept as "nominal pulse spacing", a term that reflects the origin of the data in a laser-based measuring system.
estimatedPointSpacing
- the estimated nominal distance between
points.public boolean add(Vertex v)
add
in interface IIncrementalTin
v
- a valid vertexpublic boolean add(List<Vertex> list, IMonitorWithCancellation monitor)
In the bootstrap phase, three points are chosen at random from the vertex list to create the initial triangle for insertion. The initialization will make a small number of selection attempts and select the triangle with the largest number. In the event that this process does not find three points that are not a suitable choice (as when they are collinear or nearly collinear), the process will be repeated until a valid initial triangle is selected.
Thus, there is a small performance advantage in supplying the vertices using a list that can be accessed efficiently in a random order (see the discussion of the Java API for the List and java.util.RandomAccess interfaces). Once the initial triangle is established, the list will be traversed sequentially to build the TIN and random access considerations will no longer apply.
add
in interface IIncrementalTin
list
- a valid list of vertices to be added to the TIN.monitor
- an optional instance of a monitoring implementation; null
if not usedpublic void preAllocateEdges(int nVertices)
preAllocateEdges
in interface IIncrementalTin
nVertices
- the number of vertices expected to be added to the TIN.public Rectangle2D getBounds()
getBounds
in interface IIncrementalTin
public void printEdges(PrintStream ps)
printEdges
in interface IIncrementalTin
ps
- A valid print stream.public TriangleCount countTriangles()
countTriangles
in interface IIncrementalTin
public List<IQuadEdge> getPerimeter()
Warning: For efficiency purposes, the edges return by this routine are the same objects as those currently being used in the instance. Any modification of the edge objects will damage the TIN. Therefore, applications must not modify the edges returned by this method.
getPerimeter
in interface IIncrementalTin
public void printDiagnostics(PrintStream ps)
printDiagnostics
in interface IIncrementalTin
ps
- A valid instance of a PrintStream to receive the output.public List<IQuadEdge> getEdges()
Warning: For efficiency purposes, the edges return by this routine are the same objects as those currently being used in the instance. Any modification of the edge objects will damage the TIN. Therefore, applications must not modify the edges returned by this method.
getEdges
in interface IIncrementalTin
public Iterator<IQuadEdge> getEdgeIterator()
IIncrementalTin
Note that this loop produces only the "base side" of each edge. To access the counterpart (the side of the edge in the other direction), an application needs to access its dual using the edge's getDual() method.
Warning: For efficiency purposes, the edges returned by this routine are the same objects as those currently being used in the instance. Any modification of the edge objects will damage the TIN. Therefore, applications must not modify the edges returned by this method. Caution: For reasons of efficiency, the iterator does not offer any protection against concurrent modification. Therefore applications using this iterator must never modify the TIN during iteration.
getEdgeIterator
in interface IIncrementalTin
public Iterable<IQuadEdge> edges()
IIncrementalTin
For example, this method could be used in the following manner:
IIncremntal tin = // some implementation for(IQuadEdge e: tin.edges(){ // some processing logic }
Note that this loop produces only the "base side" of each edge. To access the counterpart (the side of the edge in the other direction), an application needs to access its dual using the edge's getDual() method.
Please see the API documentation for getEdgeIterator() for cautions regarding the use of this method.
edges
in interface IIncrementalTin
public int getMaximumEdgeAllocationIndex()
IIncrementalTin
Internally, Tinfour uses edge index values to manage edges in memory. The while there can be small gaps in the indexing sequence, this method provides a way of obtaining the absolute maximum value of currently allocated edges.
getMaximumEdgeAllocationIndex
in interface IIncrementalTin
public double getNominalPointSpacing()
Lidar applications sometimes refer to the point-spacing concept as "nominal pulse spacing", a term that reflects the origin of the data in a laser-based measuring system.
getNominalPointSpacing
in interface IIncrementalTin
public Thresholds getThresholds()
IIncrementalTin
getThresholds
in interface IIncrementalTin
public void dispose()
IIncrementalTin
dispose
in interface IIncrementalTin
public void clear()
IIncrementalTin
clear
in interface IIncrementalTin
public boolean isBootstrapped()
isBootstrapped
in interface IIncrementalTin
public boolean remove(Vertex vRemove)
At this time, this method does not handle the case where all vertices are removed from the TIN.
remove
in interface IIncrementalTin
vRemove
- the vertex to be removedpublic QuadEdge getStartingEdge()
public INeighborEdgeLocator getNeighborEdgeLocator()
getNeighborEdgeLocator
in interface IIncrementalTin
public IIncrementalTinNavigator getNavigator()
IIncrementalTin
Instances observe the contract of the IProcessUsingTin interface in that they access the TIN on a read-only basis and may be used in parallel threads provided that the TIN is not modified.
getNavigator
in interface IIncrementalTin
public INeighborhoodPointsCollector getNeighborhoodPointsCollector()
getNeighborhoodPointsCollector
in interface IIncrementalTin
public IIntegrityCheck getIntegrityCheck()
IIncrementalTin
getIntegrityCheck
in interface IIncrementalTin
public void setResolutionRuleForMergedVertices(VertexMergerGroup.ResolutionRule resolutionRule)
setResolutionRuleForMergedVertices
in interface IIncrementalTin
resolutionRule
- The rule to be used for interpreting merged
vertices.public List<Vertex> getVertices()
getVertices
in interface IIncrementalTin
public void addConstraints(List<IConstraint> constraints, boolean restoreConformity)
IIncrementalTin
Using Constraints
There are a number of important restrictions to the use of constraints. Constraints must only be added to the TIN once, after all other vertices have already been added. Furthermore, the addConstraint method can only be called once. Logic is implemented as a safety measure to ensure that these restrictions are not accidentally violated.
There are also important restrictions on the geometry of constraints. Most importantly, constraints must never intersect each other except at the endpoints of the segments that define them (i.e. segments in constraints must never cross each other). Due to the high cost of processing required to check that this restriction is observed, it is not directly enforced by the Tinfour implementations.
Restoring Conformity
When constraints are added to a Delaunay triangulation, they often violate the Delaunay criterion and result in a non-conforming mesh. The addConstraint method can optionally restore conformity by inserting synthetic points into the the constraint edges. The cost of this process is additional processing time and an increase in the number of points in the TIN.
When points are synthesized, it is necessary to interpolate a value for the z-coordinate. At this time, the specific interpolation process is undefined. The current Tinfour implementations use linear interpolation between constraint points. While no viable alternative approach is currently under consideration, the choice of interpolation method is subject to change in the future.
addConstraints
in interface IIncrementalTin
constraints
- a valid, potentially empty list.restoreConformity
- restores conformitypublic List<IConstraint> getConstraints()
IIncrementalTin
getConstraints
in interface IIncrementalTin
public IConstraint getConstraint(int index)
IIncrementalTin
getConstraint
in interface IIncrementalTin
index
- an arbitrary integer indexpublic IConstraint getRegionConstraint(IQuadEdge edge)
IIncrementalTin
getRegionConstraint
in interface IIncrementalTin
edge
- a valid edge instance.public IConstraint getLinearConstraint(IQuadEdge edge)
IIncrementalTin
getLinearConstraint
in interface IIncrementalTin
edge
- a valid edge instance.public int getSyntheticVertexCount()
IIncrementalTin
getSyntheticVertexCount
in interface IIncrementalTin
public Vertex splitEdge(IQuadEdge eInput, double zSplit, boolean restoreConformity)
IIncrementalTin
WARNING The restoreDelaunay feature is not yet implemented.
splitEdge
in interface IIncrementalTin
eInput
- a valid edgezSplit
- the z coordinate for the new vertexrestoreConformity
- restore Delaunay conformance after
insertion NOT YET IMPLEMENTEpublic Iterable<SimpleTriangle> triangles()
IIncrementalTin
For example, this method could be used in the following manner:
IIncremntal tin = // a valid instance for(SimpleTriangle t: tin.triangles(){ // some processing logic }
Please see the API documentation for SimpleTriangleIterator for cautions regarding the use of this method.
triangles
in interface IIncrementalTin
public Iterable<Vertex> vertices()
IIncrementalTin
For example, this method could be used in the following manner:
IIncremntal tin = // a valid instance for(Vertex v: tin.verticess(){ // some processing logic }
Please see the API documentation for VertexIterator for cautions regarding the use of this method.
vertices
in interface IIncrementalTin
Copyright © 2021. All rights reserved.