blob: cb0719c4e55bf4068398c53e8b4f9d73f86f69ae [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;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import junit.framework.TestCase;
import java.util.List;
import java.util.Random;
public strictfp class GeometryTestCase extends TestCase {
public Random rand;
@Override
protected void setUp() {
rand = new Random(123456);
}
public void assertDoubleNear(double a, double b) {
assertDoubleNear(a, b, 1e-9);
}
public void assertDoubleNear(double a, double b, double error) {
assertTrue(a + error > b);
assertTrue(a < b + error);
}
// maybe these should be put in a special testing util class
/** Return a random unit-length vector. */
public S2Point randomPoint() {
return S2Point.normalize(new S2Point(
2 * rand.nextDouble() - 1,
2 * rand.nextDouble() - 1,
2 * rand.nextDouble() - 1));
}
/**
* Return a right-handed coordinate frame (three orthonormal vectors). Returns
* an array of three points: x,y,z
*/
public ImmutableList<S2Point> getRandomFrame() {
S2Point p0 = randomPoint();
S2Point p1 = S2Point.normalize(S2Point.crossProd(p0, randomPoint()));
S2Point p2 = S2Point.normalize(S2Point.crossProd(p0, p1));
return ImmutableList.of(p0, p1, p2);
}
/**
* Return a random cell id at the given level or at a randomly chosen level.
* The distribution is uniform over the space of cell ids, but only
* approximately uniform over the surface of the sphere.
*/
public S2CellId getRandomCellId(int level) {
int face = random(S2CellId.NUM_FACES);
long pos = rand.nextLong() & ((1L << (2 * S2CellId.MAX_LEVEL)) - 1);
return S2CellId.fromFacePosLevel(face, pos, level);
}
public S2CellId getRandomCellId() {
return getRandomCellId(random(S2CellId.MAX_LEVEL + 1));
}
int random(int n) {
if (n == 0) {
return 0;
}
return rand.nextInt(n);
}
// Pick "base" uniformly from range [0,maxLog] and then return
// "base" random bits. The effect is to pick a number in the range
// [0,2^maxLog-1] with bias towards smaller numbers.
int skewed(int maxLog) {
final int base = Math.abs(rand.nextInt()) % (maxLog + 1);
// if (!base) return 0; // if 0==base, we & with 0 below.
//
// this distribution differs slightly from ACMRandom's Skewed,
// since 0 occurs approximately 3 times more than 1 here, and
// ACMRandom's Skewed never outputs 0.
return rand.nextInt() & ((1 << base) - 1);
}
/**
* Checks that "covering" completely covers the given region. If "check_tight"
* is true, also checks that it does not contain any cells that do not
* intersect the given region. ("id" is only used internally.)
*/
void checkCovering(S2Region region, S2CellUnion covering, boolean checkTight, S2CellId id) {
if (!id.isValid()) {
for (int face = 0; face < 6; ++face) {
checkCovering(region, covering, checkTight, S2CellId.fromFacePosLevel(face, 0, 0));
}
return;
}
if (!region.mayIntersect(new S2Cell(id))) {
// If region does not intersect id, then neither should the covering.
if (checkTight) {
assertTrue(!covering.intersects(id));
}
} else if (!covering.contains(id)) {
// The region may intersect id, but we can't assert that the covering
// intersects id because we may discover that the region does not actually
// intersect upon further subdivision. (MayIntersect is not exact.)
assertTrue(!region.contains(new S2Cell(id)));
assertTrue(!id.isLeaf());
S2CellId end = id.childEnd();
for (S2CellId child = id.childBegin(); !child.equals(end); child = child.next()) {
checkCovering(region, covering, checkTight, child);
}
}
}
S2Cap getRandomCap(double minArea, double maxArea) {
double capArea = maxArea
* Math.pow(minArea / maxArea, rand.nextDouble());
assertTrue(capArea >= minArea && capArea <= maxArea);
// The surface area of a cap is 2*Pi times its height.
return S2Cap.fromAxisArea(randomPoint(), capArea);
}
S2Point samplePoint(S2Cap cap) {
// We consider the cap axis to be the "z" axis. We choose two other axes to
// complete the coordinate frame.
S2Point z = cap.axis();
S2Point x = z.ortho();
S2Point y = S2Point.crossProd(z, x);
// The surface area of a spherical cap is directly proportional to its
// height. First we choose a random height, and then we choose a random
// point along the circle at that height.
double h = rand.nextDouble() * cap.height();
double theta = 2 * S2.M_PI * rand.nextDouble();
double r = Math.sqrt(h * (2 - h)); // Radius of circle.
// (cos(theta)*r*x + sin(theta)*r*y + (1-h)*z).Normalize()
return S2Point.normalize(S2Point.add(
S2Point.add(S2Point.mul(x, Math.cos(theta) * r), S2Point.mul(y, Math.sin(theta) * r)),
S2Point.mul(z, (1 - h))));
}
static void parseVertices(String str, List<S2Point> vertices) {
if (str == null) {
return;
}
for (String token : Splitter.on(',').split(str)) {
int colon = token.indexOf(':');
if (colon == -1) {
throw new IllegalArgumentException(
"Illegal string:" + token + ". Should look like '35:20'");
}
double lat = Double.parseDouble(token.substring(0, colon));
double lng = Double.parseDouble(token.substring(colon + 1));
vertices.add(S2LatLng.fromDegrees(lat, lng).toPoint());
}
}
static S2Point makePoint(String str) {
List<S2Point> vertices = Lists.newArrayList();
parseVertices(str, vertices);
return Iterables.getOnlyElement(vertices);
}
static S2Loop makeLoop(String str) {
List<S2Point> vertices = Lists.newArrayList();
parseVertices(str, vertices);
return new S2Loop(vertices);
}
static S2Polygon makePolygon(String str) {
List<S2Loop> loops = Lists.newArrayList();
for (String token : Splitter.on(';').omitEmptyStrings().split(str)) {
S2Loop loop = makeLoop(token);
loop.normalize();
loops.add(loop);
}
return new S2Polygon(loops);
}
static S2Polyline makePolyline(String str) {
List<S2Point> vertices = Lists.newArrayList();
parseVertices(str, vertices);
return new S2Polyline(vertices);
}
}