private static S1ChordAngle GetMaxDistanceToEdgeBruteForce(S2Cell cell, S2Point a, S2Point b) { // If any antipodal endpoint is within the cell, the max distance is Pi. if (cell.Contains(-a) || cell.Contains(-b)) { return(S1ChordAngle.Straight); } S1ChordAngle max_dist = S1ChordAngle.Negative; for (int i = 0; i < 4; ++i) { S2Point v0 = cell.Vertex(i); S2Point v1 = cell.Vertex(i + 1); // If the antipodal edge crosses through the cell, max distance is Pi. if (S2.CrossingSign(-a, -b, v0, v1) >= 0) { return(S1ChordAngle.Straight); } S2.UpdateMaxDistance(a, v0, v1, ref max_dist); S2.UpdateMaxDistance(b, v0, v1, ref max_dist); S2.UpdateMaxDistance(v0, a, b, ref max_dist); } return(max_dist); }
// Returns true if any buffered shape intersects "cell" (to within a very // small error margin). public bool MayIntersect(S2Cell cell) { // Return true if the distance is less than or equal to "radius_". var target = new S2ClosestEdgeQuery.CellTarget(cell); return(query_.IsDistanceLess(target, radius_successor_)); }
/// <summary> /// Find the cell of the given level that covers the given Geoposition. /// This method uses the library given S2RegionCoverer to find the leaf cell containing the given Geoposition /// and uses binary operation on the leaf cell's Id to find the higher level cell containing the leaf. /// This is somehow more accurate than using the S2RegionCoverer for the higher level cell, where some errors occured. /// </summary> /// <param name="pos">Position in the cell</param> /// <param name="level">Level of the cell</param> /// <returns>The cell covering the given position that matches the specifications</returns> private S2Cell FindExactCell(BasicGeoposition pos, int level) { var point = S2LatLngRect.FromPoint(S2LatLng.FromDegrees(pos.Latitude, pos.Longitude)); var cells = new List <S2CellId>(); // find leaf cell var coverer = new S2RegionCoverer() { MinLevel = 30, MaxLevel = 30, MaxCells = 1 }; coverer.GetCovering(point, cells); var leaf = new S2Cell(cells[0]); int shift = 64 - (3 + level * 2 + 1); ulong id = leaf.Id.Id & ulong.MaxValue << shift; id |= 0 | ((ulong)1 << shift); return(new S2Cell(new S2CellId(id))); }
// Populates the children of "candidate" by expanding the given number of // levels from the given cell. Returns the number of children that were // marked "terminal". private int ExpandChildren(Candidate candidate, S2Cell cell, int num_levels) { num_levels--; var child_cells = new S2Cell[4]; cell.Subdivide(child_cells); int num_terminals = 0; for (int i = 0; i < 4; ++i) { if (num_levels > 0) { if (region_.MayIntersect(child_cells[i])) { num_terminals += ExpandChildren(candidate, child_cells[i], num_levels); } continue; } var child = NewCandidate(child_cells[i]); if (child != null) { candidate.Children[candidate.NumChildren++] = child; if (child.IsTerminal) { ++num_terminals; } } } return(num_terminals); }
} // Actual size may be 0, 4, 16, or 64 elements. public Candidate(S2Cell cell, int max_children) { Cell = cell; IsTerminal = max_children == 0; Children = new Candidate[max_children].Fill(() => null); NumChildren = 0; }
private async Task <S2Cell> GetS2Cell(ulong s2CellId) { if (this.s2CellsCache.TryGetValue(s2CellId, out S2Cell s2Cell) == false) { var getS2CellResult = await PlayablesManager.Instance.GetS2Cell(new GetS2CellRequest { CurrentLatLong = GPSManager.Instance.CurrentRawLatLong, S2CellId = s2CellId, }); s2Cell = new S2Cell { S2CellId = s2CellId, Locations = getS2CellResult.Locations, ExpirationUtc = DateTime.UtcNow.AddSeconds(getS2CellResult.SecondsToLive), }; if (this.disableCaching == false) { this.s2CellsCache.Add(s2Cell.S2CellId, s2Cell); } } return(s2Cell); }
public List <S2CellId> GetS2CellIds(ushort level, int maxCells) { //var geofence = Geofence.FromMultiPolygon(this); var bbox = GetBoundingBox(); var regionCoverer = new S2RegionCoverer { MinLevel = level, MaxLevel = level, MaxCells = maxCells, }; var region = new S2LatLngRect( S2LatLng.FromDegrees(bbox.MinimumLatitude, bbox.MinimumLongitude), S2LatLng.FromDegrees(bbox.MaximumLatitude, bbox.MaximumLongitude) ); var coverage = new List <S2CellId>(); regionCoverer.GetCovering(region, coverage); var result = new List <S2CellId>(); foreach (var cellId in coverage) { var cell = new S2Cell(cellId); for (var i = 0; i <= 3; i++) { var vertex = cell.GetVertex(i); var coord = new S2LatLng(new S2Point(vertex.X, vertex.Y, vertex.Z)); //if (geofence.Intersects(coord.LatDegrees, coord.LngDegrees)) if (GeofenceService.InPolygon(this, coord.LatDegrees, coord.LngDegrees)) { result.Add(cellId); } } } return(result); }
private static void ChooseEdgeNearCell(S2Cell cell, out S2Point a, out S2Point b) { S2Cap cap = cell.GetCapBound(); if (S2Testing.Random.OneIn(5)) { // Choose a point anywhere on the sphere. a = S2Testing.RandomPoint(); } else { // Choose a point inside or somewhere near the cell. a = S2Testing.SamplePoint(new S2Cap(cap.Center, 1.5 * cap.RadiusAngle())); } // Now choose a maximum edge length ranging from very short to very long // relative to the cell size, and choose the other endpoint. double max_length = Math.Min(100 * Math.Pow(1e-4, S2Testing.Random.RandDouble()) * cap.Radius.Radians(), S2.M_PI_2); b = S2Testing.SamplePoint(new S2Cap(a, S1Angle.FromRadians(max_length))); if (S2Testing.Random.OneIn(20)) { // Occasionally replace edge with antipodal edge. a = -a; b = -b; } }
public void Test_S2ClosestCellQuery_DistanceEqualToLimit() { // Tests the behavior of IsDistanceLess, IsDistanceLessOrEqual, and // IsConservativeDistanceLessOrEqual (and the corresponding Options) when // the distance to the target exactly equals the chosen limit. S2CellId id0 = new(MakePointOrDie("23:12")), id1 = new(MakePointOrDie("47:11")); S2CellIndex index = new(); index.Add(id0, 0); index.Build(); S2ClosestCellQuery query = new(index); // Start with two identical cells and a zero distance. S2ClosestCellQuery.CellTarget target0 = new(new S2Cell(id0)); S1ChordAngle dist0 = S1ChordAngle.Zero; Assert.False(query.IsDistanceLess(target0, dist0)); Assert.True(query.IsDistanceLessOrEqual(target0, dist0)); Assert.True(query.IsConservativeDistanceLessOrEqual(target0, dist0)); // Now try two cells separated by a non-zero distance. S2ClosestCellQuery.CellTarget target1 = new(new S2Cell(id1)); S1ChordAngle dist1 = new S2Cell(id0).Distance(new S2Cell(id1)); Assert.False(query.IsDistanceLess(target1, dist1)); Assert.True(query.IsDistanceLessOrEqual(target1, dist1)); Assert.True(query.IsConservativeDistanceLessOrEqual(target1, dist1)); }
public static ulong GenerateGeohash(GeoPoint geoPoint) { var latLng = S2LatLng.FromDegrees(geoPoint.lat, geoPoint.lng); var cell = new S2Cell(latLng); var cellId = cell.Id; return(cellId.Id); }
public static ulong GenerateGeohash(double latitude, double longitude) { var latLng = S2LatLng.FromDegrees(latitude, longitude); var cell = new S2Cell(latLng); var cellId = cell.Id; return(cellId.Id); }
public void Test_S2LatLngRect_CellOps() { // Contains(S2Cell), MayIntersect(S2Cell), Intersects(S2Cell) // Special cases. TestCellOps(S2LatLngRect.Empty, S2Cell.FromFacePosLevel(3, 0, 0), 0); TestCellOps(S2LatLngRect.Full, S2Cell.FromFacePosLevel(2, 0, 0), 4); TestCellOps(S2LatLngRect.Full, S2Cell.FromFacePosLevel(5, 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, 0, 0), 3); TestCellOps(r4, S2Cell.FromFacePosLevel(0, 0, 1), 4); TestCellOps(r4, S2Cell.FromFacePosLevel(1, 0, 1), 0); // This rectangle intersects the first quadrant of face 0. S2LatLngRect r5 = RectFromDegrees(-10, -45, 10, 0); TestCellOps(r5, S2Cell.FromFacePosLevel(0, 0, 0), 3); TestCellOps(r5, S2Cell.FromFacePosLevel(0, 0, 1), 3); TestCellOps(r5, S2Cell.FromFacePosLevel(1, 0, 1), 0); // Rectangle consisting of a single point. TestCellOps(RectFromDegrees(4, 4, 4, 4), S2Cell.FromFace(0), 3); // Rectangles that intersect the bounding rectangle of a face // but not the face itself. TestCellOps(RectFromDegrees(41, -87, 42, -79), S2Cell.FromFace(2), 1); TestCellOps(RectFromDegrees(-41, 160, -40, -160), S2Cell.FromFace(5), 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(new S2Point(1 + 1e-12, 1, 1)); _ = cell0tr.GetRectBound(); S2LatLng v0 = new(cell0tr.VertexRaw(0)); TestCellOps(RectFromDegrees(v0.Lat().GetDegrees() - 1e-8, v0.Lng().GetDegrees() - 1e-8, v0.Lat().GetDegrees() - 2e-10, v0.Lng().GetDegrees() + 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.FromFace(5), 2); // These two intersect like a diamond and a square. S2Cell cell202 = S2Cell.FromFacePosLevel(2, 0, 2); S2LatLngRect bound202 = cell202.GetRectBound(); TestCellOps(RectFromDegrees(bound202.Lo().Lat().GetDegrees() + 3, bound202.Lo().Lng().GetDegrees() + 3, bound202.Hi().Lat().GetDegrees() - 3, bound202.Hi().Lng().GetDegrees() - 3), cell202, 2); }
public void Test_ShapeIndexTarget_UpdateMinDistanceToCellWhenEqual() { var target_index = MakeIndexOrDie("1:0 # #"); var target = new S2MinDistanceShapeIndexTarget(target_index); var dist = S1ChordAngle.Infinity; var cell = new S2Cell(new S2CellId(MakePointOrDie("0:0"))); Assert.True(target.UpdateMinDistance(cell, ref dist)); Assert.False(target.UpdateMinDistance(cell, ref dist)); }
public void Test_CellTarget_UpdateMinDistanceToCellWhenEqual() { var target = new S2MinDistanceCellTarget(new S2Cell( new S2CellId(MakePointOrDie("0:1")))); var dist = S1ChordAngle.Infinity; var cell = new S2Cell(new S2CellId(MakePointOrDie("0:0"))); Assert.True(target.UpdateMinDistance(cell, ref dist)); Assert.False(target.UpdateMinDistance(cell, ref dist)); }
private void TestCell(S2Cell target) { // Indicates whether each shape that intersects "target" also contains it. Dictionary <int, bool> shape_contains = new(); Assert.True(region_.VisitIntersectingShapes( target, (S2Shape shape, bool contains_target) => { // Verify that each shape is visited at most once. Assert.False(shape_contains.ContainsKey(shape.Id)); shape_contains[shape.Id] = contains_target; return(true); })); for (int s = 0; s < index_.NumShapeIds(); ++s) { var shape_region = shape_indexes_[s].MakeS2ShapeIndexRegion(); if (!shape_region.MayIntersect(target)) { Assert.False(shape_contains.ContainsKey(s)); } else { Assert.Equal(shape_contains[s], shape_region.Contains(target)); } } var(cellRelation, pos) = index_.LocateCell(target.Id); iter_.SetPosition(pos); switch (cellRelation) { case S2ShapeIndex.CellRelation.DISJOINT: return; case S2ShapeIndex.CellRelation.SUBDIVIDED: { S2Cell[] children = new S2Cell[4]; Assert.True(target.Subdivide(children)); foreach (var child in children) { TestCell(child); } return; } case S2ShapeIndex.CellRelation.INDEXED: { // We check a few random descendant cells by continuing randomly down // one branch of the tree for a few levels. if (target.IsLeaf() || S2Testing.Random.OneIn(3)) { return; } TestCell(new S2Cell(target.Id.Child(S2Testing.Random.Uniform(4)))); return; } } }
private S2Loop makeCellLoop(S2CellId begin, S2CellId end) { // Construct a CCW polygon whose boundary is the union of the cell ids // in the range [begin, end). We Add the edges one by one, removing // any edges that are already present in the opposite direction. IDictionary <S2Point, ISet <S2Point> > edges = new Dictionary <S2Point, ISet <S2Point> >(); for (var id = begin; !id.Equals(end); id = id.Next) { var cell = new S2Cell(id); for (var k = 0; k < 4; ++k) { var a = cell.GetVertex(k); var b = cell.GetVertex((k + 1) & 3); if (!edges.ContainsKey(b)) { edges.Add(b, new HashSet <S2Point>()); } // if a is in b's set, remove it and remove b's set if it's empty // otherwise, Add b to a's set if (!edges[b].Remove(a)) { if (!edges.ContainsKey(a)) { edges.Add(a, new HashSet <S2Point>()); } edges[a].Add(b); } else if (edges[b].Count == 0) { edges.Remove(b); } } } // The remaining edges form a single loop. We simply follow it starting // at an arbitrary vertex and build up a list of vertices. var vertices = new List <S2Point>(); //S2Point p = edges.keySet().iterator().next(); var p = edges.Keys.First(); while (edges.Any()) { assertEquals(1, edges[p].Count); var next = edges[p].First(); //S2Point next = edges[p].iterator().next(); vertices.Add(p); edges.Remove(p); p = next; } return(new S2Loop(vertices)); }
private static S1ChordAngle GetDistanceToPointBruteForce(S2Cell cell, S2Point target) { S1ChordAngle min_distance = S1ChordAngle.Infinity; for (int i = 0; i < 4; ++i) { S2.UpdateMinDistance(target, cell.Vertex(i), cell.Vertex(i + 1), ref min_distance); } return(min_distance); }
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 bool Contains(S2Cell cell) { for (int i = 0; i < Regions.Length; ++i) { if (!Region(i).Contains(cell)) { return(false); } } return(true); }
private static void CompareS2CellToPadded(S2Cell cell, S2PaddedCell pcell, double padding) { Assert.Equal(cell.Id, pcell.Id); Assert.Equal(cell.Level, pcell.Level); Assert.Equal(padding, pcell.Padding); Assert.Equal(cell.BoundUV.Expanded(padding), pcell.Bound); var center_uv = cell.Id.CenterUV(); Assert.Equal(R2Rect.FromPoint(center_uv).Expanded(padding), pcell.Middle); Assert.Equal(cell.Center(), pcell.GetCenter()); }
public Cell(ulong cellId) { var s2cell = new S2Cell(new S2CellId(cellId)); var center = s2cell.RectBound.Center; Id = cellId; Latitude = center.LatDegrees; Longitude = center.LngDegrees; Level = 15; Updated = DateTime.UtcNow.ToTotalSeconds(); }
public bool MayIntersect(S2Cell cell) { for (int i = 0; i < Regions.Length; ++i) { if (!Region(i).MayIntersect(cell)) { return(false); } } return(true); }
private static void GatherStats(S2Cell cell) { var s = level_stats[cell.Level]; double exact_area = cell.ExactArea(); double approx_area = cell.ApproxArea(); double min_edge = 100, max_edge = 0, avg_edge = 0; double min_diag = 100, max_diag = 0; double min_width = 100, max_width = 0; double min_angle_span = 100, max_angle_span = 0; for (int i = 0; i < 4; ++i) { double edge = cell.VertexRaw(i).Angle(cell.VertexRaw(i + 1)); min_edge = Math.Min(edge, min_edge); max_edge = Math.Max(edge, max_edge); avg_edge += 0.25 * edge; S2Point mid = cell.VertexRaw(i) + cell.VertexRaw(i + 1); double width = S2.M_PI_2 - mid.Angle(cell.EdgeRaw(i + 2)); min_width = Math.Min(width, min_width); max_width = Math.Max(width, max_width); if (i < 2) { double diag = cell.VertexRaw(i).Angle(cell.VertexRaw(i + 2)); min_diag = Math.Min(diag, min_diag); max_diag = Math.Max(diag, max_diag); double angle_span = cell.EdgeRaw(i).Angle(-cell.EdgeRaw(i + 2)); min_angle_span = Math.Min(angle_span, min_angle_span); max_angle_span = Math.Max(angle_span, max_angle_span); } } s.Count += 1; s.Min_area = Math.Min(exact_area, s.Min_area); s.Max_area = Math.Max(exact_area, s.Max_area); s.Avg_area += exact_area; s.Min_width = Math.Min(min_width, s.Min_width); s.Max_width = Math.Max(max_width, s.Max_width); s.Avg_width += 0.5 * (min_width + max_width); s.Min_edge = Math.Min(min_edge, s.Min_edge); s.Max_edge = Math.Max(max_edge, s.Max_edge); s.Avg_edge += avg_edge; s.Max_edge_aspect = Math.Max(max_edge / min_edge, s.Max_edge_aspect); s.Min_diag = Math.Min(min_diag, s.Min_diag); s.Max_diag = Math.Max(max_diag, s.Max_diag); s.Avg_diag += 0.5 * (min_diag + max_diag); s.Max_diag_aspect = Math.Max(max_diag / min_diag, s.Max_diag_aspect); s.Min_angle_span = Math.Min(min_angle_span, s.Min_angle_span); s.Max_angle_span = Math.Max(max_angle_span, s.Max_angle_span); s.Avg_angle_span += 0.5 * (min_angle_span + max_angle_span); double approx_ratio = approx_area / exact_area; s.Min_approx_ratio = Math.Min(approx_ratio, s.Min_approx_ratio); s.Max_approx_ratio = Math.Max(approx_ratio, s.Max_approx_ratio); }
public void Test_S2CellUnion_Expand() { // This test generates coverings for caps of random sizes, expands // the coverings by a random radius, and then make sure that the new // covering covers the expanded cap. It also makes sure that the // new covering is not too much larger than expected. var coverer = new S2RegionCoverer(); for (var i = 0; i < 1000; ++i) { _logger.WriteLine($"Iteration {i}"); var cap = S2Testing.GetRandomCap( S2Cell.AverageArea(S2.kMaxCellLevel), S2.M_4_PI); // Expand the cap area by a random factor whose log is uniformly // distributed between 0 and log(1e2). var expanded_cap = S2Cap.FromCenterHeight( cap.Center, Math.Min(2.0, Math.Pow(1e2, rnd.RandDouble()) * cap.Height())); var radius = (expanded_cap.Radius - cap.Radius).Radians(); var max_level_diff = rnd.Uniform(8); // Generate a covering for the original cap, and measure the maximum // distance from the cap center to any point in the covering. coverer.Options_.MaxCells = 1 + rnd.Skewed(10); var covering = coverer.GetCovering(cap); S2Testing.CheckCovering(cap, covering, true); var covering_radius = GetRadius(covering, cap.Center); // This code duplicates the logic in Expand(min_radius, max_level_diff) // that figures out an appropriate cell level to use for the expansion. int min_level = S2.kMaxCellLevel; foreach (var id in covering) { min_level = Math.Min(min_level, id.Level()); } var expand_level = Math.Min(min_level + max_level_diff, S2.kMinWidth.GetLevelForMinValue(radius)); // Generate a covering for the expanded cap, and measure the new maximum // distance from the cap center to any point in the covering. covering.Expand(S1Angle.FromRadians(radius), max_level_diff); S2Testing.CheckCovering(expanded_cap, covering, false); double expanded_covering_radius = GetRadius(covering, cap.Center); // If the covering includes a tiny cell along the boundary, in theory the // maximum angle of the covering from the cap center can increase by up to // twice the maximum length of a cell diagonal. Assert.True(expanded_covering_radius - covering_radius <= 2 * S2.kMaxDiag.GetValue(expand_level)); } }
public void testContains() { assertTrue(candyCane.Contains(S2LatLng.FromDegrees(5, 71).ToPoint())); for (var i = 0; i < 4; ++i) { assertTrue(northHemi.Contains(new S2Point(0, 0, 1))); assertTrue(!northHemi.Contains(new S2Point(0, 0, -1))); assertTrue(!southHemi.Contains(new S2Point(0, 0, 1))); assertTrue(southHemi.Contains(new S2Point(0, 0, -1))); assertTrue(!westHemi.Contains(new S2Point(0, 1, 0))); assertTrue(westHemi.Contains(new S2Point(0, -1, 0))); assertTrue(eastHemi.Contains(new S2Point(0, 1, 0))); assertTrue(!eastHemi.Contains(new S2Point(0, -1, 0))); northHemi = rotate(northHemi); southHemi = rotate(southHemi); eastHemi = rotate(eastHemi); westHemi = rotate(westHemi); } // This code checks each cell vertex is contained by exactly one of // the adjacent cells. for (var level = 0; level < 3; ++level) { var loops = new List <S2Loop>(); var loopVertices = new List <S2Point>(); ISet <S2Point> points = new HashSet <S2Point>(); for (var id = S2CellId.Begin(level); !id.Equals(S2CellId.End(level)); id = id.Next) { var cell = new S2Cell(id); points.Add(cell.Center); for (var k = 0; k < 4; ++k) { loopVertices.Add(cell.GetVertex(k)); points.Add(cell.GetVertex(k)); } loops.Add(new S2Loop(loopVertices)); loopVertices.Clear(); } foreach (var point in points) { var count = 0; for (var j = 0; j < loops.Count; ++j) { if (loops[j].Contains(point)) { ++count; } } assertEquals(count, 1); } } }
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 Test_S2ShapeIndexRegion_GetRectBound() { var id = S2CellId.FromDebugString("3/0123012301230123012301230123"); // Add a polygon that is slightly smaller than the cell being tested. MutableS2ShapeIndex index = new(); index.Add(NewPaddedCell(id, -kPadding)); S2LatLngRect cell_bound = new S2Cell(id).GetRectBound(); S2LatLngRect index_bound = index.MakeS2ShapeIndexRegion().GetRectBound(); Assert.Equal(index_bound, cell_bound); }
public void expandChildren1(S2Cell cell) { var children = new S2Cell[4]; Assert.True(cell.Subdivide(children)); if (children[0].Level < MAX_LEVEL) { for (var pos = 0; pos < 4; ++pos) { expandChildren1(children[pos]); } } }
public void expandChildren2(S2Cell cell) { var id = cell.Id.ChildBegin; for (var pos = 0; pos < 4; ++pos, id = id.Next) { var child = new S2Cell(id); if (child.Level < MAX_LEVEL) { expandChildren2(child); } } }
public void Test_S2RegionUnionTest_Basic() { S2RegionUnion ru_empty = new(new List <IS2Region>()); Assert.Equal(0, ru_empty.Count()); Assert.Equal(S2Cap.Empty, ru_empty.GetCapBound()); Assert.Equal(S2LatLngRect.Empty, ru_empty.GetRectBound()); var empty_clone = (S2RegionUnion)ru_empty.CustomClone(); var two_point_region = new List <IS2Region> { new S2PointRegion(S2LatLng.FromDegrees(35, 40).ToPoint()), new S2PointRegion(S2LatLng.FromDegrees(-35, -40).ToPoint()) }; var two_points_orig = new S2RegionUnion(two_point_region); // two_point_region is in a valid, but unspecified, state. // Check that Clone() returns a deep copy. var two_points = (S2RegionUnion)two_points_orig.CustomClone(); // The bounds below may not be exactly equal because the S2PointRegion // version converts each S2LatLng value to an S2Point and back. Assert.True(MakeLatLngRectOrDie("-35:-40,35:40") !.Value .ApproxEquals(two_points.GetRectBound())); S2Cell face0 = S2Cell.FromFace(0); Assert.True(two_points.MayIntersect(face0)); Assert.False(two_points.Contains(face0)); Assert.True(two_points.Contains(S2LatLng.FromDegrees(35, 40).ToPoint())); Assert.True(two_points.Contains(S2LatLng.FromDegrees(-35, -40).ToPoint())); Assert.False(two_points.Contains(S2LatLng.FromDegrees(0, 0).ToPoint())); // Check that we can Add() another region. var three_points = (S2RegionUnion)two_points.CustomClone(); Assert.False(three_points.Contains(S2LatLng.FromDegrees(10, 10).ToPoint())); three_points.Add(new S2RegionUnion(new List <IS2Region> { new S2PointRegion(S2LatLng.FromDegrees(10, 10).ToPoint()) })); Assert.True(three_points.Contains(S2LatLng.FromDegrees(10, 10).ToPoint())); var coverer = new S2RegionCoverer(); coverer.Options_.MaxCells = 1; coverer.GetCovering(two_points, out var covering); Assert.Single(covering); Assert.Equal(face0.Id, covering[0]); }
public static void DrawS2Cells(List<ulong> cellsIds, GMapOverlay mapLayer) { for (int i=0; i<cellsIds.Count; i++) { S2CellId cellId = new S2CellId(cellsIds[i]); S2Cell cell = new S2Cell(cellId); List<PointLatLng> points = new List<PointLatLng>(); for (int j=0; j<4; j++) { S2LatLng point = new S2LatLng(cell.GetVertex(j)); points.Add(new PointLatLng(point.LatDegrees, point.LngDegrees)); } GMapPolygon polygon = new GMapPolygon(points, "mypolygon"); polygon.Fill = new SolidBrush(Color.FromArgb(50, Color.Red)); polygon.Stroke = new Pen(Color.Red, 1); mapLayer.Polygons.Add(polygon); } }
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); }
private S2Loop makeCellLoop(S2CellId begin, S2CellId end) { // Construct a CCW polygon whose boundary is the union of the cell ids // in the range [begin, end). We Add the edges one by one, removing // any edges that are already present in the opposite direction. IDictionary<S2Point, ISet<S2Point>> edges = new Dictionary<S2Point, ISet<S2Point>>(); for (var id = begin; !id.Equals(end); id = id.Next) { var cell = new S2Cell(id); for (var k = 0; k < 4; ++k) { var a = cell.GetVertex(k); var b = cell.GetVertex((k + 1) & 3); if (!edges.ContainsKey(b)) { edges.Add(b, new HashSet<S2Point>()); } // if a is in b's set, remove it and remove b's set if it's empty // otherwise, Add b to a's set if (!edges[b].Remove(a)) { if (!edges.ContainsKey(a)) { edges.Add(a, new HashSet<S2Point>()); } edges[a].Add(b); } else if (edges[b].Count == 0) { edges.Remove(b); } } } // The remaining edges form a single loop. We simply follow it starting // at an arbitrary vertex and build up a list of vertices. var vertices = new List<S2Point>(); //S2Point p = edges.keySet().iterator().next(); var p = edges.Keys.First(); while (edges.Any()) { assertEquals(1, edges[p].Count); var next = edges[p].First(); //S2Point next = edges[p].iterator().next(); vertices.Add(p); edges.Remove(p); p = next; } return new S2Loop(vertices); }
public void testContains() { assertTrue(candyCane.Contains(S2LatLng.FromDegrees(5, 71).ToPoint())); for (var i = 0; i < 4; ++i) { assertTrue(northHemi.Contains(new S2Point(0, 0, 1))); assertTrue(!northHemi.Contains(new S2Point(0, 0, -1))); assertTrue(!southHemi.Contains(new S2Point(0, 0, 1))); assertTrue(southHemi.Contains(new S2Point(0, 0, -1))); assertTrue(!westHemi.Contains(new S2Point(0, 1, 0))); assertTrue(westHemi.Contains(new S2Point(0, -1, 0))); assertTrue(eastHemi.Contains(new S2Point(0, 1, 0))); assertTrue(!eastHemi.Contains(new S2Point(0, -1, 0))); northHemi = rotate(northHemi); southHemi = rotate(southHemi); eastHemi = rotate(eastHemi); westHemi = rotate(westHemi); } // This code checks each cell vertex is contained by exactly one of // the adjacent cells. for (var level = 0; level < 3; ++level) { var loops = new List<S2Loop>(); var loopVertices = new List<S2Point>(); ISet<S2Point> points = new HashSet<S2Point>(); for (var id = S2CellId.Begin(level); !id.Equals(S2CellId.End(level)); id = id.Next) { var cell = new S2Cell(id); points.Add(cell.Center); for (var k = 0; k < 4; ++k) { loopVertices.Add(cell.GetVertex(k)); points.Add(cell.GetVertex(k)); } loops.Add(new S2Loop(loopVertices)); loopVertices.Clear(); } foreach (var point in points) { var count = 0; for (var j = 0; j < loops.Count; ++j) { if (loops[j].Contains(point)) { ++count; } } assertEquals(count, 1); } } }
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 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 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. var kFaceRadius = Math.Atan(S2.Sqrt2); for (var face = 0; face < 6; ++face) { // The cell consisting of the entire face. var rootCell = S2Cell.FromFacePosLevel(face, (byte)0, 0); // A leaf cell at the midpoint of the v=1 edge. var edgeCell = new S2Cell(S2Projections.FaceUvToXyz(face, 0, 1 - EPS)); // A leaf cell at the u=1, v=1 corner. var cornerCell = new S2Cell(S2Projections.FaceUvToXyz(face, 1 - EPS, 1 - EPS)); // Quick check for full and empty caps. Assert.True(S2Cap.Full.Contains(rootCell)); Assert.True(!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. var first = cornerCell.Id.Previous.Previous.Previous; var last = cornerCell.Id.Next.Next.Next.Next; for (var id = first; id <last; id = id.Next) { var cell = new S2Cell(id); JavaAssert.Equal(cell.CapBound.Contains(cornerCell), id.Equals(cornerCell.Id)); JavaAssert.Equal( cell.CapBound.MayIntersect(cornerCell), id.Parent.Contains(cornerCell.Id)); } var antiFace = (face + 3)%6; // Opposite face. for (var capFace = 0; capFace < 6; ++capFace) { // A cap that barely contains all of 'cap_face'. var center = S2Projections.GetNorm(capFace); var covering = S2Cap.FromAxisAngle(center, S1Angle.FromRadians(kFaceRadius + EPS)); JavaAssert.Equal(covering.Contains(rootCell), capFace == face); JavaAssert.Equal(covering.MayIntersect(rootCell), capFace != antiFace); JavaAssert.Equal(covering.Contains(edgeCell), center.DotProd(edgeCell.Center) > 0.1); JavaAssert.Equal(covering.Contains(edgeCell), covering.MayIntersect(edgeCell)); JavaAssert.Equal(covering.Contains(cornerCell), capFace == face); JavaAssert.Equal( covering.MayIntersect(cornerCell), center.DotProd(cornerCell.Center) > 0); // A cap that barely intersects the edges of 'cap_face'. var bulging = S2Cap.FromAxisAngle(center, S1Angle.FromRadians(S2.PiOver4 + EPS)); Assert.True(!bulging.Contains(rootCell)); JavaAssert.Equal(bulging.MayIntersect(rootCell), capFace != antiFace); JavaAssert.Equal(bulging.Contains(edgeCell), capFace == face); JavaAssert.Equal(bulging.MayIntersect(edgeCell), center.DotProd(edgeCell.Center) > 0.1); Assert.True(!bulging.Contains(cornerCell)); Assert.True(!bulging.MayIntersect(cornerCell)); // A singleton cap. var singleton = S2Cap.FromAxisAngle(center, S1Angle.FromRadians(0)); JavaAssert.Equal(singleton.MayIntersect(rootCell), capFace == face); Assert.True(!singleton.MayIntersect(edgeCell)); Assert.True(!singleton.MayIntersect(cornerCell)); } } }