/** * Given a point X and an edge AB, return the distance ratio AX / (AX + BX). * If X happens to be on the line segment AB, this is the fraction "t" such * that X == Interpolate(A, B, t). Requires that A and B are distinct. */ public static double GetDistanceFraction(S2Point x, S2Point a0, S2Point a1) { Preconditions.CheckArgument(!a0.Equals(a1)); var d0 = x.Angle(a0); var d1 = x.Angle(a1); return(d0 / (d0 + d1)); }
/** Return an empty cap, i.e. a cap that contains no points. */ /** * Return true if and only if this cap contains the given other cap (in a set * containment sense, e.g. every cap contains the empty cap). */ public bool Contains(S2Cap other) { if (IsFull || other.IsEmpty) { return(true); } return(Angle.Radians >= _axis.Angle(other._axis) + other.Angle.Radians); }
/** * Return the set the unmarked points whose distance to "center" is less * than search_radius_, and mark these points. By construction, these points * will be contained by one of the vertex neighbors of "center". */ public void Query(S2Point center, System.Collections.Generic.ICollection <S2Point> output) { output.Clear(); var neighbors = new List <S2CellId>(); S2CellId.FromPoint(center).GetVertexNeighbors(_level, neighbors); foreach (var id in neighbors) { // Iterate over the points contained by each vertex neighbor. foreach (var mp in _delegate[id]) { if (mp.IsMarked) { continue; } var p = mp.Point; if (center.Angle(p) <= _searchRadius) { output.Add(p); mp.Mark(); } } } }
/// <summary> /// Return the angle between two points, which is also equal to the distance /// between these points on the unit sphere. The points do not need to be /// normalized. /// </summary> /// <param name="x"></param> /// <param name="y"></param> public S1Angle(S2Point x, S2Point y) { _radians = x.Angle(y); }
/** * Computes a cell covering of an edge. Clears edgeCovering and returns the * level of the s2 cells used in the covering (only one level is ever used for * each call). * * If thickenEdge is true, the edge is thickened and extended by 1% of its * length. * * It is guaranteed that no child of a covering cell will fully contain the * covered edge. */ private int GetCovering( S2Point a, S2Point b, bool thickenEdge, List <S2CellId> edgeCovering) { edgeCovering.Clear(); // Selects the ideal s2 level at which to cover the edge, this will be the // level whose S2 cells have a width roughly commensurate to the length of // the edge. We multiply the edge length by 2*THICKENING to guarantee the // thickening is honored (it's not a big deal if we honor it when we don't // request it) when doing the covering-by-cap trick. var edgeLength = a.Angle(b); var idealLevel = S2Projections.MinWidth.GetMaxLevel(edgeLength * (1 + 2 * Thickening)); S2CellId containingCellId; if (!thickenEdge) { containingCellId = ContainingCell(a, b); } else { if (idealLevel == S2CellId.MaxLevel) { // If the edge is tiny, instabilities are more likely, so we // want to limit the number of operations. // We pretend we are in a cell much larger so as to trigger the // 'needs covering' case, so we won't try to thicken the edge. containingCellId = (new S2CellId(0xFFF0)).ParentForLevel(3); } else { var pq = (b - a) * Thickening; var ortho = (S2Point.Normalize(S2Point.CrossProd(pq, a))) * edgeLength * Thickening; var p = a - pq; var q = b + pq; // If p and q were antipodal, the edge wouldn't be lengthened, // and it could even flip! This is not a problem because // idealLevel != 0 here. The farther p and q can be is roughly // a quarter Earth away from each other, so we remain // Theta(THICKENING). containingCellId = ContainingCell(p - ortho, p + ortho, q - ortho, q + ortho); } } // Best case: edge is fully contained in a cell that's not too big. if (!containingCellId.Equals(S2CellId.Sentinel) && containingCellId.Level >= idealLevel - 2) { edgeCovering.Add(containingCellId); return(containingCellId.Level); } if (idealLevel == 0) { // Edge is very long, maybe even longer than a face width, so the // trick below doesn't work. For now, we will add the whole S2 sphere. // TODO(user): Do something a tad smarter (and beware of the // antipodal case). for (var cellid = S2CellId.Begin(0); !cellid.Equals(S2CellId.End(0)); cellid = cellid.Next) { edgeCovering.Add(cellid); } return(0); } // TODO(user): Check trick below works even when vertex is at // interface // between three faces. // Use trick as in S2PolygonBuilder.PointIndex.findNearbyPoint: // Cover the edge by a cap centered at the edge midpoint, then cover // the cap by four big-enough cells around the cell vertex closest to the // cap center. var middle = S2Point.Normalize((a + b) / 2); var actualLevel = Math.Min(idealLevel, S2CellId.MaxLevel - 1); S2CellId.FromPoint(middle).GetVertexNeighbors(actualLevel, edgeCovering); return(actualLevel); }
/** * Return true if two points are within the given distance of each other * (mainly useful for testing). */ public static bool ApproxEquals(S2Point a, S2Point b, double maxError) { return(a.Angle(b) <= maxError); }
/** * Return the area of triangle ABC. The method used is about twice as * expensive as Girard's formula, but it is numerically stable for both large * and very small triangles. The points do not need to be normalized. The area * is always positive. * * The triangle area is undefined if it contains two antipodal points, and * becomes numerically unstable as the length of any edge approaches 180 * degrees. */ internal static double Area(S2Point a, S2Point b, S2Point c) { // This method is based on l'Huilier's theorem, // // tan(E/4) = sqrt(tan(s/2) tan((s-a)/2) tan((s-b)/2) tan((s-c)/2)) // // where E is the spherical excess of the triangle (i.e. its area), // a, b, c, are the side lengths, and // s is the semiperimeter (a + b + c) / 2 . // // The only significant source of error using l'Huilier's method is the // cancellation error of the terms (s-a), (s-b), (s-c). This leads to a // *relative* error of about 1e-16 * s / Min(s-a, s-b, s-c). This compares // to a relative error of about 1e-15 / E using Girard's formula, where E is // the true area of the triangle. Girard's formula can be even worse than // this for very small triangles, e.g. a triangle with a true area of 1e-30 // might evaluate to 1e-5. // // So, we prefer l'Huilier's formula unless dmin < s * (0.1 * E), where // dmin = Min(s-a, s-b, s-c). This basically includes all triangles // except for extremely long and skinny ones. // // Since we don't know E, we would like a conservative upper bound on // the triangle area in terms of s and dmin. It's possible to show that // E <= k1 * s * sqrt(s * dmin), where k1 = 2*sqrt(3)/Pi (about 1). // Using this, it's easy to show that we should always use l'Huilier's // method if dmin >= k2 * s^5, where k2 is about 1e-2. Furthermore, // if dmin < k2 * s^5, the triangle area is at most k3 * s^4, where // k3 is about 0.1. Since the best case error using Girard's formula // is about 1e-15, this means that we shouldn't even consider it unless // s >= 3e-4 or so. // We use volatile doubles to force the compiler to truncate all of these // quantities to 64 bits. Otherwise it may compute a value of dmin > 0 // simply because it chose to spill one of the intermediate values to // memory but not one of the others. var sa = b.Angle(c); var sb = c.Angle(a); var sc = a.Angle(b); var s = 0.5 * (sa + sb + sc); if (s >= 3e-4) { // Consider whether Girard's formula might be more accurate. var s2 = s * s; var dmin = s - Math.Max(sa, Math.Max(sb, sc)); if (dmin < 1e-2 * s * s2 * s2) { // This triangle is skinny enough to consider Girard's formula. var area = GirardArea(a, b, c); if (dmin < s * (0.1 * area)) { return(area); } } } // Use l'Huilier's formula. return(4 * Math.Atan( Math.Sqrt( Math.Max(0.0, Math.Tan(0.5 * s) * Math.Tan(0.5 * (s - sa)) * Math.Tan(0.5 * (s - sb)) * Math.Tan(0.5 * (s - sc)))))); }