/*
* 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 com.oculusinfo.binning.impl.WebMercatorTilePyramid;
import org.junit.Assert;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public class MercatorBinningTests {
private static final double EPSILON = 1E-12;
// When dealing with numbers on the scale of the circumference of the globe
// in meters, 1E-12 implies too many digits of precision. We really want
// this relative, but JUnit doesn't give us that, so we satisfy ourselves by
// just bumping it up the (approximately 6) significant digits of the
// circumference.
private static final double WORLD_SCALE_EPSILON = 1E-6;
private WebMercatorTilePyramid _mercator;
@Before
public void setup () {
_mercator = new WebMercatorTilePyramid();
}
@After
public void teardown () {
_mercator = null;
}
@Test
public void testLevel0 () {
TileIndex tile = new TileIndex(0, 0, 0);
Rectangle2D tileBounds = _mercator.getTileBounds(tile);
Assert.assertEquals(-180.0, tileBounds.getMinX(), EPSILON);
Assert.assertEquals(180.0, tileBounds.getMaxX(), EPSILON);
Assert.assertEquals(-85.05112877980659, tileBounds.getMinY(), EPSILON);
Assert.assertEquals(85.05112877980659, tileBounds.getMaxY(), EPSILON);
}
@Test
public void testLevel1 () {
TileIndex tile;
Rectangle2D bounds;
tile = new TileIndex(1, 0, 0);
bounds = _mercator.getTileBounds(tile);
Assert.assertEquals(-180.0, bounds.getMinX(), EPSILON);
Assert.assertEquals(-85.05112877980659, bounds.getMinY(), EPSILON);
Assert.assertEquals(0.0, bounds.getMaxX(), EPSILON);
Assert.assertEquals(0.0, bounds.getMaxY(), EPSILON);
tile = new TileIndex(1, 0, 1);
bounds = _mercator.getTileBounds(tile);
Assert.assertEquals(-180.0, bounds.getMinX(), EPSILON);
Assert.assertEquals(0.0, bounds.getMinY(), EPSILON);
Assert.assertEquals(0.0, bounds.getMaxX(), EPSILON);
Assert.assertEquals(85.05112877980659, bounds.getMaxY(), EPSILON);
tile = new TileIndex(1, 1, 0);
bounds = _mercator.getTileBounds(tile);
Assert.assertEquals(0.0, bounds.getMinX(), EPSILON);
Assert.assertEquals(-85.05112877980659, bounds.getMinY(), EPSILON);
Assert.assertEquals(180.0, bounds.getMaxX(), EPSILON);
Assert.assertEquals(0.0, bounds.getMaxY(), EPSILON);
tile = new TileIndex(1, 1, 1);
bounds = _mercator.getTileBounds(tile);
Assert.assertEquals(0.0, bounds.getMinX(), EPSILON);
Assert.assertEquals(0.0, bounds.getMinY(), EPSILON);
Assert.assertEquals(180.0, bounds.getMaxX(), EPSILON);
Assert.assertEquals(85.05112877980659, bounds.getMaxY(), EPSILON);
}
@Test
public void testLevel2 () {
TileIndex tile;
Rectangle2D bounds;
tile = new TileIndex(2, 0, 0);
bounds = _mercator.getTileBounds(tile);
Assert.assertEquals(-180.0, bounds.getMinX(), EPSILON);
Assert.assertEquals(-85.05112877980659, bounds.getMinY(), EPSILON);
Assert.assertEquals(-90.0, bounds.getMaxX(), EPSILON);
Assert.assertEquals(-66.51326044311186, bounds.getMaxY(), EPSILON);
tile = new TileIndex(2, 3, 3);
bounds = _mercator.getTileBounds(tile);
Assert.assertEquals(180.0, bounds.getMaxX(), EPSILON);
Assert.assertEquals(85.05112877980659, bounds.getMaxY(), EPSILON);
Assert.assertEquals(90.0, bounds.getMinX(), EPSILON);
Assert.assertEquals(66.51326044311186, bounds.getMinY(), EPSILON);
}
@Test
public void testBoundingBoxes () {
TileIndex tile = new TileIndex(10, 14, 6, 256, 256);
Rectangle2D tileBounds = _mercator.getTileBounds(tile);
BinIndex bll = new BinIndex(0, 255);
BinIndex bur = new BinIndex(255, 0);
Rectangle2D llBounds = _mercator.getBinBounds(tile, bll);
Rectangle2D urBounds = _mercator.getBinBounds(tile, bur);
Assert.assertEquals(llBounds.getMinX(), tileBounds.getMinX(), EPSILON);
Assert.assertEquals(urBounds.getMaxX(), tileBounds.getMaxX(), EPSILON);
Assert.assertEquals(llBounds.getMinY(), tileBounds.getMinY(), EPSILON);
Assert.assertEquals(urBounds.getMaxY(), tileBounds.getMaxY(), EPSILON);
}
@Test
public void testBounds () {
int minBinX = 1000;
int maxBinX = -1000;
int minBinY = 1000;
int maxBinY = -1000;
for (int i=0; i<10000000; ++i) {
double longitude = Math.random()*180-180;
double latitude = Math.random()*90-90;
Point2D pt = new Point2D.Double(longitude, latitude);
TileIndex tile = _mercator.rootToTile(pt, 1);
if (0 != tile.getX() || 0 != tile.getY()) continue;
BinIndex bin = _mercator.rootToBin(pt, tile);
if (bin.getX() < minBinX) minBinX = bin.getX();
if (bin.getX() > maxBinX) maxBinX = bin.getX();
if (bin.getY() < minBinY) minBinY = bin.getY();
if (bin.getY() > maxBinY) maxBinY = bin.getY();
}
Assert.assertEquals(0, minBinX);
Assert.assertEquals(0, minBinY);
Assert.assertEquals(255, maxBinX);
Assert.assertEquals(255, maxBinY);
}
private Point2D getCenter (Rectangle2D rect) {
return new Point2D.Double(rect.getCenterX(), rect.getCenterY());
}
@Test
public void testRoundTripTransformation () {
for (int level=0; level<4; ++level) {
int pow2 = 1 << level;
for (int x = 0; x<pow2; ++x) {
for (int y=0; y<pow2; ++y) {
TileIndex t0 = new TileIndex(level, x, y);
Rectangle2D tileBounds = _mercator.getTileBounds(t0);
TileIndex t1 = _mercator.rootToTile(getCenter(tileBounds), level);
Assert.assertEquals(t0, t1);
for (int dx=0; dx<t0.getXBins(); ++dx) {
for (int dy=0; dy<t0.getYBins(); ++dy) {
BinIndex b0 = new BinIndex(dx, dy);
Rectangle2D binBounds = _mercator.getBinBounds(t0, b0);
BinIndex b1 = _mercator.rootToBin(getCenter(binBounds), t0);
Assert.assertEquals(b0, b1);
}
}
}
}
}
}
@Test
public void testBoundingBoxDirection () {
TileIndex tile = new TileIndex(5, 23, 17);
BinIndex bin = new BinIndex(34, 78);
Rectangle2D bounds = _mercator.getBinBounds(tile, bin);
Assert.assertTrue(bounds.getMaxX() > bounds.getMinX());
Assert.assertTrue(bounds.getMaxY() > bounds.getMinY());
}
@Test
public void testEPSG900913Projection () {
TileIndex tile = new TileIndex(1, 0, 0);
Rectangle2D bounds = _mercator.getEPSG_900913Bounds(tile, null);
Assert.assertEquals(-20037508.342789244, bounds.getMinX(), EPSILON);
Assert.assertEquals(-20037508.342789244, bounds.getMinY(), EPSILON);
Assert.assertEquals(0, bounds.getMaxX(), EPSILON);
Assert.assertEquals(0, bounds.getMaxY(), EPSILON);
tile = new TileIndex(3, 1, 2);
bounds = _mercator.getEPSG_900913Bounds(tile, null);
Assert.assertEquals(-15028131.257091932, bounds.getMinX(), WORLD_SCALE_EPSILON);
Assert.assertEquals(-10018754.171394622, bounds.getMaxX(), WORLD_SCALE_EPSILON);
Assert.assertEquals(-10018754.171394622 , bounds.getMinY(), WORLD_SCALE_EPSILON);
Assert.assertEquals(-5009377.085697312, bounds.getMaxY(), WORLD_SCALE_EPSILON);
}
@Test
public void testBinOverlap () {
// Southern hemisphere; upper half should be smaller
TileIndex tile = new TileIndex(5, 12, 14);
BinIndex bin = new BinIndex(124, 154);
Rectangle2D realBounds = _mercator.getBinBounds(tile, bin);
Assert.assertEquals(1.0,
_mercator.getBinOverlap(tile, bin, realBounds),
EPSILON);
Rectangle2D leftHalf = new Rectangle2D.Double(realBounds.getMinX(),
realBounds.getMinY(),
realBounds.getWidth() / 2.0,
realBounds.getHeight());
Assert.assertEquals(0.5, _mercator.getBinOverlap(tile, bin, leftHalf),
EPSILON);
Rectangle2D rightHalf = new Rectangle2D.Double(realBounds.getMinX() + realBounds.getWidth()/2.0,
realBounds.getMinY(),
realBounds.getWidth() / 2.0,
realBounds.getHeight());
Assert.assertEquals(0.5, _mercator.getBinOverlap(tile, bin, rightHalf),
EPSILON);
Rectangle2D topHalf = new Rectangle2D.Double(realBounds.getMinX(),
realBounds.getMinY() + realBounds.getHeight()/2.0,
realBounds.getWidth(),
realBounds.getHeight() / 2.0);
double topOverlap = _mercator.getBinOverlap(tile, bin, topHalf);
Assert.assertTrue(0.5 > topOverlap);
Assert.assertTrue(0.0 < topOverlap);
Rectangle2D bottomHalf = new Rectangle2D.Double(realBounds.getMinX(),
realBounds.getMinY(),
realBounds.getWidth(),
realBounds.getHeight() / 2.0);
double bottomOverlap = _mercator.getBinOverlap(tile, bin, bottomHalf);
Assert.assertTrue(0.5 < bottomOverlap);
Assert.assertTrue(1.0 > bottomOverlap);
Assert.assertEquals(1.0, topOverlap + bottomOverlap, EPSILON);
Rectangle2D bottomLeftCorner = new Rectangle2D.Double(realBounds.getMinX(),
realBounds.getMinY(),
realBounds.getWidth() / 2.0,
realBounds.getHeight() / 2.0);
Assert.assertEquals(bottomOverlap / 2.0,
_mercator.getBinOverlap(tile, bin, bottomLeftCorner),
EPSILON);
Rectangle2D bottomLeftCornerPlus = new Rectangle2D.Double(realBounds.getMinX() - 1.0,
realBounds.getMinY() - 1.0,
realBounds.getWidth() / 2.0 + 1.0,
realBounds.getHeight() / 2.0 + 1.0);
Assert.assertEquals(bottomOverlap / 2.0,
_mercator.getBinOverlap(tile, bin, bottomLeftCornerPlus),
EPSILON);
}
// Write out test data for testing actual binning in situ
// Writes a dot at the center of tile(5, 23, 14), with lines going north and east.
@Ignore
@Test
public void isolateTestCases () {
WebMercatorTilePyramid mercator = new WebMercatorTilePyramid();
TileIndex tile = new TileIndex(5, 23, 17);
for (int x=-3; x<3; ++x) {
for (int y=-3; y<4; ++y) {
BinIndex bin = new BinIndex(127+x, 127+y);
Rectangle2D bounds = mercator.getBinBounds(tile, bin);
int reps = Math.max(0, 16-((x*x+y*y)));
for (int i=0; i<reps; ++i) {
System.out.println(String.format("ais(3.0), 0, 'test ship', '', %.6f, %.6f, 0.0, 0.0, 1207040300, 'TEST', 0203011000", bounds.getCenterY(), bounds.getCenterX()));
}
}
}
for (int n=0; n<10; ++n) {
BinIndex xbin = new BinIndex(131+n, 127);
BinIndex ybin = new BinIndex(127, 131+n);
Rectangle2D xbounds = mercator.getBinBounds(tile, xbin);
Rectangle2D ybounds = mercator.getBinBounds(tile, ybin);
for (int i=0; i<16; ++i) {
System.out.println(String.format("ais(3.0), 0, 'test ship', '', %.6f, %.6f, 0.0, 0.0, 1207040300, 'TEST', 0203011000",
xbounds.getCenterY(), xbounds.getCenterX()));
System.out.println(String.format("ais(3.0), 0, 'test ship', '', %.6f, %.6f, 0.0, 0.0, 1207040300, 'TEST', 0203011000",
ybounds.getCenterY(), ybounds.getCenterX()));
}
}
}
}