package delaunay_triangulation;
import java.awt.*;
import java.util.Iterator;
/**
* Created by IntelliJ IDEA.
* User: Aviad Segev
* Date: 22/11/2009
* Time: 20:10:04
*
* Grid Index is a simple spatial index for fast point/triangle location.
* The idea is to divide a predefined geographic extent into equal sized
* cell matrix (tiles). Every cell will be associated with a triangle which lies inside.
* Therfore, one can easily locate a triangle in close proximity of the required
* point by searching from the point's cell triangle. If the triangulation is
* more or less uniform and bound in space, this index is very effective,
* roughly recuing the searched triangles by square(xCellCount * yCellCount),
* as only the triangles inside the cell are searched.
*
* The index takes xCellCount * yCellCount capacity. While more cells allow
* faster searches, even a small grid is helpfull.
*
* This implementation holds the cells in a memory matrix, but such a grid can
* be easily mapped to a DB table or file where it is usually used for it's fullest.
*
* Note that the index is geographically bound - only the region given in the
* c'tor is indexed. Added Triangles outside the indexed region will cause rebuilding of
* the whole index. Since triangulation is mostly always used for static raster data,
* and usually is never updated outside the initial zone (only refininf existing triangles)
* this is never an issue in real life.
*/
public class GridIndex
{
/**
* The triangulation of the index
*/
private Delaunay_Triangulation indexDelaunay;
/**
* Horizontal geographic size of a cell index
*/
private double x_size;
/**
* Vertical geographic size of a cell inedx
*/
private double y_size;
/**
* The indexed geographic size
*/
private BoundingBox indexRegion;
/**
* A division of indexRegion to a cell matrix, where each cell holds a triangle
* which lies in it
*/
private Triangle_dt[][] grid;
/**
* Constructs a grid index holding the triangles of a delaunay triangulation.
* This version uses the bounding box of the triangulation as the region to index.
*
* @param delaunay delaunay triangulation to index
* @param xCellCount number of grid cells in a row
* @param yCellCount number of grid cells in a column
*/
public GridIndex( Delaunay_Triangulation delaunay, int xCellCount, int yCellCount)
{
this(delaunay, xCellCount, yCellCount, delaunay.getBoundingBox());
}
/**
* Constructs a grid index holding the triangles of a delaunay triangulation.
* The grid will be made of (xCellCount * yCellCount) cells.
* The smaller the cells the less triangles that fall in them, whuch means better
* indexing, but also more cells in the index, which mean more storage.
* The smaller the indexed region is, the smaller the cells can be and still
* maintain the same capacity, but adding geometries outside the initial region
* will invalidate the index !
*
* @param delaunay delaunay triangulation to index
* @param xCellCount number of grid cells in a row
* @param yCellCount number of grid cells in a column
* @param region geographic region to index
*/
public GridIndex( Delaunay_Triangulation delaunay, int xCellCount,
int yCellCount, BoundingBox region)
{
init(delaunay, xCellCount, yCellCount, region);
}
/**
* Initialize the grid index
* @param delaunay delaunay triangulation to index
* @param xCellCount number of grid cells in a row
* @param yCellCount number of grid cells in a column
* @param region geographic region to index
*/
private void init(Delaunay_Triangulation delaunay, int xCellCount, int yCellCount, BoundingBox region)
{
indexDelaunay = delaunay;
indexRegion = region;
x_size = region.getWidth() / yCellCount;
y_size = region.getHeight() / xCellCount;
// The grid will hold a trinagle for each cell, so a point (x,y) will lie
// in the cell representing the grid partition of region to a
// xCellCount on yCellCount grid
grid = new Triangle_dt[xCellCount][yCellCount];
Triangle_dt colStartTriangle = indexDelaunay.find(middleOfCell(0,0));
updateCellValues(0, 0, xCellCount-1, yCellCount-1, colStartTriangle);
}
/**
* Finds a triangle near the given point
* @param point a query point
* @return a triangle at the same cell of the point
*/
public Triangle_dt findCellTriangleOf(Point_dt point) {
int x_index = (int) ((point.x() - indexRegion.minX()) / x_size);
int y_index = (int) ((point.y() - indexRegion.minY()) / y_size);
return grid[x_index][y_index];
}
/**
* Updates the grid index to reflect changes to the triangulation. Note that added
* triangles outside the indexed region will force to recompute the whole index
* with the enlarged region.
*/
/**
* Updates the grid index to reflect changes to the triangulation. Note that added
* triangles outside the indexed region will force to recompute the whole index
* with the enlarged region.
*
* @param updatedTriangles changed triangles of the triangulation. This may be added triangles,
* removed triangles or both. All that matter is that they cover the
* changed area.
*/
public void updateIndex(Iterator<Triangle_dt> updatedTriangles) {
// Gather the bounding box of the updated area
BoundingBox updatedRegion = new BoundingBox();
while(updatedTriangles.hasNext()) {
updatedRegion = updatedRegion.unionWith(updatedTriangles.next().getBoundingBox());
}
if(updatedRegion.isNull()) // No update...
return;
// Bad news - the updated region lies outside the indexed region.
// The whole index must be recalculated
if(!indexRegion.contains(updatedRegion)) {
init(indexDelaunay,
(int) (indexRegion.getWidth() / x_size),
(int)(indexRegion.getHeight() / y_size),
indexRegion.unionWith(updatedRegion));
}
else {
// Find the cell region to be updated
Point minInvalidCell = getCellOf(updatedRegion.getMinPoint());
Point maxInvalidCell = getCellOf(updatedRegion.getMaxPoint());
// And update it with fresh triangles
Triangle_dt adjacentValidTriangle = findValidTriangle(minInvalidCell);
updateCellValues(minInvalidCell.x, minInvalidCell.y, maxInvalidCell.x, maxInvalidCell.y, adjacentValidTriangle);
}
}
private void updateCellValues(int startXCell, int startYCell, int lastXCell, int lastYCell, Triangle_dt startTriangle)
{
// Go over each grid cell and locate a triangle in it to be the cell's
// starting search triangle. Since we only pass between adjacent cells
// we can search from the last triangle found and not from the start.
// Add triangles for each column cells
for(int i = startXCell; i <= lastXCell; i++)
{
// Find a triangle at the begining of the current column
startTriangle = indexDelaunay.find(middleOfCell(i, startYCell), startTriangle);
grid[i][startYCell] = startTriangle;
Triangle_dt prevRowTriangle = startTriangle;
// Add triangles for the next row cells
for(int j = startYCell+1; j <= lastYCell; j++)
{
grid[i][j] = indexDelaunay.find(middleOfCell(i,j), prevRowTriangle);
prevRowTriangle = grid[i][j];
}
}
}
/**
* Finds a valid (existing) trinagle adjacent to a given invalid cell
* @param minInvalidCell minimum bounding box invalid cell
* @return a valid triangle adjacent to the invalid cell
*/
private Triangle_dt findValidTriangle(Point minInvalidCell)
{
// If the invalid cell is the minimal one in the grid we are forced to search the
// triangulation for a trinagle at that location
if(minInvalidCell.x == 0 && minInvalidCell.y == 0)
return indexDelaunay.find(middleOfCell(minInvalidCell.x,minInvalidCell.y), null);
else
// Otherwise we can take an adjacent cell triangle that is still valid
return grid[Math.min(0, minInvalidCell.x)][Math.min(0, minInvalidCell.y)];
}
/**
* Locates the grid cell point covering the given coordinate
* @param coordinate world coordinate to locate
* @return cell covering the coordinate
*/
private Point getCellOf(Point_dt coordinate)
{
int xCell = (int) ((coordinate.x() - indexRegion.minX()) / x_size);
int yCell = (int) ((coordinate.y() - indexRegion.minY()) / y_size);
return new Point(xCell, yCell);
}
/**
* Create a point at the center of a cell
* @param x_index horizontal cell index
* @param y_index vertical cell index
* @return Point at the center of the cell at (x_index, y_index)
*/
private Point_dt middleOfCell(int x_index, int y_index)
{
double middleXCell = indexRegion.minX() + x_index * x_size + x_size/2;
double middleYCell = indexRegion.minY() + y_index * y_size + y_size/2;
return new Point_dt(middleXCell, middleYCell);
}
}