You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
6.8 KiB
212 lines
6.8 KiB
/*
|
|
* 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);
|
|
}
|
|
}
|