| /* |
| * 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); |
| } |
| } |