/* This program 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 3 of
the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package org.opentripplanner.common.geometry;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.vividsolutions.jts.geom.Coordinate;
/**
* A generic indexed grid of Z samples.
*
* Internally use a SparseMatrix to store samples.
*
* @author laurent
*/
public final class SparseMatrixZSampleGrid<TZ> implements ZSampleGrid<TZ>,
DelaunayTriangulation<TZ> {
private final class SparseMatrixSamplePoint implements ZSamplePoint<TZ>, DelaunayPoint<TZ> {
private int x;
private int y;
private TZ z;
private SparseMatrixSamplePoint up, down, right, left;
private GridDelaunayEdge eUp, eUpRight, eRight;
@Override
public ZSamplePoint<TZ> up() {
return up;
}
@Override
public ZSamplePoint<TZ> down() {
return down;
}
@Override
public ZSamplePoint<TZ> right() {
return right;
}
@Override
public ZSamplePoint<TZ> left() {
return left;
}
@Override
public Coordinate getCoordinates() {
return SparseMatrixZSampleGrid.this.getCoordinates(this);
}
@Override
public int getX() {
return this.x;
}
@Override
public int getY() {
return this.y;
}
@Override
public TZ getZ() {
return this.z;
}
@Override
public void setZ(TZ z) {
this.z = z;
}
}
private final class GridDelaunayEdge implements DelaunayEdge<TZ> {
private static final int TYPE_VERTICAL = 0;
private static final int TYPE_HORIZONTAL = 1;
private static final int TYPE_DIAGONAL = 2;
private boolean processed;
private SparseMatrixSamplePoint A, B;
private GridDelaunayEdge ccw1, ccw2, cw1, cw2;
private int type;
private GridDelaunayEdge(SparseMatrixSamplePoint A, SparseMatrixSamplePoint B, int type) {
this.A = A;
this.B = B;
switch (type) {
case TYPE_HORIZONTAL:
A.eRight = this;
break;
case TYPE_VERTICAL:
A.eUp = this;
break;
case TYPE_DIAGONAL:
A.eUpRight = this;
break;
}
this.type = type;
}
@Override
public DelaunayPoint<TZ> getA() {
return A;
}
@Override
public DelaunayPoint<TZ> getB() {
return B;
}
@Override
public DelaunayEdge<TZ> getEdge1(boolean ccw) {
return ccw ? ccw1 : cw1;
}
@Override
public DelaunayEdge<TZ> getEdge2(boolean ccw) {
return ccw ? ccw2 : cw2;
}
@Override
public boolean isProcessed() {
return processed;
}
@Override
public void setProcessed(boolean processed) {
this.processed = processed;
}
@Override
public String toString() {
return "<GridDelaunayEdge " + A.getCoordinates() + "->" + B.getCoordinates() + ">";
}
}
private double dX, dY;
private Coordinate center;
@SuppressWarnings("unused")
private int chunkSize;
private SparseMatrix<SparseMatrixSamplePoint> allSamples;
private List<GridDelaunayEdge> triangulation = null;
/**
* @param chunkSize SparseMatrix chunk side (eg 8 or 16). See SparseMatrix.
* @param totalSize Total estimated size for pre-allocating.
* @param dX X grid size, same units as center coordinates.
* @param dY Y grid size, same units as center coordinates.
* @param center Center position of the grid. Do not need to be precise.
*/
public SparseMatrixZSampleGrid(int chunkSize, int totalSize, double dX, double dY,
Coordinate center) {
this.center = center;
this.dX = dX;
this.dY = dY;
this.chunkSize = chunkSize;
allSamples = new SparseMatrix<SparseMatrixSamplePoint>(chunkSize, totalSize);
}
public ZSamplePoint<TZ> getOrCreate(int x, int y) {
SparseMatrixSamplePoint A = allSamples.get(x, y);
if (A != null)
return A;
A = new SparseMatrixSamplePoint();
A.x = x;
A.y = y;
A.z = null;
SparseMatrixSamplePoint Aup = allSamples.get(x, y + 1);
if (Aup != null) {
Aup.down = A;
A.up = Aup;
}
SparseMatrixSamplePoint Adown = allSamples.get(x, y - 1);
if (Adown != null) {
Adown.up = A;
A.down = Adown;
}
SparseMatrixSamplePoint Aright = allSamples.get(x + 1, y);
if (Aright != null) {
Aright.left = A;
A.right = Aright;
}
SparseMatrixSamplePoint Aleft = allSamples.get(x - 1, y);
if (Aleft != null) {
Aleft.right = A;
A.left = Aleft;
}
allSamples.put(x, y, A);
return A;
}
@Override
public Iterator<ZSamplePoint<TZ>> iterator() {
return new Iterator<ZSamplePoint<TZ>>() {
private Iterator<SparseMatrixSamplePoint> iterator = allSamples.iterator();
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public ZSamplePoint<TZ> next() {
return iterator.next();
}
@Override
public void remove() {
iterator.remove();
}
};
}
@Override
public Coordinate getCoordinates(ZSamplePoint<TZ> point) {
// TODO Cache the coordinates in the point?
return new Coordinate(point.getX() * dX + center.x, point.getY() * dY + center.y);
}
@Override
public int[] getLowerLeftIndex(Coordinate C) {
return new int[] { (int) Math.round((C.x - center.x - dX / 2) / dX),
(int) Math.round((C.y - center.y - dY / 2) / dY) };
}
@Override
public Coordinate getCenter() {
return center;
}
@Override
public Coordinate getCellSize() {
return new Coordinate(dX, dY);
}
@Override
public int getXMin() {
return allSamples.xMin;
}
@Override
public int getXMax() {
return allSamples.xMax;
}
@Override
public int getYMin() {
return allSamples.yMin;
}
@Override
public int getYMax() {
return allSamples.yMax;
}
@Override
public int size() {
return allSamples.size();
}
@Override
public int edgesCount() {
if (triangulation == null) {
delaunify();
}
return triangulation.size();
}
@Override
public Iterable<? extends DelaunayEdge<TZ>> edges() {
if (triangulation == null) {
delaunify();
}
return triangulation;
}
/**
* The conversion from a grid of points to a Delaunay triangulation is trivial. Each square from
* the grid is cut through one diagonal in two triangles, the resulting output is a Delaunay
* triangulation.
*/
private void delaunify() {
triangulation = new ArrayList<GridDelaunayEdge>(allSamples.size() * 3);
// 1. Create unlinked edges
for (SparseMatrixSamplePoint A : allSamples) {
SparseMatrixSamplePoint B = (SparseMatrixSamplePoint) A.right();
SparseMatrixSamplePoint D = (SparseMatrixSamplePoint) A.up();
SparseMatrixSamplePoint C = (SparseMatrixSamplePoint) (B != null ? B.up()
: D != null ? D.right() : null);
if (B != null)
triangulation.add(new GridDelaunayEdge(A, B, GridDelaunayEdge.TYPE_HORIZONTAL));
if (D != null)
triangulation.add(new GridDelaunayEdge(A, D, GridDelaunayEdge.TYPE_VERTICAL));
if (C != null)
triangulation.add(new GridDelaunayEdge(A, C, GridDelaunayEdge.TYPE_DIAGONAL));
}
// 2. Link edges
for (GridDelaunayEdge e : triangulation) {
switch (e.type) {
case GridDelaunayEdge.TYPE_HORIZONTAL:
e.ccw1 = e.B.eUp;
e.ccw2 = e.A.eUpRight;
e.cw1 = e.A.down == null ? null : e.A.down.eUpRight;
e.cw2 = e.A.down == null ? null : e.A.down.eUp;
break;
case GridDelaunayEdge.TYPE_VERTICAL:
e.ccw1 = e.A.left == null ? null : e.A.left.eUpRight;
e.ccw2 = e.A.left == null ? null : e.A.left.eRight;
e.cw1 = e.B.eRight;
e.cw2 = e.A.eUpRight;
break;
case GridDelaunayEdge.TYPE_DIAGONAL:
e.ccw1 = e.A.up == null ? null : e.A.up.eRight;
e.ccw2 = e.A.eUp;
e.cw1 = e.A.right == null ? null : e.A.right.eUp;
e.cw2 = e.A.eRight;
break;
}
}
}
@Override
public DelaunayTriangulation<TZ> delaunayTriangulate() {
// We ourselves are a DelaunayTriangulation
return this;
}
}