/* * Copyright (c) 2014 Oculus Info Inc.
* http://www.oculusinfo.com/
*
* Released under the MIT License.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oculusinfo.binning;
import java.io.Serializable;
import java.util.Arrays;
/**
* Simple immutable tile index representation, assuming a tile tree with double
* the number of tiles in each dimension each level.
*
* @author nkronenfeld
*/
public class TileIndex implements Serializable, Comparable<TileIndex> {
private static final long serialVersionUID = -1L;
private int _level;
private int _x;
private int _y;
private int _xBins;
private int _yBins;
private TileIndex() {
}
/**
* Create a tile index representation. The number of bins per axis is defaulted to 256.
*
* @param level
* The level of the tile
* @param x
* The x coordinate of the tile
* @param y
* The y coordinate of the tile
*/
public TileIndex (int level, int x, int y) {
this();
_level = level;
_x = x;
_y = y;
_xBins = 256;
_yBins = 256;
}
/**
* Create a tile index representation
*
* @param level
* The level of the tile
* @param x
* The x coordinate of the tile
* @param y
* The y coordinate of the tile
* @param xBins
* the number of bins in this tile along the x axis
* @param yBins
* the number of bins in this tile along the y axis
*/
public TileIndex (int level, int x, int y, int xBins, int yBins) {
_level = level;
_x = x;
_y = y;
_xBins = xBins;
_yBins = yBins;
}
/**
* Create a new tile index representation, identical but for the number of
* bins in the tile
*
* @param base
* The base tile representation being copied
* @param xBins
* The new number of bins to use along the x axis
* @param yBins
* The new number of bins to use along the y axis
*/
public TileIndex (TileIndex base, int xBins, int yBins) {
_level = base.getLevel();
_x = base.getX();
_y = base.getY();
_xBins = xBins;
_yBins = yBins;
}
/**
* Get the tile level (or zoom level)
*/
public int getLevel () {
return _level;
}
/**
* Get the x index of the tile
*/
public int getX () {
return _x;
}
/**
* Get the y index of the tile
*/
public int getY () {
return _y;
}
/**
* Get the number of bins this tile should have along the x axis
*/
public int getXBins () {
return _xBins;
}
/**
* Get the number of bins this tile should have along the y axis
*/
public int getYBins () {
return _yBins;
}
/**
* Get the tile one level up from a given tile.
* @param child The tile from which to search
* @return The tile one level up (lower level number) containing the child tile
*/
public static TileIndex getParent (TileIndex child) {
return new TileIndex(
child.getLevel() - 1,
child.getX() / 2,
child.getY() / 2,
child.getXBins(),
child.getYBins()
);
}
/**
* Get the tiles one level down from a given tile.
* @param parent The tile from which to search
* @return The four tiles one level down (higher level number) contained by the parent tile
*/
public static Iterable<TileIndex> getChildren (TileIndex parent) {
int level = parent.getLevel()+1;
int x = parent.getX()*2;
int y = parent.getY()*2;
int xBins = parent.getXBins();
int yBins = parent.getYBins();
return Arrays.asList(
new TileIndex(level, x+0, y+0, xBins, yBins),
new TileIndex(level, x+1, y+0, xBins, yBins),
new TileIndex(level, x+0, y+1, xBins, yBins),
new TileIndex(level, x+1, y+1, xBins, yBins)
);
}
/**
* Translates from a bin relative to the root position of this tile, to a
* bin relative to the root position of the entire data set.
*
* @param tile
* The tile in which this bin falls
* @param bin
* The bin relative to the root position of this tile (with
* coordinates [0 to getXBins(), 0 to getYBins()])
* @return The bin relative to the root position of the entire data set
* (with coordinates [0 to getXBins()*2^level, 0 to
* getYBins()*2^level])
*/
public static BinIndex tileBinIndexToUniversalBinIndex (TileIndex tile, BinIndex bin) {
// Tiles go from lower left to upper right
// Bins go from upper left to lower right
int pow2 = 1 << tile.getLevel();
int tileLeft, tileTop;
int xBins = tile.getXBins();
tileLeft = tile.getX() * xBins;
int yBins = tile.getYBins();
tileTop = (pow2 - tile.getY() - 1) * yBins;
return new BinIndex(tileLeft + bin.getX(), tileTop + bin.getY());
}
/**
* Translates from the root position of the entire data set to a bin
* relative to the root position of this tile, to a bin relative.
*
* @param sampleTile
* a sample tile specifying the level and number of x and y bins
* per tile required
* @param bin
* The bin relative to the root position of the entire data set
* (with coordinates [0 to getXBins()*2^level, 0 to
* getYBins()*2^level])
* @return The tile (with level, xbins, and ybins matching the input sample
* tile), and bin relative to the root position of this tile (with
* coordinates [0 to getXBins(), 0 to getYBins()])
*/
public static TileAndBinIndices universalBinIndexToTileBinIndex (TileIndex sampleTile,
BinIndex bin) {
// Tiles go from lower left to upper right
// Bins go from upper left to lower right
int level = sampleTile.getLevel();
int pow2 = 1 << level;
int tileX, tileY, tileLeft, tileTop;
int xBins = sampleTile.getXBins();
tileX = bin.getX()/xBins;
tileLeft = tileX * xBins;
int yBins = sampleTile.getYBins();
tileY = pow2 - bin.getY()/yBins - 1;
tileTop = (pow2 - tileY - 1) * yBins;
BinIndex tileBin = new BinIndex(bin.getX() - tileLeft, bin.getY() - tileTop);
TileIndex tile = new TileIndex(level, tileX, tileY, xBins, yBins);
return new TileAndBinIndices(tile, tileBin);
}
/**
* Translates from the root position of the entire data set to a bin
* relative to the root position of this tile, to a bin relative. Also
* clips data points to be within valid tile/bin range for a given level.
*
* @param sampleTile
* a sample tile specifying the level and number of x and y bins
* per tile required
* @param bin
* The bin relative to the root position of the entire data set
* (with coordinates [0 to getXBins()*2^level, 0 to
* getYBins()*2^level])
* @return The tile (with level, xbins, and ybins matching the input sample
* tile), and bin relative to the root position of this tile (with
* coordinates [0 to getXBins(), 0 to getYBins()])
*/
public static TileAndBinIndices universalBinIndexToTileBinIndexClipped (TileIndex sampleTile,
BinIndex bin) {
// Tiles go from lower left to upper right
// Bins go from upper left to lower right
int level = sampleTile.getLevel();
int pow2 = 1 << level;
int tileX, tileY, tileLeft, tileTop;
int xBins = sampleTile.getXBins();
int uniBinX = Math.min(Math.max(bin.getX(), 0), pow2*xBins-1); // restrict uni X bin to valid range
tileX = uniBinX/xBins;
tileLeft = tileX * xBins;
int yBins = sampleTile.getYBins();
int uniBinY = Math.min(Math.max(bin.getY(), 0), pow2*yBins-1); // restrict uni Y bin to valid range
tileY = pow2 - uniBinY/yBins - 1;
tileTop = (pow2 - tileY - 1) * yBins;
BinIndex tileBin = new BinIndex(uniBinX - tileLeft, uniBinY - tileTop);
TileIndex tile = new TileIndex(level, tileX, tileY, xBins, yBins);
return new TileAndBinIndices(tile, tileBin);
}
/**
* {@inheritDoc}
*
* This tile is less than that tile if it is below that tile or directly
* left from it.
*/
@Override
public int compareTo (TileIndex t) {
if (_level < t._level)
return -1;
if (_level > t._level)
return 1;
if (_y < t._y)
return -1;
if (_y > t._y)
return 1;
if (_x < t._x)
return -1;
if (_x > t._x)
return 1;
return 0;
}
@Override
public boolean equals (Object obj) {
if (this == obj)
return true;
if (null == obj)
return false;
if (!(obj instanceof TileIndex))
return false;
TileIndex t = (TileIndex) obj;
if (t._level != _level)
return false;
if (t._x != _x)
return false;
if (t._y != _y)
return false;
if (t._xBins != _xBins)
return false;
if (t._yBins != _yBins)
return false;
return true;
}
@Override
public int hashCode () {
// Shouldn't often be comparing tiles of different bin sizes; ignoring
// bin size for hash code calculation.
int pow2 = 1 << _level;
return _x * pow2 + _y;
}
@Override
public String toString () {
return String.format("[%d / %d, %d / %d, lvl %d]", _x, _xBins, _y,
_yBins, _level);
}
/**
* Converts from a string to a tile index. This takes in strings of exactly
* the form output by {@link #toString()} - i.e., formatted with "[%d / %d, %d / %d, lvl %d]"
*/
public static TileIndex fromString (String string) {
try {
int a = string.indexOf('[') + 1;
int b = string.indexOf('/', a);
int x = Integer.parseInt(string.substring(a, b).trim());
a = b + 1;
b = string.indexOf(',', a);
int xBins = Integer.parseInt(string.substring(a, b).trim());
a = b + 1;
b = string.indexOf('/', a);
int y = Integer.parseInt(string.substring(a, b).trim());
a = b + 1;
b = string.indexOf(", lvl", a);
int yBins = Integer.parseInt(string.substring(a, b).trim());
a = b + 5;
b = string.indexOf(']', a);
int level = Integer.parseInt(string.substring(a, b).trim());
return new TileIndex(level, x, y, xBins, yBins);
} catch (NumberFormatException e) {
return null;
} catch (IndexOutOfBoundsException e) {
return null;
}
}
}