Example #1
0
        /**
         * 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));
        }
Example #2
0
        /** 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))))));
        }