blob: bc39ef0e8db01c44cb468b580be72054dd11c846 [file] [log] [blame]
/*
* 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 S2CapTest extends GeometryTestCase {
public S2Point getLatLngPoint(double latDegrees, double lngDegrees) {
return S2LatLng.fromDegrees(latDegrees, lngDegrees).toPoint();
}
// About 9 times the double-precision roundoff relative error.
public static final double EPS = 1e-15;
public void testBasic() {
// Test basic properties of empty and full caps.
S2Cap empty = S2Cap.empty();
S2Cap full = S2Cap.full();
assertTrue(empty.isValid());
assertTrue(empty.isEmpty());
assertTrue(empty.complement().isFull());
assertTrue(full.isValid());
assertTrue(full.isFull());
assertTrue(full.complement().isEmpty());
assertEquals(full.height(), 2.0);
assertDoubleNear(full.angle().degrees(), 180);
// Containment and intersection of empty and full caps.
assertTrue(empty.contains(empty));
assertTrue(full.contains(empty));
assertTrue(full.contains(full));
assertTrue(!empty.interiorIntersects(empty));
assertTrue(full.interiorIntersects(full));
assertTrue(!full.interiorIntersects(empty));
// Singleton cap containing the x-axis.
S2Cap xaxis = S2Cap.fromAxisHeight(new S2Point(1, 0, 0), 0);
assertTrue(xaxis.contains(new S2Point(1, 0, 0)));
assertTrue(!xaxis.contains(new S2Point(1, 1e-20, 0)));
assertEquals(xaxis.angle().radians(), 0.0);
// Singleton cap containing the y-axis.
S2Cap yaxis = S2Cap.fromAxisAngle(new S2Point(0, 1, 0), S1Angle.radians(0));
assertTrue(!yaxis.contains(xaxis.axis()));
assertEquals(xaxis.height(), 0.0);
// Check that the complement of a singleton cap is the full cap.
S2Cap xcomp = xaxis.complement();
assertTrue(xcomp.isValid());
assertTrue(xcomp.isFull());
assertTrue(xcomp.contains(xaxis.axis()));
// Check that the complement of the complement is *not* the original.
assertTrue(xcomp.complement().isValid());
assertTrue(xcomp.complement().isEmpty());
assertTrue(!xcomp.complement().contains(xaxis.axis()));
// Check that very small caps can be represented accurately.
// Here "kTinyRad" is small enough that unit vectors perturbed by this
// amount along a tangent do not need to be renormalized.
final double kTinyRad = 1e-10;
S2Cap tiny =
S2Cap.fromAxisAngle(S2Point.normalize(new S2Point(1, 2, 3)), S1Angle.radians(kTinyRad));
S2Point tangent = S2Point.normalize(S2Point.crossProd(tiny.axis(), new S2Point(3, 2, 1)));
assertTrue(tiny.contains(S2Point.add(tiny.axis(), S2Point.mul(tangent, 0.99 * kTinyRad))));
assertTrue(!tiny.contains(S2Point.add(tiny.axis(), S2Point.mul(tangent, 1.01 * kTinyRad))));
// Basic tests on a hemispherical cap.
S2Cap hemi = S2Cap.fromAxisHeight(S2Point.normalize(new S2Point(1, 0, 1)), 1);
assertEquals(hemi.complement().axis(), S2Point.neg(hemi.axis()));
assertEquals(hemi.complement().height(), 1.0);
assertTrue(hemi.contains(new S2Point(1, 0, 0)));
assertTrue(!hemi.complement().contains(new S2Point(1, 0, 0)));
assertTrue(hemi.contains(S2Point.normalize(new S2Point(1, 0, -(1 - EPS)))));
assertTrue(!hemi.interiorContains(S2Point.normalize(new S2Point(1, 0, -(1 + EPS)))));
// A concave cap.
S2Cap concave = S2Cap.fromAxisAngle(getLatLngPoint(80, 10), S1Angle.degrees(150));
assertTrue(concave.contains(getLatLngPoint(-70 * (1 - EPS), 10)));
assertTrue(!concave.contains(getLatLngPoint(-70 * (1 + EPS), 10)));
assertTrue(concave.contains(getLatLngPoint(-50 * (1 - EPS), -170)));
assertTrue(!concave.contains(getLatLngPoint(-50 * (1 + EPS), -170)));
// Cap containment tests.
assertTrue(!empty.contains(xaxis));
assertTrue(!empty.interiorIntersects(xaxis));
assertTrue(full.contains(xaxis));
assertTrue(full.interiorIntersects(xaxis));
assertTrue(!xaxis.contains(full));
assertTrue(!xaxis.interiorIntersects(full));
assertTrue(xaxis.contains(xaxis));
assertTrue(!xaxis.interiorIntersects(xaxis));
assertTrue(xaxis.contains(empty));
assertTrue(!xaxis.interiorIntersects(empty));
assertTrue(hemi.contains(tiny));
assertTrue(hemi.contains(
S2Cap.fromAxisAngle(new S2Point(1, 0, 0), S1Angle.radians(S2.M_PI_4 - EPS))));
assertTrue(!hemi.contains(
S2Cap.fromAxisAngle(new S2Point(1, 0, 0), S1Angle.radians(S2.M_PI_4 + EPS))));
assertTrue(concave.contains(hemi));
assertTrue(concave.interiorIntersects(hemi.complement()));
assertTrue(!concave.contains(S2Cap.fromAxisHeight(S2Point.neg(concave.axis()), 0.1)));
}
public void testRectBound() {
// Empty and full caps.
assertTrue(S2Cap.empty().getRectBound().isEmpty());
assertTrue(S2Cap.full().getRectBound().isFull());
final double kDegreeEps = 1e-13;
// Maximum allowable error for latitudes and longitudes measured in
// degrees. (assertDoubleNear uses a fixed tolerance that is too small.)
// Cap that includes the south pole.
S2LatLngRect rect =
S2Cap.fromAxisAngle(getLatLngPoint(-45, 57), S1Angle.degrees(50)).getRectBound();
assertDoubleNear(rect.latLo().degrees(), -90, kDegreeEps);
assertDoubleNear(rect.latHi().degrees(), 5, kDegreeEps);
assertTrue(rect.lng().isFull());
// Cap that is tangent to the north pole.
rect = S2Cap.fromAxisAngle(S2Point.normalize(new S2Point(1, 0, 1)), S1Angle.radians(S2.M_PI_4))
.getRectBound();
assertDoubleNear(rect.lat().lo(), 0);
assertDoubleNear(rect.lat().hi(), S2.M_PI_2);
assertTrue(rect.lng().isFull());
rect = S2Cap
.fromAxisAngle(S2Point.normalize(new S2Point(1, 0, 1)), S1Angle.degrees(45)).getRectBound();
assertDoubleNear(rect.latLo().degrees(), 0, kDegreeEps);
assertDoubleNear(rect.latHi().degrees(), 90, kDegreeEps);
assertTrue(rect.lng().isFull());
// The eastern hemisphere.
rect = S2Cap
.fromAxisAngle(new S2Point(0, 1, 0), S1Angle.radians(S2.M_PI_2 + 5e-16)).getRectBound();
assertDoubleNear(rect.latLo().degrees(), -90, kDegreeEps);
assertDoubleNear(rect.latHi().degrees(), 90, kDegreeEps);
assertTrue(rect.lng().isFull());
// A cap centered on the equator.
rect = S2Cap.fromAxisAngle(getLatLngPoint(0, 50), S1Angle.degrees(20)).getRectBound();
assertDoubleNear(rect.latLo().degrees(), -20, kDegreeEps);
assertDoubleNear(rect.latHi().degrees(), 20, kDegreeEps);
assertDoubleNear(rect.lngLo().degrees(), 30, kDegreeEps);
assertDoubleNear(rect.lngHi().degrees(), 70, kDegreeEps);
// A cap centered on the north pole.
rect = S2Cap.fromAxisAngle(getLatLngPoint(90, 123), S1Angle.degrees(10)).getRectBound();
assertDoubleNear(rect.latLo().degrees(), 80, kDegreeEps);
assertDoubleNear(rect.latHi().degrees(), 90, kDegreeEps);
assertTrue(rect.lng().isFull());
}
public void testCells() {
// For each cube face, we construct some cells on
// that face and some caps whose positions are relative to that face,
// and then check for the expected intersection/containment results.
// The distance from the center of a face to one of its vertices.
final double kFaceRadius = Math.atan(S2.M_SQRT2);
for (int face = 0; face < 6; ++face) {
// The cell consisting of the entire face.
S2Cell rootCell = S2Cell.fromFacePosLevel(face, (byte) 0, 0);
// A leaf cell at the midpoint of the v=1 edge.
S2Cell edgeCell = new S2Cell(S2Projections.faceUvToXyz(face, 0, 1 - EPS));
// A leaf cell at the u=1, v=1 corner.
S2Cell cornerCell = new S2Cell(S2Projections.faceUvToXyz(face, 1 - EPS, 1 - EPS));
// Quick check for full and empty caps.
assertTrue(S2Cap.full().contains(rootCell));
assertTrue(!S2Cap.empty().mayIntersect(rootCell));
// Check intersections with the bounding caps of the leaf cells that are
// adjacent to 'corner_cell' along the Hilbert curve. Because this corner
// is at (u=1,v=1), the curve stays locally within the same cube face.
S2CellId first = cornerCell.id().prev().prev().prev();
S2CellId last = cornerCell.id().next().next().next().next();
for (S2CellId id = first; id.lessThan(last); id = id.next()) {
S2Cell cell = new S2Cell(id);
assertEquals(cell.getCapBound().contains(cornerCell), id.equals(cornerCell.id()));
assertEquals(
cell.getCapBound().mayIntersect(cornerCell), id.parent().contains(cornerCell.id()));
}
int antiFace = (face + 3) % 6; // Opposite face.
for (int capFace = 0; capFace < 6; ++capFace) {
// A cap that barely contains all of 'cap_face'.
S2Point center = S2Projections.getNorm(capFace);
S2Cap covering = S2Cap.fromAxisAngle(center, S1Angle.radians(kFaceRadius + EPS));
assertEquals(covering.contains(rootCell), capFace == face);
assertEquals(covering.mayIntersect(rootCell), capFace != antiFace);
assertEquals(covering.contains(edgeCell), center.dotProd(edgeCell.getCenter()) > 0.1);
assertEquals(covering.contains(edgeCell), covering.mayIntersect(edgeCell));
assertEquals(covering.contains(cornerCell), capFace == face);
assertEquals(
covering.mayIntersect(cornerCell), center.dotProd(cornerCell.getCenter()) > 0);
// A cap that barely intersects the edges of 'cap_face'.
S2Cap bulging = S2Cap.fromAxisAngle(center, S1Angle.radians(S2.M_PI_4 + EPS));
assertTrue(!bulging.contains(rootCell));
assertEquals(bulging.mayIntersect(rootCell), capFace != antiFace);
assertEquals(bulging.contains(edgeCell), capFace == face);
assertEquals(bulging.mayIntersect(edgeCell), center.dotProd(edgeCell.getCenter()) > 0.1);
assertTrue(!bulging.contains(cornerCell));
assertTrue(!bulging.mayIntersect(cornerCell));
// A singleton cap.
S2Cap singleton = S2Cap.fromAxisAngle(center, S1Angle.radians(0));
assertEquals(singleton.mayIntersect(rootCell), capFace == face);
assertTrue(!singleton.mayIntersect(edgeCell));
assertTrue(!singleton.mayIntersect(cornerCell));
}
}
}
}