private static void gatherStats(S2Cell cell) { var s = levelStats[cell.Level]; var exactArea = cell.ExactArea(); var approxArea = cell.ApproxArea(); double minEdge = 100, maxEdge = 0, avgEdge = 0; double minDiag = 100, maxDiag = 0; double minWidth = 100, maxWidth = 0; double minAngleSpan = 100, maxAngleSpan = 0; for (var i = 0; i < 4; ++i) { var edge = cell.GetVertexRaw(i).Angle(cell.GetVertexRaw((i + 1) & 3)); minEdge = Math.Min(edge, minEdge); maxEdge = Math.Max(edge, maxEdge); avgEdge += 0.25*edge; var mid = cell.GetVertexRaw(i) + cell.GetVertexRaw((i + 1) & 3); var width = S2.PiOver2 - mid.Angle(cell.GetEdgeRaw(i ^ 2)); minWidth = Math.Min(width, minWidth); maxWidth = Math.Max(width, maxWidth); if (i < 2) { var diag = cell.GetVertexRaw(i).Angle(cell.GetVertexRaw(i ^ 2)); minDiag = Math.Min(diag, minDiag); maxDiag = Math.Max(diag, maxDiag); var angleSpan = cell.GetEdgeRaw(i).Angle( -cell.GetEdgeRaw(i ^ 2)); minAngleSpan = Math.Min(angleSpan, minAngleSpan); maxAngleSpan = Math.Max(angleSpan, maxAngleSpan); } } s.count += 1; s.minArea = Math.Min(exactArea, s.minArea); s.maxArea = Math.Max(exactArea, s.maxArea); s.avgArea += exactArea; s.minWidth = Math.Min(minWidth, s.minWidth); s.maxWidth = Math.Max(maxWidth, s.maxWidth); s.avgWidth += 0.5*(minWidth + maxWidth); s.minEdge = Math.Min(minEdge, s.minEdge); s.maxEdge = Math.Max(maxEdge, s.maxEdge); s.avgEdge += avgEdge; s.maxEdgeAspect = Math.Max(maxEdge/minEdge, s.maxEdgeAspect); s.minDiag = Math.Min(minDiag, s.minDiag); s.maxDiag = Math.Max(maxDiag, s.maxDiag); s.avgDiag += 0.5*(minDiag + maxDiag); s.maxDiagAspect = Math.Max(maxDiag/minDiag, s.maxDiagAspect); s.minAngleSpan = Math.Min(minAngleSpan, s.minAngleSpan); s.maxAngleSpan = Math.Max(maxAngleSpan, s.maxAngleSpan); s.avgAngleSpan += 0.5*(minAngleSpan + maxAngleSpan); var approxRatio = approxArea/exactArea; s.minApproxRatio = Math.Min(approxRatio, s.minApproxRatio); s.maxApproxRatio = Math.Max(approxRatio, s.maxApproxRatio); }
public void testFaces() { IDictionary <S2Point, int> edgeCounts = new Dictionary <S2Point, int>(); IDictionary <S2Point, int> vertexCounts = new Dictionary <S2Point, int>(); for (var face = 0; face < 6; ++face) { var id = S2CellId.FromFacePosLevel(face, 0, 0); var cell = new S2Cell(id); JavaAssert.Equal(cell.Id, id); JavaAssert.Equal(cell.Face, face); JavaAssert.Equal(cell.Level, 0); // Top-level faces have alternating orientations to get RHS coordinates. JavaAssert.Equal(cell.Orientation, face & S2.SwapMask); Assert.True(!cell.IsLeaf); for (var k = 0; k < 4; ++k) { if (edgeCounts.ContainsKey(cell.GetEdgeRaw(k))) { edgeCounts[cell.GetEdgeRaw(k)] = edgeCounts[cell .GetEdgeRaw(k)] + 1; } else { edgeCounts[cell.GetEdgeRaw(k)] = 1; } if (vertexCounts.ContainsKey(cell.GetVertexRaw(k))) { vertexCounts[cell.GetVertexRaw(k)] = vertexCounts[cell .GetVertexRaw(k)] + 1; } else { vertexCounts[cell.GetVertexRaw(k)] = 1; } assertDoubleNear(cell.GetVertexRaw(k).DotProd(cell.GetEdgeRaw(k)), 0); assertDoubleNear(cell.GetVertexRaw((k + 1) & 3).DotProd( cell.GetEdgeRaw(k)), 0); assertDoubleNear(S2Point.Normalize( S2Point.CrossProd(cell.GetVertexRaw(k), cell .GetVertexRaw((k + 1) & 3))).DotProd(cell.GetEdge(k)), 1.0); } } // Check that edges have multiplicity 2 and vertices have multiplicity 3. foreach (var i in edgeCounts.Values) { JavaAssert.Equal(i, 2); } foreach (var i in vertexCounts.Values) { JavaAssert.Equal(i, 3); } }
private static void gatherStats(S2Cell cell) { var s = levelStats[cell.Level]; var exactArea = cell.ExactArea(); var approxArea = cell.ApproxArea(); double minEdge = 100, maxEdge = 0, avgEdge = 0; double minDiag = 100, maxDiag = 0; double minWidth = 100, maxWidth = 0; double minAngleSpan = 100, maxAngleSpan = 0; for (var i = 0; i < 4; ++i) { var edge = cell.GetVertexRaw(i).Angle(cell.GetVertexRaw((i + 1) & 3)); minEdge = Math.Min(edge, minEdge); maxEdge = Math.Max(edge, maxEdge); avgEdge += 0.25 * edge; var mid = cell.GetVertexRaw(i) + cell.GetVertexRaw((i + 1) & 3); var width = S2.PiOver2 - mid.Angle(cell.GetEdgeRaw(i ^ 2)); minWidth = Math.Min(width, minWidth); maxWidth = Math.Max(width, maxWidth); if (i < 2) { var diag = cell.GetVertexRaw(i).Angle(cell.GetVertexRaw(i ^ 2)); minDiag = Math.Min(diag, minDiag); maxDiag = Math.Max(diag, maxDiag); var angleSpan = cell.GetEdgeRaw(i).Angle( -cell.GetEdgeRaw(i ^ 2)); minAngleSpan = Math.Min(angleSpan, minAngleSpan); maxAngleSpan = Math.Max(angleSpan, maxAngleSpan); } } s.count += 1; s.minArea = Math.Min(exactArea, s.minArea); s.maxArea = Math.Max(exactArea, s.maxArea); s.avgArea += exactArea; s.minWidth = Math.Min(minWidth, s.minWidth); s.maxWidth = Math.Max(maxWidth, s.maxWidth); s.avgWidth += 0.5 * (minWidth + maxWidth); s.minEdge = Math.Min(minEdge, s.minEdge); s.maxEdge = Math.Max(maxEdge, s.maxEdge); s.avgEdge += avgEdge; s.maxEdgeAspect = Math.Max(maxEdge / minEdge, s.maxEdgeAspect); s.minDiag = Math.Min(minDiag, s.minDiag); s.maxDiag = Math.Max(maxDiag, s.maxDiag); s.avgDiag += 0.5 * (minDiag + maxDiag); s.maxDiagAspect = Math.Max(maxDiag / minDiag, s.maxDiagAspect); s.minAngleSpan = Math.Min(minAngleSpan, s.minAngleSpan); s.maxAngleSpan = Math.Max(maxAngleSpan, s.maxAngleSpan); s.avgAngleSpan += 0.5 * (minAngleSpan + maxAngleSpan); var approxRatio = approxArea / exactArea; s.minApproxRatio = Math.Min(approxRatio, s.minApproxRatio); s.maxApproxRatio = Math.Max(approxRatio, s.maxApproxRatio); }
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 var vertexContained = false; for (var 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 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 var vertexContained = false; for (var 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. var empty = S2LatLngRect.Empty; var full = S2LatLngRect.Full; assertTrue(empty.IsValid); assertTrue(empty.IsEmpty); assertTrue(full.IsValid); assertTrue(full.IsFull); // assertTrue various constructors and accessor methods. var 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.PiOver2, -S2.PiOver4))); assertTrue(d1.Lng.Equals(new S1Interval(0, S2.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). var eqM180 = S2LatLng.FromRadians(0, -S2.Pi); var northPole = S2LatLng.FromRadians(S2.PiOver2, 0); var r1 = new S2LatLngRect(eqM180, northPole); assertEquals(r1.Center, S2LatLng.FromRadians(S2.PiOver4, -S2.PiOver2)); assertEquals(r1.GetVertex(0), S2LatLng.FromRadians(0, S2.Pi)); assertEquals(r1.GetVertex(1), S2LatLng.FromRadians(0, 0)); assertEquals(r1.GetVertex(2), S2LatLng.FromRadians(S2.PiOver2, 0)); assertEquals(r1.GetVertex(3), S2LatLng.FromRadians(S2.PiOver2, S2.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 (var i = 0; i < 4; ++i) { var lat = S2.PiOver4*(i - 2); var lng = S2.PiOver2*(i - 2) + 0.2; var r = new S2LatLngRect(new R1Interval(lat, lat + S2.PiOver4), new S1Interval( Math.IEEERemainder(lng, 2*S2.Pi), Math.IEEERemainder(lng + S2.PiOver2, 2*S2.Pi))); for (var 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. var r1Mid = rectFromDegrees(45, -90, 45, -90); var reqM180 = new S2LatLngRect(eqM180, eqM180); var 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() var p = S2LatLngRect.Empty; p = p.AddPoint(S2LatLng.FromDegrees(0, 0)); p = p.AddPoint(S2LatLng.FromRadians(0, -S2.PiOver2)); p = p.AddPoint(S2LatLng.FromRadians(S2.PiOver4, -S2.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() var llr1 = new S2LatLngRect(S2LatLng.FromDegrees(0, 170), S2LatLng.FromDegrees(0, -170)) .ConvolveWithCap(S1Angle.FromDegrees(15)); var 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.FromDegrees(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)).CapBound.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)).CapBound.ApproxEquals(S2Cap.FromAxisAngle(new S2Point(0, 0, 1), S1Angle.FromDegrees(2)))); // GetCapBound(), longitude span > 180 degrees: assertTrue( new S2LatLngRect(S2LatLng.FromDegrees(-30, -150), S2LatLng.FromDegrees(-10, 50)).CapBound .ApproxEquals(S2Cap.FromAxisAngle(new S2Point(0, 0, -1), S1Angle.FromDegrees(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. var 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. var 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. var cell0tr = new S2Cell(new S2Point(1 + 1e-12, 1, 1)); var bound0tr = cell0tr.RectBound; var 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. var cell202 = S2Cell.FromFacePosLevel(2, (byte)0, 2); var bound202 = cell202.RectBound; 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 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. var empty = S2LatLngRect.Empty; var full = S2LatLngRect.Full; assertTrue(empty.IsValid); assertTrue(empty.IsEmpty); assertTrue(full.IsValid); assertTrue(full.IsFull); // assertTrue various constructors and accessor methods. var 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.PiOver2, -S2.PiOver4))); assertTrue(d1.Lng.Equals(new S1Interval(0, S2.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). var eqM180 = S2LatLng.FromRadians(0, -S2.Pi); var northPole = S2LatLng.FromRadians(S2.PiOver2, 0); var r1 = new S2LatLngRect(eqM180, northPole); assertEquals(r1.Center, S2LatLng.FromRadians(S2.PiOver4, -S2.PiOver2)); assertEquals(r1.GetVertex(0), S2LatLng.FromRadians(0, S2.Pi)); assertEquals(r1.GetVertex(1), S2LatLng.FromRadians(0, 0)); assertEquals(r1.GetVertex(2), S2LatLng.FromRadians(S2.PiOver2, 0)); assertEquals(r1.GetVertex(3), S2LatLng.FromRadians(S2.PiOver2, S2.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 (var i = 0; i < 4; ++i) { var lat = S2.PiOver4 * (i - 2); var lng = S2.PiOver2 * (i - 2) + 0.2; var r = new S2LatLngRect(new R1Interval(lat, lat + S2.PiOver4), new S1Interval( Math.IEEERemainder(lng, 2 * S2.Pi), Math.IEEERemainder(lng + S2.PiOver2, 2 * S2.Pi))); for (var 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. var r1Mid = rectFromDegrees(45, -90, 45, -90); var reqM180 = new S2LatLngRect(eqM180, eqM180); var 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() var p = S2LatLngRect.Empty; p = p.AddPoint(S2LatLng.FromDegrees(0, 0)); p = p.AddPoint(S2LatLng.FromRadians(0, -S2.PiOver2)); p = p.AddPoint(S2LatLng.FromRadians(S2.PiOver4, -S2.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() var llr1 = new S2LatLngRect(S2LatLng.FromDegrees(0, 170), S2LatLng.FromDegrees(0, -170)) .ConvolveWithCap(S1Angle.FromDegrees(15)); var 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.FromDegrees(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)).CapBound.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)).CapBound.ApproxEquals(S2Cap.FromAxisAngle(new S2Point(0, 0, 1), S1Angle.FromDegrees(2)))); // GetCapBound(), longitude span > 180 degrees: assertTrue( new S2LatLngRect(S2LatLng.FromDegrees(-30, -150), S2LatLng.FromDegrees(-10, 50)).CapBound .ApproxEquals(S2Cap.FromAxisAngle(new S2Point(0, 0, -1), S1Angle.FromDegrees(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. var 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. var 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. var cell0tr = new S2Cell(new S2Point(1 + 1e-12, 1, 1)); var bound0tr = cell0tr.RectBound; var 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. var cell202 = S2Cell.FromFacePosLevel(2, (byte)0, 2); var bound202 = cell202.RectBound; 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 testSubdivide(S2Cell cell) { gatherStats(cell); if (cell.IsLeaf) { return; } var children = new S2Cell[4]; for (var i = 0; i < children.Length; ++i) { children[i] = new S2Cell(); } Assert.True(cell.Subdivide(children)); var childId = cell.Id.ChildBegin; double exactArea = 0; double approxArea = 0; double averageArea = 0; for (var i = 0; i < 4; ++i, childId = childId.Next) { exactArea += children[i].ExactArea(); approxArea += children[i].ApproxArea(); averageArea += children[i].AverageArea(); // Check that the child geometry is consistent with its cell id. JavaAssert.Equal(children[i].Id, childId); Assert.True(children[i].Center.ApproxEquals(childId.ToPoint(), 1e-15)); var direct = new S2Cell(childId); JavaAssert.Equal(children[i].Face, direct.Face); JavaAssert.Equal(children[i].Level, direct.Level); JavaAssert.Equal(children[i].Orientation, direct.Orientation); JavaAssert.Equal(children[i].CenterRaw, direct.CenterRaw); for (var k = 0; k < 4; ++k) { JavaAssert.Equal(children[i].GetVertexRaw(k), direct.GetVertexRaw(k)); JavaAssert.Equal(children[i].GetEdgeRaw(k), direct.GetEdgeRaw(k)); } // Test Contains() and MayIntersect(). Assert.True(cell.Contains(children[i])); Assert.True(cell.MayIntersect(children[i])); Assert.True(!children[i].Contains(cell)); Assert.True(cell.Contains(children[i].CenterRaw)); for (var j = 0; j < 4; ++j) { Assert.True(cell.Contains(children[i].GetVertexRaw(j))); if (j != i) { Assert.True(!children[i].Contains(children[j].CenterRaw)); Assert.True(!children[i].MayIntersect(children[j])); } } // Test GetCapBound and GetRectBound. var parentCap = cell.CapBound; var parentRect = cell.RectBound; if (cell.Contains(new S2Point(0, 0, 1)) || cell.Contains(new S2Point(0, 0, -1))) { Assert.True(parentRect.Lng.IsFull); } var childCap = children[i].CapBound; var childRect = children[i].RectBound; Assert.True(childCap.Contains(children[i].Center)); Assert.True(childRect.Contains(children[i].CenterRaw)); Assert.True(parentCap.Contains(children[i].Center)); Assert.True(parentRect.Contains(children[i].CenterRaw)); for (var j = 0; j < 4; ++j) { Assert.True(childCap.Contains(children[i].GetVertex(j))); Assert.True(childRect.Contains(children[i].GetVertex(j))); Assert.True(childRect.Contains(children[i].GetVertexRaw(j))); Assert.True(parentCap.Contains(children[i].GetVertex(j))); if (!parentRect.Contains(children[i].GetVertex(j))) { Console.WriteLine("cell: " + cell + " i: " + i + " j: " + j); Console.WriteLine("Children " + i + ": " + children[i]); Console.WriteLine("Parent rect: " + parentRect); Console.WriteLine("Vertex raw(j) " + children[i].GetVertex(j)); Console.WriteLine("Latlng of vertex: " + new S2LatLng(children[i].GetVertex(j))); Console.WriteLine("RectBound: " + cell.RectBound); } Assert.True(parentRect.Contains(children[i].GetVertex(j))); if (!parentRect.Contains(children[i].GetVertexRaw(j))) { Console.WriteLine("cell: " + cell + " i: " + i + " j: " + j); Console.WriteLine("Children " + i + ": " + children[i]); Console.WriteLine("Parent rect: " + parentRect); Console.WriteLine("Vertex raw(j) " + children[i].GetVertexRaw(j)); Console.WriteLine("Latlng of vertex: " + new S2LatLng(children[i].GetVertexRaw(j))); Console.WriteLine("RectBound: " + cell.RectBound); } Assert.True(parentRect.Contains(children[i].GetVertexRaw(j))); if (j != i) { // The bounding caps and rectangles should be tight enough so that // they exclude at least two vertices of each adjacent cell. var capCount = 0; var rectCount = 0; for (var k = 0; k < 4; ++k) { if (childCap.Contains(children[j].GetVertex(k))) { ++capCount; } if (childRect.Contains(children[j].GetVertexRaw(k))) { ++rectCount; } } Assert.True(capCount <= 2); if (childRect.LatLo.Radians > -S2.PiOver2 && childRect.LatHi.Radians < S2.PiOver2) { // Bounding rectangles may be too large at the poles because the // pole itself has an arbitrary fixed longitude. Assert.True(rectCount <= 2); } } } // Check all children for the first few levels, and then sample randomly. // Also subdivide one corner cell, one edge cell, and one center cell // so that we have a better chance of sample the minimum metric values. var forceSubdivide = false; var center = S2Projections.GetNorm(children[i].Face); var edge = center + S2Projections.GetUAxis(children[i].Face); var corner = edge + S2Projections.GetVAxis(children[i].Face); for (var j = 0; j < 4; ++j) { var p = children[i].GetVertexRaw(j); if (p.Equals(center) || p.Equals(edge) || p.Equals(corner)) { forceSubdivide = true; } } if (forceSubdivide || cell.Level < (DEBUG_MODE ? 5 : 6) || random(DEBUG_MODE ? 10 : 4) == 0) { testSubdivide(children[i]); } } // Check sum of child areas equals parent area. // // For ExactArea(), the best relative error we can expect is about 1e-6 // because the precision of the unit vector coordinates is only about 1e-15 // and the edge length of a leaf cell is about 1e-9. // // For ApproxArea(), the areas are accurate to within a few percent. // // For AverageArea(), the areas themselves are not very accurate, but // the average area of a parent is exactly 4 times the area of a child. Assert.True(Math.Abs(Math.Log(exactArea / cell.ExactArea())) <= Math .Abs(Math.Log(1 + 1e-6))); Assert.True(Math.Abs(Math.Log(approxArea / cell.ApproxArea())) <= Math .Abs(Math.Log(1.03))); Assert.True(Math.Abs(Math.Log(averageArea / cell.AverageArea())) <= Math .Abs(Math.Log(1 + 1e-15))); }
public void testFaces() { IDictionary<S2Point, int> edgeCounts = new Dictionary<S2Point, int>(); IDictionary<S2Point, int> vertexCounts = new Dictionary<S2Point, int>(); for (var face = 0; face < 6; ++face) { var id = S2CellId.FromFacePosLevel(face, 0, 0); var cell = new S2Cell(id); JavaAssert.Equal(cell.Id, id); JavaAssert.Equal(cell.Face, face); JavaAssert.Equal(cell.Level, 0); // Top-level faces have alternating orientations to get RHS coordinates. JavaAssert.Equal(cell.Orientation, face & S2.SwapMask); Assert.True(!cell.IsLeaf); for (var k = 0; k < 4; ++k) { if (edgeCounts.ContainsKey(cell.GetEdgeRaw(k))) { edgeCounts[cell.GetEdgeRaw(k)] = edgeCounts[cell .GetEdgeRaw(k)] + 1; } else { edgeCounts[cell.GetEdgeRaw(k)] = 1; } if (vertexCounts.ContainsKey(cell.GetVertexRaw(k))) { vertexCounts[cell.GetVertexRaw(k)] = vertexCounts[cell .GetVertexRaw(k)] + 1; } else { vertexCounts[cell.GetVertexRaw(k)] = 1; } assertDoubleNear(cell.GetVertexRaw(k).DotProd(cell.GetEdgeRaw(k)), 0); assertDoubleNear(cell.GetVertexRaw((k + 1) & 3).DotProd( cell.GetEdgeRaw(k)), 0); assertDoubleNear(S2Point.Normalize( S2Point.CrossProd(cell.GetVertexRaw(k), cell .GetVertexRaw((k + 1) & 3))).DotProd(cell.GetEdge(k)), 1.0); } } // Check that edges have multiplicity 2 and vertices have multiplicity 3. foreach (var i in edgeCounts.Values) { JavaAssert.Equal(i, 2); } foreach (var i in vertexCounts.Values) { JavaAssert.Equal(i, 3); } }
public void testSubdivide(S2Cell cell) { gatherStats(cell); if (cell.IsLeaf) { return; } var children = new S2Cell[4]; for (var i = 0; i < children.Length; ++i) { children[i] = new S2Cell(); } Assert.True(cell.Subdivide(children)); var childId = cell.Id.ChildBegin; double exactArea = 0; double approxArea = 0; double averageArea = 0; for (var i = 0; i < 4; ++i, childId = childId.Next) { exactArea += children[i].ExactArea(); approxArea += children[i].ApproxArea(); averageArea += children[i].AverageArea(); // Check that the child geometry is consistent with its cell id. JavaAssert.Equal(children[i].Id, childId); Assert.True(children[i].Center.ApproxEquals(childId.ToPoint(), 1e-15)); var direct = new S2Cell(childId); JavaAssert.Equal(children[i].Face, direct.Face); JavaAssert.Equal(children[i].Level, direct.Level); JavaAssert.Equal(children[i].Orientation, direct.Orientation); JavaAssert.Equal(children[i].CenterRaw, direct.CenterRaw); for (var k = 0; k < 4; ++k) { JavaAssert.Equal(children[i].GetVertexRaw(k), direct.GetVertexRaw(k)); JavaAssert.Equal(children[i].GetEdgeRaw(k), direct.GetEdgeRaw(k)); } // Test Contains() and MayIntersect(). Assert.True(cell.Contains(children[i])); Assert.True(cell.MayIntersect(children[i])); Assert.True(!children[i].Contains(cell)); Assert.True(cell.Contains(children[i].CenterRaw)); for (var j = 0; j < 4; ++j) { Assert.True(cell.Contains(children[i].GetVertexRaw(j))); if (j != i) { Assert.True(!children[i].Contains(children[j].CenterRaw)); Assert.True(!children[i].MayIntersect(children[j])); } } // Test GetCapBound and GetRectBound. var parentCap = cell.CapBound; var parentRect = cell.RectBound; if (cell.Contains(new S2Point(0, 0, 1)) || cell.Contains(new S2Point(0, 0, -1))) { Assert.True(parentRect.Lng.IsFull); } var childCap = children[i].CapBound; var childRect = children[i].RectBound; Assert.True(childCap.Contains(children[i].Center)); Assert.True(childRect.Contains(children[i].CenterRaw)); Assert.True(parentCap.Contains(children[i].Center)); Assert.True(parentRect.Contains(children[i].CenterRaw)); for (var j = 0; j < 4; ++j) { Assert.True(childCap.Contains(children[i].GetVertex(j))); Assert.True(childRect.Contains(children[i].GetVertex(j))); Assert.True(childRect.Contains(children[i].GetVertexRaw(j))); Assert.True(parentCap.Contains(children[i].GetVertex(j))); if (!parentRect.Contains(children[i].GetVertex(j))) { Console.WriteLine("cell: " + cell + " i: " + i + " j: " + j); Console.WriteLine("Children " + i + ": " + children[i]); Console.WriteLine("Parent rect: " + parentRect); Console.WriteLine("Vertex raw(j) " + children[i].GetVertex(j)); Console.WriteLine("Latlng of vertex: " + new S2LatLng(children[i].GetVertex(j))); Console.WriteLine("RectBound: " + cell.RectBound); } Assert.True(parentRect.Contains(children[i].GetVertex(j))); if (!parentRect.Contains(children[i].GetVertexRaw(j))) { Console.WriteLine("cell: " + cell + " i: " + i + " j: " + j); Console.WriteLine("Children " + i + ": " + children[i]); Console.WriteLine("Parent rect: " + parentRect); Console.WriteLine("Vertex raw(j) " + children[i].GetVertexRaw(j)); Console.WriteLine("Latlng of vertex: " + new S2LatLng(children[i].GetVertexRaw(j))); Console.WriteLine("RectBound: " + cell.RectBound); } Assert.True(parentRect.Contains(children[i].GetVertexRaw(j))); if (j != i) { // The bounding caps and rectangles should be tight enough so that // they exclude at least two vertices of each adjacent cell. var capCount = 0; var rectCount = 0; for (var k = 0; k < 4; ++k) { if (childCap.Contains(children[j].GetVertex(k))) { ++capCount; } if (childRect.Contains(children[j].GetVertexRaw(k))) { ++rectCount; } } Assert.True(capCount <= 2); if (childRect.LatLo.Radians > -S2.PiOver2 && childRect.LatHi.Radians < S2.PiOver2) { // Bounding rectangles may be too large at the poles because the // pole itself has an arbitrary fixed longitude. Assert.True(rectCount <= 2); } } } // Check all children for the first few levels, and then sample randomly. // Also subdivide one corner cell, one edge cell, and one center cell // so that we have a better chance of sample the minimum metric values. var forceSubdivide = false; var center = S2Projections.GetNorm(children[i].Face); var edge = center + S2Projections.GetUAxis(children[i].Face); var corner = edge + S2Projections.GetVAxis(children[i].Face); for (var j = 0; j < 4; ++j) { var p = children[i].GetVertexRaw(j); if (p.Equals(center) || p.Equals(edge) || p.Equals(corner)) { forceSubdivide = true; } } if (forceSubdivide || cell.Level < (DEBUG_MODE ? 5 : 6) || random(DEBUG_MODE ? 10 : 4) == 0) { testSubdivide(children[i]); } } // Check sum of child areas equals parent area. // // For ExactArea(), the best relative error we can expect is about 1e-6 // because the precision of the unit vector coordinates is only about 1e-15 // and the edge length of a leaf cell is about 1e-9. // // For ApproxArea(), the areas are accurate to within a few percent. // // For AverageArea(), the areas themselves are not very accurate, but // the average area of a parent is exactly 4 times the area of a child. Assert.True(Math.Abs(Math.Log(exactArea/cell.ExactArea())) <= Math .Abs(Math.Log(1 + 1e-6))); Assert.True(Math.Abs(Math.Log(approxArea/cell.ApproxArea())) <= Math .Abs(Math.Log(1.03))); Assert.True(Math.Abs(Math.Log(averageArea/cell.AverageArea())) <= Math .Abs(Math.Log(1 + 1e-15))); }