513 lines
23 KiB
Java
513 lines
23 KiB
Java
/*
|
|
* Copyright 2005 Google Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.google.common.geometry;
|
|
|
|
public strictfp class S2LatLngRectTest extends GeometryTestCase {
|
|
|
|
public void testIntervalOps(S2LatLngRect x, S2LatLngRect y, String expectedRelation,
|
|
S2LatLngRect expectedUnion, S2LatLngRect expectedIntersection) {
|
|
// Test all of the interval operations on the given pair of intervals.
|
|
// "expected_relation" is a sequence of "T" and "F" characters corresponding
|
|
// to the expected results of Contains(), InteriorContains(), Intersects(),
|
|
// and InteriorIntersects() respectively.
|
|
|
|
assertEquals(x.contains(y), expectedRelation.charAt(0) == 'T');
|
|
assertEquals(x.interiorContains(y), expectedRelation.charAt(1) == 'T');
|
|
assertEquals(x.intersects(y), expectedRelation.charAt(2) == 'T');
|
|
assertEquals(x.interiorIntersects(y), expectedRelation.charAt(3) == 'T');
|
|
|
|
assertEquals(x.contains(y), x.union(y).equals(x));
|
|
assertEquals(x.intersects(y), !x.intersection(y).isEmpty());
|
|
|
|
assertTrue(x.union(y).equals(expectedUnion));
|
|
assertTrue(x.intersection(y).equals(expectedIntersection));
|
|
|
|
if (y.getSize() == S2LatLng.fromRadians(0, 0)) {
|
|
S2LatLngRect r = x.addPoint(y.lo());
|
|
assertTrue(r == expectedUnion);
|
|
}
|
|
}
|
|
|
|
public void testCellOps(S2LatLngRect r, S2Cell cell, int level) {
|
|
// Test the relationship between the given rectangle and cell:
|
|
// 0 == no intersection, 1 == MayIntersect, 2 == Intersects,
|
|
// 3 == Vertex Containment, 4 == Contains
|
|
|
|
boolean vertexContained = false;
|
|
for (int i = 0; i < 4; ++i) {
|
|
if (r.contains(cell.getVertexRaw(i))
|
|
|| (!r.isEmpty() && cell.contains(r.getVertex(i).toPoint()))) {
|
|
vertexContained = true;
|
|
}
|
|
}
|
|
assertEquals(r.mayIntersect(cell), level >= 1);
|
|
assertEquals(r.intersects(cell), level >= 2);
|
|
assertEquals(vertexContained, level >= 3);
|
|
assertEquals(r.contains(cell), level >= 4);
|
|
}
|
|
|
|
public void testBasic() {
|
|
// Most of the S2LatLngRect methods have trivial implementations that
|
|
// use the R1Interval and S1Interval classes, so most of the testing
|
|
// is done in those unit tests.
|
|
|
|
// Test basic properties of empty and full caps.
|
|
S2LatLngRect empty = S2LatLngRect.empty();
|
|
S2LatLngRect full = S2LatLngRect.full();
|
|
assertTrue(empty.isValid());
|
|
assertTrue(empty.isEmpty());
|
|
assertTrue(full.isValid());
|
|
assertTrue(full.isFull());
|
|
|
|
// assertTrue various constructors and accessor methods.
|
|
S2LatLngRect d1 = rectFromDegrees(-90, 0, -45, 180);
|
|
assertDoubleNear(d1.latLo().degrees(), -90);
|
|
assertDoubleNear(d1.latHi().degrees(), -45);
|
|
assertDoubleNear(d1.lngLo().degrees(), 0);
|
|
assertDoubleNear(d1.lngHi().degrees(), 180);
|
|
assertTrue(d1.lat().equals(new R1Interval(-S2.M_PI_2, -S2.M_PI_4)));
|
|
assertTrue(d1.lng().equals(new S1Interval(0, S2.M_PI)));
|
|
|
|
// FromCenterSize()
|
|
assertTrue(
|
|
S2LatLngRect.fromCenterSize(S2LatLng.fromDegrees(80, 170), S2LatLng.fromDegrees(40, 60))
|
|
.approxEquals(rectFromDegrees(60, 140, 90, -160)));
|
|
assertTrue(S2LatLngRect
|
|
.fromCenterSize(S2LatLng.fromDegrees(10, 40), S2LatLng.fromDegrees(210, 400)).isFull());
|
|
assertTrue(
|
|
S2LatLngRect.fromCenterSize(S2LatLng.fromDegrees(-90, 180), S2LatLng.fromDegrees(20, 50))
|
|
.approxEquals(rectFromDegrees(-90, 155, -80, -155)));
|
|
|
|
// FromPoint(), FromPointPair()
|
|
assertEquals(S2LatLngRect.fromPoint(d1.lo()), new S2LatLngRect(d1.lo(), d1.lo()));
|
|
assertEquals(
|
|
S2LatLngRect.fromPointPair(S2LatLng.fromDegrees(-35, -140), S2LatLng.fromDegrees(15, 155)),
|
|
rectFromDegrees(-35, 155, 15, -140));
|
|
assertEquals(
|
|
S2LatLngRect.fromPointPair(S2LatLng.fromDegrees(25, -70), S2LatLng.fromDegrees(-90, 80)),
|
|
rectFromDegrees(-90, -70, 25, 80));
|
|
|
|
// GetCenter(), GetVertex(), Contains(S2LatLng), InteriorContains(S2LatLng).
|
|
S2LatLng eqM180 = S2LatLng.fromRadians(0, -S2.M_PI);
|
|
S2LatLng northPole = S2LatLng.fromRadians(S2.M_PI_2, 0);
|
|
S2LatLngRect r1 = new S2LatLngRect(eqM180, northPole);
|
|
|
|
assertEquals(r1.getCenter(), S2LatLng.fromRadians(S2.M_PI_4, -S2.M_PI_2));
|
|
assertEquals(r1.getVertex(0), S2LatLng.fromRadians(0, S2.M_PI));
|
|
assertEquals(r1.getVertex(1), S2LatLng.fromRadians(0, 0));
|
|
assertEquals(r1.getVertex(2), S2LatLng.fromRadians(S2.M_PI_2, 0));
|
|
assertEquals(r1.getVertex(3), S2LatLng.fromRadians(S2.M_PI_2, S2.M_PI));
|
|
assertTrue(r1.contains(S2LatLng.fromDegrees(30, -45)));
|
|
assertTrue(!r1.contains(S2LatLng.fromDegrees(30, 45)));
|
|
assertTrue(!r1.interiorContains(eqM180) && !r1.interiorContains(northPole));
|
|
assertTrue(r1.contains(new S2Point(0.5, -0.3, 0.1)));
|
|
assertTrue(!r1.contains(new S2Point(0.5, 0.2, 0.1)));
|
|
|
|
// Make sure that GetVertex() returns vertices in CCW order.
|
|
for (int i = 0; i < 4; ++i) {
|
|
double lat = S2.M_PI_4 * (i - 2);
|
|
double lng = S2.M_PI_2 * (i - 2) + 0.2;
|
|
S2LatLngRect r = new S2LatLngRect(new R1Interval(lat, lat + S2.M_PI_4), new S1Interval(
|
|
Math.IEEEremainder(lng, 2 * S2.M_PI), Math.IEEEremainder(lng + S2.M_PI_2, 2 * S2.M_PI)));
|
|
for (int k = 0; k < 4; ++k) {
|
|
assertTrue(
|
|
S2.simpleCCW(r.getVertex((k - 1) & 3).toPoint(), r.getVertex(k).toPoint(),
|
|
r.getVertex((k + 1) & 3).toPoint()));
|
|
}
|
|
}
|
|
|
|
// Contains(S2LatLngRect), InteriorContains(S2LatLngRect),
|
|
// Intersects(), InteriorIntersects(), Union(), Intersection().
|
|
//
|
|
// Much more testing of these methods is done in s1interval_unittest
|
|
// and r1interval_unittest.
|
|
|
|
S2LatLngRect r1Mid = rectFromDegrees(45, -90, 45, -90);
|
|
S2LatLngRect reqM180 = new S2LatLngRect(eqM180, eqM180);
|
|
S2LatLngRect rNorthPole = new S2LatLngRect(northPole, northPole);
|
|
|
|
testIntervalOps(r1, r1Mid, "TTTT", r1, r1Mid);
|
|
testIntervalOps(r1, reqM180, "TFTF", r1, reqM180);
|
|
testIntervalOps(r1, rNorthPole, "TFTF", r1, rNorthPole);
|
|
|
|
assertTrue(r1.equals(rectFromDegrees(0, -180, 90, 0)));
|
|
testIntervalOps(r1, rectFromDegrees(-10, -1, 1, 20), "FFTT", rectFromDegrees(-10, -180, 90, 20),
|
|
rectFromDegrees(0, -1, 1, 0));
|
|
testIntervalOps(r1, rectFromDegrees(-10, -1, 0, 20), "FFTF", rectFromDegrees(-10, -180, 90, 20),
|
|
rectFromDegrees(0, -1, 0, 0));
|
|
testIntervalOps(r1, rectFromDegrees(-10, 0, 1, 20), "FFTF", rectFromDegrees(-10, -180, 90, 20),
|
|
rectFromDegrees(0, 0, 1, 0));
|
|
|
|
testIntervalOps(rectFromDegrees(-15, -160, -15, -150), rectFromDegrees(20, 145, 25, 155),
|
|
"FFFF", rectFromDegrees(-15, 145, 25, -150), empty);
|
|
testIntervalOps(rectFromDegrees(70, -10, 90, -140), rectFromDegrees(60, 175, 80, 5), "FFTT",
|
|
rectFromDegrees(60, -180, 90, 180), rectFromDegrees(70, 175, 80, 5));
|
|
|
|
// assertTrue that the intersection of two rectangles that overlap in
|
|
// latitude
|
|
// but not longitude is valid, and vice versa.
|
|
testIntervalOps(rectFromDegrees(12, 30, 60, 60), rectFromDegrees(0, 0, 30, 18), "FFFF",
|
|
rectFromDegrees(0, 0, 60, 60), empty);
|
|
testIntervalOps(rectFromDegrees(0, 0, 18, 42), rectFromDegrees(30, 12, 42, 60), "FFFF",
|
|
rectFromDegrees(0, 0, 42, 60), empty);
|
|
|
|
// AddPoint()
|
|
S2LatLngRect p = S2LatLngRect.empty();
|
|
p = p.addPoint(S2LatLng.fromDegrees(0, 0));
|
|
p = p.addPoint(S2LatLng.fromRadians(0, -S2.M_PI_2));
|
|
p = p.addPoint(S2LatLng.fromRadians(S2.M_PI_4, -S2.M_PI));
|
|
p = p.addPoint(new S2Point(0, 0, 1));
|
|
assertTrue(p.equals(r1));
|
|
|
|
// Expanded()
|
|
assertTrue(
|
|
rectFromDegrees(70, 150, 80, 170).expanded(S2LatLng.fromDegrees(20, 30)).approxEquals(
|
|
rectFromDegrees(50, 120, 90, -160)));
|
|
assertTrue(S2LatLngRect.empty().expanded(S2LatLng.fromDegrees(20, 30)).isEmpty());
|
|
assertTrue(S2LatLngRect.full().expanded(S2LatLng.fromDegrees(20, 30)).isFull());
|
|
assertTrue(
|
|
rectFromDegrees(-90, 170, 10, 20).expanded(S2LatLng.fromDegrees(30, 80)).approxEquals(
|
|
rectFromDegrees(-90, -180, 40, 180)));
|
|
|
|
// ConvolveWithCap()
|
|
S2LatLngRect llr1 =
|
|
new S2LatLngRect(S2LatLng.fromDegrees(0, 170), S2LatLng.fromDegrees(0, -170))
|
|
.convolveWithCap(S1Angle.degrees(15));
|
|
S2LatLngRect llr2 =
|
|
new S2LatLngRect(S2LatLng.fromDegrees(-15, 155), S2LatLng.fromDegrees(15, -155));
|
|
assertTrue(llr1.approxEquals(llr2));
|
|
|
|
llr1 = new S2LatLngRect(S2LatLng.fromDegrees(60, 150), S2LatLng.fromDegrees(80, 10))
|
|
.convolveWithCap(S1Angle.degrees(15));
|
|
llr2 = new S2LatLngRect(S2LatLng.fromDegrees(45, -180), S2LatLng.fromDegrees(90, 180));
|
|
assertTrue(llr1.approxEquals(llr2));
|
|
|
|
// GetCapBound(), bounding cap at center is smaller:
|
|
assertTrue(new S2LatLngRect(S2LatLng.fromDegrees(-45, -45), S2LatLng.fromDegrees(45, 45))
|
|
.getCapBound().approxEquals(S2Cap.fromAxisHeight(new S2Point(1, 0, 0), 0.5)));
|
|
// GetCapBound(), bounding cap at north pole is smaller:
|
|
assertTrue(new S2LatLngRect(S2LatLng.fromDegrees(88, -80), S2LatLng.fromDegrees(89, 80))
|
|
.getCapBound().approxEquals(S2Cap.fromAxisAngle(new S2Point(0, 0, 1), S1Angle.degrees(2))));
|
|
// GetCapBound(), longitude span > 180 degrees:
|
|
assertTrue(
|
|
new S2LatLngRect(S2LatLng.fromDegrees(-30, -150), S2LatLng.fromDegrees(-10, 50))
|
|
.getCapBound()
|
|
.approxEquals(S2Cap.fromAxisAngle(new S2Point(0, 0, -1), S1Angle.degrees(80))));
|
|
|
|
// Contains(S2Cell), MayIntersect(S2Cell), Intersects(S2Cell)
|
|
|
|
// Special cases.
|
|
testCellOps(empty, S2Cell.fromFacePosLevel(3, (byte) 0, 0), 0);
|
|
testCellOps(full, S2Cell.fromFacePosLevel(2, (byte) 0, 0), 4);
|
|
testCellOps(full, S2Cell.fromFacePosLevel(5, (byte) 0, 25), 4);
|
|
|
|
// This rectangle includes the first quadrant of face 0. It's expanded
|
|
// slightly because cell bounding rectangles are slightly conservative.
|
|
S2LatLngRect r4 = rectFromDegrees(-45.1, -45.1, 0.1, 0.1);
|
|
testCellOps(r4, S2Cell.fromFacePosLevel(0, (byte) 0, 0), 3);
|
|
testCellOps(r4, S2Cell.fromFacePosLevel(0, (byte) 0, 1), 4);
|
|
testCellOps(r4, S2Cell.fromFacePosLevel(1, (byte) 0, 1), 0);
|
|
|
|
// This rectangle intersects the first quadrant of face 0.
|
|
S2LatLngRect r5 = rectFromDegrees(-10, -45, 10, 0);
|
|
testCellOps(r5, S2Cell.fromFacePosLevel(0, (byte) 0, 0), 3);
|
|
testCellOps(r5, S2Cell.fromFacePosLevel(0, (byte) 0, 1), 3);
|
|
testCellOps(r5, S2Cell.fromFacePosLevel(1, (byte) 0, 1), 0);
|
|
|
|
// Rectangle consisting of a single point.
|
|
testCellOps(rectFromDegrees(4, 4, 4, 4), S2Cell.fromFacePosLevel(0, (byte) 0, 0), 3);
|
|
|
|
// Rectangles that intersect the bounding rectangle of a face
|
|
// but not the face itself.
|
|
testCellOps(rectFromDegrees(41, -87, 42, -79), S2Cell.fromFacePosLevel(2, (byte) 0, 0), 1);
|
|
testCellOps(rectFromDegrees(-41, 160, -40, -160), S2Cell.fromFacePosLevel(5, (byte) 0, 0), 1);
|
|
{
|
|
// This is the leaf cell at the top right hand corner of face 0.
|
|
// It has two angles of 60 degrees and two of 120 degrees.
|
|
S2Cell cell0tr = new S2Cell(new S2Point(1 + 1e-12, 1, 1));
|
|
S2LatLngRect bound0tr = cell0tr.getRectBound();
|
|
S2LatLng v0 = new S2LatLng(cell0tr.getVertexRaw(0));
|
|
testCellOps(
|
|
rectFromDegrees(v0.lat().degrees() - 1e-8, v0.lng().degrees() - 1e-8,
|
|
v0.lat().degrees() - 2e-10, v0.lng().degrees() + 1e-10), cell0tr, 1);
|
|
}
|
|
|
|
// Rectangles that intersect a face but where no vertex of one region
|
|
// is contained by the other region. The first one passes through
|
|
// a corner of one of the face cells.
|
|
testCellOps(rectFromDegrees(-37, -70, -36, -20), S2Cell.fromFacePosLevel(5, (byte) 0, 0), 2);
|
|
{
|
|
// These two intersect like a diamond and a square.
|
|
S2Cell cell202 = S2Cell.fromFacePosLevel(2, (byte) 0, 2);
|
|
S2LatLngRect bound202 = cell202.getRectBound();
|
|
testCellOps(
|
|
rectFromDegrees(bound202.lo().lat().degrees() + 3, bound202.lo().lng().degrees() + 3,
|
|
bound202.hi().lat().degrees() - 3, bound202.hi().lng().degrees() - 3), cell202, 2);
|
|
}
|
|
}
|
|
|
|
public void testArea() {
|
|
assertEquals(0.0, S2LatLngRect.empty().area());
|
|
assertDoubleNear(4 * Math.PI, S2LatLngRect.full().area());
|
|
assertDoubleNear(Math.PI / 2, rectFromDegrees(0, 0, 90, 90).area());
|
|
}
|
|
|
|
public void testEdgeBound() {
|
|
// assertTrue cases where min/max latitude is not at a vertex.
|
|
assertDoubleNear(getEdgeBound(1, 1, 1, 1, -1, 1).lat().hi(), S2.M_PI_4); // Max,
|
|
// CW
|
|
assertDoubleNear(getEdgeBound(1, -1, 1, 1, 1, 1).lat().hi(), S2.M_PI_4); // Max,
|
|
// CCW
|
|
assertDoubleNear(getEdgeBound(1, -1, -1, -1, -1, -1).lat().lo(), -S2.M_PI_4); // Min,
|
|
// CW
|
|
assertDoubleNear(getEdgeBound(-1, 1, -1, -1, -1, -1).lat().lo(), -S2.M_PI_4); // Min,
|
|
// CCW
|
|
|
|
// assertTrue cases where the edge passes through one of the poles.
|
|
assertDoubleNear(getEdgeBound(.3, .4, 1, -.3, -.4, 1).lat().hi(), S2.M_PI_2);
|
|
assertDoubleNear(getEdgeBound(.3, .4, -1, -.3, -.4, -1).lat().lo(), -S2.M_PI_2);
|
|
|
|
// assertTrue cases where the min/max latitude is attained at a vertex.
|
|
final double kCubeLat = Math.asin(Math.sqrt(1. / 3)); // 35.26 degrees
|
|
assertTrue(
|
|
getEdgeBound(1, 1, 1, 1, -1, -1).lat().approxEquals(new R1Interval(-kCubeLat, kCubeLat)));
|
|
assertTrue(
|
|
getEdgeBound(1, -1, 1, 1, 1, -1).lat().approxEquals(new R1Interval(-kCubeLat, kCubeLat)));
|
|
}
|
|
|
|
public void testGetDistanceOverlapping() {
|
|
// Check pairs of rectangles that overlap: (should all return 0):
|
|
S2LatLngRect a = rectFromDegrees(0, 0, 2, 2);
|
|
S2LatLngRect b = pointRectFromDegrees(0, 0);
|
|
S1Angle zero = S1Angle.radians(0);
|
|
assertEquals(zero, a.getDistance(a));
|
|
assertEquals(zero, a.getDistance(b));
|
|
assertEquals(zero, b.getDistance(b));
|
|
assertEquals(zero, a.getDistance(S2LatLng.fromDegrees(0, 0)));
|
|
assertEquals(zero, a.getDistance(rectFromDegrees(0, 1, 2, 3)));
|
|
assertEquals(zero, a.getDistance(rectFromDegrees(0, 2, 2, 4)));
|
|
assertEquals(zero, a.getDistance(rectFromDegrees(1, 0, 3, 2)));
|
|
assertEquals(zero, a.getDistance(rectFromDegrees(2, 0, 4, 2)));
|
|
assertEquals(zero, a.getDistance(rectFromDegrees(1, 1, 3, 3)));
|
|
assertEquals(zero, a.getDistance(rectFromDegrees(2, 2, 4, 4)));
|
|
}
|
|
|
|
public void testGetDistanceRectVsPoint() {
|
|
// Rect that spans 180.
|
|
S2LatLngRect a = rectFromDegrees(-1, -1, 2, 1);
|
|
verifyGetDistance(a, pointRectFromDegrees(-2, -1));
|
|
verifyGetDistance(a, pointRectFromDegrees(1, 2));
|
|
|
|
verifyGetDistance(pointRectFromDegrees(-2, -1), a);
|
|
verifyGetDistance(pointRectFromDegrees(1, 2), a);
|
|
|
|
verifyGetRectPointDistance(a, S2LatLng.fromDegrees(-2, -1));
|
|
verifyGetRectPointDistance(a, S2LatLng.fromDegrees(1, 2));
|
|
|
|
// Tests near the north pole.
|
|
S2LatLngRect b = rectFromDegrees(86, 0, 88, 2);
|
|
verifyGetDistance(b, pointRectFromDegrees(87, 3));
|
|
verifyGetDistance(b, pointRectFromDegrees(87, -1));
|
|
verifyGetDistance(b, pointRectFromDegrees(89, 1));
|
|
verifyGetDistance(b, pointRectFromDegrees(89, 181));
|
|
verifyGetDistance(b, pointRectFromDegrees(85, 1));
|
|
verifyGetDistance(b, pointRectFromDegrees(85, 181));
|
|
verifyGetDistance(b, pointRectFromDegrees(90, 0));
|
|
|
|
verifyGetDistance(pointRectFromDegrees(87, 3), b);
|
|
verifyGetDistance(pointRectFromDegrees(87, -1), b);
|
|
verifyGetDistance(pointRectFromDegrees(89, 1), b);
|
|
verifyGetDistance(pointRectFromDegrees(89, 181), b);
|
|
verifyGetDistance(pointRectFromDegrees(85, 1), b);
|
|
verifyGetDistance(pointRectFromDegrees(85, 181), b);
|
|
verifyGetDistance(pointRectFromDegrees(90, 0), b);
|
|
|
|
verifyGetRectPointDistance(b, S2LatLng.fromDegrees(87, 3));
|
|
verifyGetRectPointDistance(b, S2LatLng.fromDegrees(87, -1));
|
|
verifyGetRectPointDistance(b, S2LatLng.fromDegrees(89, 1));
|
|
verifyGetRectPointDistance(b, S2LatLng.fromDegrees(89, 181));
|
|
verifyGetRectPointDistance(b, S2LatLng.fromDegrees(85, 1));
|
|
verifyGetRectPointDistance(b, S2LatLng.fromDegrees(85, 181));
|
|
verifyGetRectPointDistance(b, S2LatLng.fromDegrees(90, 0));
|
|
|
|
// Rect that touches the north pole.
|
|
S2LatLngRect c = rectFromDegrees(88, 0, 90, 2);
|
|
verifyGetDistance(c, pointRectFromDegrees(89, 3));
|
|
verifyGetDistance(c, pointRectFromDegrees(89, 90));
|
|
verifyGetDistance(c, pointRectFromDegrees(89, 181));
|
|
verifyGetDistance(pointRectFromDegrees(89, 3), c);
|
|
verifyGetDistance(pointRectFromDegrees(89, 90), c);
|
|
verifyGetDistance(pointRectFromDegrees(89, 181), c);
|
|
}
|
|
|
|
public void testGetDistanceRectVsRect() {
|
|
// Rect that spans 180.
|
|
S2LatLngRect a = rectFromDegrees(-1, -1, 2, 1);
|
|
verifyGetDistance(a, rectFromDegrees(0, 2, 1, 3));
|
|
verifyGetDistance(a, rectFromDegrees(-2, -3, -1, -2));
|
|
|
|
// Tests near the south pole.
|
|
S2LatLngRect b = rectFromDegrees(-87, 0, -85, 3);
|
|
verifyGetDistance(b, rectFromDegrees(-89, 1, -88, 2));
|
|
verifyGetDistance(b, rectFromDegrees(-84, 1, -83, 2));
|
|
verifyGetDistance(b, rectFromDegrees(-88, 90, -86, 91));
|
|
verifyGetDistance(b, rectFromDegrees(-84, -91, -83, -90));
|
|
verifyGetDistance(b, rectFromDegrees(-90, 181, -89, 182));
|
|
verifyGetDistance(b, rectFromDegrees(-84, 181, -83, 182));
|
|
}
|
|
|
|
public void testGetDistanceRandomPairs() {
|
|
// Test random pairs.
|
|
for (int i = 0; i < 10000; ++i) {
|
|
S2LatLngRect a =
|
|
S2LatLngRect.fromPointPair(new S2LatLng(randomPoint()), new S2LatLng(randomPoint()));
|
|
S2LatLngRect b =
|
|
S2LatLngRect.fromPointPair(new S2LatLng(randomPoint()), new S2LatLng(randomPoint()));
|
|
verifyGetDistance(a, b);
|
|
|
|
|
|
S2LatLng c = new S2LatLng(randomPoint());
|
|
verifyGetRectPointDistance(a, c);
|
|
verifyGetRectPointDistance(b, c);
|
|
}
|
|
}
|
|
|
|
private static S1Angle bruteForceDistance(S2LatLngRect a, S2LatLngRect b) {
|
|
if (a.intersects(b)) {
|
|
return S1Angle.radians(0);
|
|
}
|
|
|
|
// Compare every point in 'a' against every latitude edge and longitude edge
|
|
// in 'b', and vice-versa, for a total of 16 point-vs-latitude-edge tests
|
|
// and 16 point-vs-longitude-edge tests.
|
|
S2LatLng pntA[] =
|
|
{new S2LatLng(a.latLo(), a.lngLo()), new S2LatLng(a.latLo(), a.lngHi()),
|
|
new S2LatLng(a.latHi(), a.lngHi()), new S2LatLng(a.latHi(), a.lngLo())};
|
|
S2LatLng pntB[] =
|
|
{new S2LatLng(b.latLo(), b.lngLo()), new S2LatLng(b.latLo(), b.lngHi()),
|
|
new S2LatLng(b.latHi(), b.lngHi()), new S2LatLng(b.latHi(), b.lngLo())};
|
|
|
|
// Make arrays containing the lo/hi latitudes and the lo/hi longitude edges.
|
|
S1Angle latA[] = {a.latLo(), a.latHi()};
|
|
S1Angle latB[] = {b.latLo(), b.latHi()};
|
|
S2Point lng_edge_a[][] =
|
|
{ {pntA[0].toPoint(), pntA[3].toPoint()}, {pntA[1].toPoint(), pntA[2].toPoint()}};
|
|
S2Point lng_edge_b[][] =
|
|
{ {pntB[0].toPoint(), pntB[3].toPoint()}, {pntB[1].toPoint(), pntB[2].toPoint()}};
|
|
|
|
S1Angle minDistance = S1Angle.degrees(180.0);
|
|
for (int i = 0; i < 4; ++i) {
|
|
// For each point in a and b.
|
|
S2LatLng currentA = pntA[i];
|
|
S2LatLng currentB = pntB[i];
|
|
|
|
for (int j = 0; j < 2; ++j) {
|
|
// Get distances to latitude and longitude edges.
|
|
S1Angle aToLat = getDistance(currentA, latB[j], b.lng());
|
|
S1Angle bToLat = getDistance(currentB, latA[j], a.lng());
|
|
S1Angle aToLng =
|
|
S2EdgeUtil.getDistance(currentA.toPoint(), lng_edge_b[j][0], lng_edge_b[j][1]);
|
|
S1Angle bToLng =
|
|
S2EdgeUtil.getDistance(currentB.toPoint(), lng_edge_a[j][0], lng_edge_a[j][1]);
|
|
|
|
minDistance = S1Angle.min(
|
|
minDistance, S1Angle.min(aToLat, S1Angle.min(bToLat, S1Angle.min(aToLng, bToLng))));
|
|
}
|
|
}
|
|
return minDistance;
|
|
}
|
|
|
|
private static S1Angle bruteForceRectPointDistance(S2LatLngRect a, S2LatLng b) {
|
|
if (a.contains(b)) {
|
|
return S1Angle.radians(0);
|
|
}
|
|
|
|
S1Angle bToLoLat = getDistance(b, a.latLo(), a.lng());
|
|
S1Angle bToHiLat = getDistance(b, a.latHi(), a.lng());
|
|
S1Angle bToLoLng =
|
|
S2EdgeUtil.getDistance(b.toPoint(), new S2LatLng(a.latLo(), a.lngLo()).toPoint(),
|
|
new S2LatLng(a.latHi(), a.lngLo()).toPoint());
|
|
S1Angle bToHiLng =
|
|
S2EdgeUtil.getDistance(b.toPoint(), new S2LatLng(a.latLo(), a.lngHi()).toPoint(),
|
|
new S2LatLng(a.latHi(), a.lngHi()).toPoint());
|
|
return S1Angle.min(bToLoLat, S1Angle.min(bToHiLat, S1Angle.min(bToLoLng, bToHiLng)));
|
|
}
|
|
|
|
/**
|
|
* Returns the minimum distance from X to the latitude line segment defined by
|
|
* the given latitude and longitude interval.
|
|
*/
|
|
private static S1Angle getDistance(S2LatLng x, S1Angle lat, S1Interval interval) {
|
|
assertTrue(x.isValid());
|
|
assertTrue(interval.isValid());
|
|
|
|
// Is X inside the longitude interval?
|
|
if (interval.contains(x.lng().radians()))
|
|
return S1Angle.radians(Math.abs(x.lat().radians() - lat.radians()));
|
|
|
|
// Return the distance to the closer endpoint.
|
|
return S1Angle.min(x.getDistance(new S2LatLng(lat, S1Angle.radians(interval.lo()))),
|
|
x.getDistance(new S2LatLng(lat, S1Angle.radians(interval.hi()))));
|
|
}
|
|
|
|
private static S2LatLngRect getEdgeBound(double x1,
|
|
double y1,
|
|
double z1,
|
|
double x2,
|
|
double y2,
|
|
double z2) {
|
|
return S2LatLngRect.fromEdge(
|
|
S2Point.normalize(new S2Point(x1, y1, z1)), S2Point.normalize(new S2Point(x2, y2, z2)));
|
|
}
|
|
|
|
private static S2LatLngRect pointRectFromDegrees(double lat, double lng) {
|
|
return S2LatLngRect.fromPoint(S2LatLng.fromDegrees(lat, lng).normalized());
|
|
}
|
|
|
|
private static S2LatLngRect rectFromDegrees(
|
|
double latLo, double lngLo, double latHi, double lngHi) {
|
|
// Convenience method to construct a rectangle. This method is
|
|
// intentionally *not* in the S2LatLngRect interface because the
|
|
// argument order is ambiguous, but hopefully it's not too confusing
|
|
// within the context of this unit test.
|
|
|
|
return new S2LatLngRect(S2LatLng.fromDegrees(latLo, lngLo).normalized(),
|
|
S2LatLng.fromDegrees(latHi, lngHi).normalized());
|
|
}
|
|
|
|
/**
|
|
* This method verifies a.getDistance(b), where b is a S2LatLng, by comparing
|
|
* its result against a.getDistance(c), c being the point rectangle created
|
|
* from b.
|
|
*/
|
|
private static void verifyGetRectPointDistance(S2LatLngRect a, S2LatLng p) {
|
|
S1Angle distance1 = bruteForceRectPointDistance(a, p.normalized());
|
|
S1Angle distance2 = a.getDistance(p.normalized());
|
|
assertEquals(distance1.radians(), distance2.radians(), 1e-10);
|
|
}
|
|
|
|
/**
|
|
* This method verifies a.getDistance(b) by comparing its result against a
|
|
* brute-force implementation. The correctness of the brute-force version is
|
|
* much easier to verify by inspection.
|
|
*/
|
|
private static void verifyGetDistance(S2LatLngRect a, S2LatLngRect b) {
|
|
S1Angle distance1 = bruteForceDistance(a, b);
|
|
S1Angle distance2 = a.getDistance(b);
|
|
assertEquals(distance1.radians(), distance2.radians(), 1e-10);
|
|
}
|
|
}
|