package org.geoserver.kml.regionate;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.util.CanonicalSet;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Envelope;
/**
* A regionating tile identified by its coordinates
*
* @author Andrea Aime
*/
public class Tile {
public static final CoordinateReferenceSystem WGS84;
public static final ReferencedEnvelope WORLD_BOUNDS;
static final double MAX_TILE_WIDTH;
/**
* This structure is used to make sure that multiple threads end up using
* the same table name object, so that we can use it as a synchonization
* token
*/
static CanonicalSet<String> canonicalizer = CanonicalSet
.newInstance(String.class);
static {
try {
// common geographic info
WGS84 = CRS.decode("EPSG:4326");
WORLD_BOUNDS = new ReferencedEnvelope(new Envelope(180.0, -180.0,
90.0, -90.0), WGS84);
MAX_TILE_WIDTH = WORLD_BOUNDS.getWidth() / 2.0;
// make sure, once and for all, that H2 is around
Class.forName("org.h2.Driver");
} catch (Exception e) {
throw new RuntimeException(
"Could not initialize the class constants", e);
}
}
protected long x;
protected long y;
protected long z;
protected ReferencedEnvelope envelope;
/**
* Creates a new tile with the given coordinates
*
* @param x
* @param y
* @param z
*/
public Tile(long x, long y, long z) {
this.x = x;
this.y = y;
this.z = z;
envelope = envelope(x, y, z);
}
/**
* Tile containment check is not trivial due to a couple of issues:
* <ul>
* <li>centroids sitting on the tile borders must be associated to exactly one tile,
* so we have to consider only two borders as inclusive in general (S and W)
* but add on occasion the other two when we reach the extent of our data set</li>
* <li>coordinates going beyond the natural lat/lon range</li>
* </ul>
* This code takes care of the first, whilst the second issue remains as a TODO
* @param x
* @param y
*
*/
public boolean contains(double x, double y) {
double minx = envelope.getMinX();
double maxx = envelope.getMaxX();
double miny = envelope.getMinY();
double maxy = envelope.getMaxY();
// standard borders, N and W in, E and S out
if(x >= minx && x < maxx && y >= miny && y < maxy)
return true;
return false;
}
private ReferencedEnvelope envelope(long x, long y, long z) {
double tileSize = MAX_TILE_WIDTH / Math.pow(2, z);
double xMin = x * tileSize + WORLD_BOUNDS.getMinX();
double yMin = y * tileSize + WORLD_BOUNDS.getMinY();
return new ReferencedEnvelope(xMin, xMin + tileSize, yMin, yMin
+ tileSize, WGS84);
}
/**
* Builds the best matching tile for the specified envelope
*/
public Tile(ReferencedEnvelope wgs84Envelope) {
z = Math.round(Math.log(MAX_TILE_WIDTH / wgs84Envelope.getWidth())
/ Math.log(2));
x = Math.round(((wgs84Envelope.getMinimum(0) - WORLD_BOUNDS
.getMinimum(0)) / MAX_TILE_WIDTH)
* Math.pow(2, z));
y = Math.round(((wgs84Envelope.getMinimum(1) - WORLD_BOUNDS
.getMinimum(1)) / MAX_TILE_WIDTH)
* Math.pow(2, z));
envelope = envelope(x, y, z);
}
/**
* Returns the parent of this tile, or null if this tile is (one of) the
* root of the current dataset
*
*
*/
public Tile getParent() {
// if we got to one of the root tiles for this data set, just stop
if (z == 0)
return null;
else
return new Tile((long) Math.floor(x / 2.0), (long) Math
.floor(y / 2.0), z - 1);
}
/**
* Returns the four direct children of this tile
*
*
*/
public Tile[] getChildren() {
Tile[] result = new Tile[4];
result[0] = new Tile(x * 2, y * 2, z + 1);
result[1] = new Tile(x * 2 + 1, y * 2, z + 1);
result[2] = new Tile(x * 2, y * 2 + 1, z + 1);
result[3] = new Tile(x * 2 + 1, y * 2 + 1, z + 1);
return result;
}
/**
* Returns the WGS84 envelope of this tile
*
*
*/
public ReferencedEnvelope getEnvelope() {
return envelope;
}
@Override
public String toString() {
return "Tile X: " + x + ", Y: " + y + ", Z: " + z + " (" + envelope
+ ")";
}
public long getX() {
return x;
}
public long getY() {
return y;
}
public long getZ() {
return z;
}
}