Пример #1
0
        /**
         * Return a latitude-longitude rectangle that contains the edge from "a" to
         * "b". Both points must be unit-length. Note that the bounding rectangle of
         * an edge can be larger than the bounding rectangle of its endpoints.
         */

        public static S2LatLngRect FromEdge(S2Point a, S2Point b)
        {
            // assert (S2.isUnitLength(a) && S2.isUnitLength(b));
            var r = FromPointPair(new S2LatLng(a), new S2LatLng(b));

            // Check whether the min/max latitude occurs in the edge interior.
            // We find the normal to the plane containing AB, and then a vector "dir" in
            // this plane that also passes through the equator. We use RobustCrossProd
            // to ensure that the edge normal is accurate even when the two points are
            // very close together.
            var ab  = S2.RobustCrossProd(a, b);
            var dir = S2Point.CrossProd(ab, new S2Point(0, 0, 1));
            var da  = dir.DotProd(a);
            var db  = dir.DotProd(b);

            if (da * db >= 0)
            {
                // Minimum and maximum latitude are attained at the vertices.
                return(r);
            }
            // Minimum/maximum latitude occurs in the edge interior. This affects the
            // latitude bounds but not the longitude bounds.
            var absLat = Math.Acos(Math.Abs(ab.Z / ab.Norm));

            if (da < 0)
            {
                return(new S2LatLngRect(new R1Interval(r.Lat.Lo, absLat), r.Lng));
            }
            else
            {
                return(new S2LatLngRect(new R1Interval(-absLat, r.Lat.Hi), r.Lng));
            }
        }
Пример #2
0
        /**
         * A slightly more efficient version of getDistance() where the cross product
         * of the two endpoints has been precomputed. The cross product does not need
         * to be normalized, but should be computed using S2.robustCrossProd() for the
         * most accurate results.
         */

        public static S1Angle GetDistance(S2Point x, S2Point a, S2Point b, S2Point aCrossB)
        {
            Preconditions.CheckArgument(S2.IsUnitLength(x));
            Preconditions.CheckArgument(S2.IsUnitLength(a));
            Preconditions.CheckArgument(S2.IsUnitLength(b));

            // There are three cases. If X is located in the spherical wedge defined by
            // A, B, and the axis A x B, then the closest point is on the segment AB.
            // Otherwise the closest point is either A or B; the dividing line between
            // these two cases is the great circle passing through (A x B) and the
            // midpoint of AB.

            if (S2.SimpleCcw(aCrossB, a, x) && S2.SimpleCcw(x, b, aCrossB))
            {
                // The closest point to X lies on the segment AB. We compute the distance
                // to the corresponding great circle. The result is accurate for small
                // distances but not necessarily for large distances (approaching Pi/2).

                var sinDist = Math.Abs(x.DotProd(aCrossB)) / aCrossB.Norm;
                return(S1Angle.FromRadians(Math.Asin(Math.Min(1.0, sinDist))));
            }

            // Otherwise, the closest point is either A or B. The cheapest method is
            // just to compute the minimum of the two linear (as opposed to spherical)
            // distances and convert the result to an angle. Again, this method is
            // accurate for small but not large distances (approaching Pi).

            var linearDist2 = Math.Min((x - a).Norm2, (x - b).Norm2);

            return(S1Angle.FromRadians(2 * Math.Asin(Math.Min(1.0, 0.5 * Math.Sqrt(linearDist2)))));
        }
        /**
         * Return true if the given vertices form a valid polyline.
         */

        public bool IsValidPolyline(IReadOnlyList <S2Point> vertices)
        {
            // All vertices must be unit length.
            var n = vertices.Count;

            for (var i = 0; i < n; ++i)
            {
                if (!S2.IsUnitLength(vertices[i]))
                {
                    Debug.WriteLine("Vertex " + i + " is not unit length");
                    return(false);
                }
            }

            // Adjacent vertices must not be identical or antipodal.
            for (var i = 1; i < n; ++i)
            {
                if (vertices[i - 1].Equals(vertices[i]) ||
                    vertices[i - 1].Equals(-vertices[i]))
                {
                    Debug.WriteLine("Vertices " + (i - 1) + " and " + i + " are identical or antipodal");
                    return(false);
                }
            }

            return(true);
        }
Пример #4
0
        /**
         * Return the inward-facing normal of the great circle passing through the
         * edge from vertex k to vertex k+1 (mod 4). The normals returned by
         * GetEdgeRaw are not necessarily unit length.
         *
         *  If this is not a leaf cell, set children[0..3] to the four children of
         * this cell (in traversal order) and return true. Otherwise returns false.
         * This method is equivalent to the following:
         *
         *  for (pos=0, id=child_begin(); id != child_end(); id = id.next(), ++pos)
         * children[i] = S2Cell(id);
         *
         * except that it is more than two times faster.
         */

        public bool Subdivide(IReadOnlyList <S2Cell> children)
        {
            // This function is equivalent to just iterating over the child cell ids
            // and calling the S2Cell constructor, but it is about 2.5 times faster.

            if (_cellId.IsLeaf)
            {
                return(false);
            }

            // Compute the cell midpoint in uv-space.
            var uvMid = CenterUv;

            // Create four children with the appropriate bounds.
            var id = _cellId.ChildBegin;

            for (var pos = 0; pos < 4; ++pos, id = id.Next)
            {
                var child = children[pos];
                child._face        = _face;
                child._level       = (byte)(_level + 1);
                child._orientation = (byte)(_orientation ^ S2.PosToOrientation(pos));
                child._cellId      = id;
                var ij = S2.PosToIj(_orientation, pos);
                for (var d = 0; d < 2; ++d)
                {
                    // The dimension 0 index (i/u) is in bit 1 of ij.
                    var m = 1 - ((ij >> (1 - d)) & 1);
                    child._uv[d][m]     = uvMid[d];
                    child._uv[d][1 - m] = _uv[d][1 - m];
                }
            }
            return(true);
        }
Пример #5
0
        private void InitOrigin()
        {
            // The bounding box does not need to be correct before calling this
            // function, but it must at least contain vertex(1) since we need to
            // do a Contains() test on this point below.
            Preconditions.CheckState(_bound.Contains(Vertex(1)));

            // To ensure that every point is contained in exactly one face of a
            // subdivision of the sphere, all containment tests are done by counting the
            // edge crossings starting at a fixed point on the sphere (S2::Origin()).
            // We need to know whether this point is inside or outside of the loop.
            // We do this by first guessing that it is outside, and then seeing whether
            // we get the correct containment result for vertex 1. If the result is
            // incorrect, the origin must be inside the loop.
            //
            // A loop with consecutive vertices A,B,C contains vertex B if and only if
            // the fixed vector R = S2::Ortho(B) is on the left side of the wedge ABC.
            // The test below is written so that B is inside if C=R but not if A=R.

            _originInside = false; // Initialize before calling Contains().
            var v1Inside = S2.OrderedCcw(S2.Ortho(Vertex(1)), Vertex(0), Vertex(2), Vertex(1));

            if (v1Inside != Contains(Vertex(1)))
            {
                _originInside = true;
            }
        }
Пример #6
0
        /**
         * Given two edge chains (see WedgeRelation above), this function returns +1
         * if the region to the left of A contains the region to the left of B, and
         * 0 otherwise.
         */

        public int Test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2)
        {
            // For A to contain B (where each loop interior is defined to be its left
            // side), the CCW edge order around ab1 must be a2 b2 b0 a0. We split
            // this test into two parts that test three vertices each.
            return(S2.OrderedCcw(a2, b2, b0, ab1) && S2.OrderedCcw(b0, a0, a2, ab1) ? 1 : 0);
        }
Пример #7
0
        /**
         * Given two edges AB and CD where at least two vertices are identical (i.e.
         * robustCrossing(a,b,c,d) == 0), this function defines whether the two edges
         * "cross" in a such a way that point-in-polygon containment tests can be
         * implemented by counting the number of edge crossings. The basic rule is
         * that a "crossing" occurs if AB is encountered after CD during a CCW sweep
         * around the shared vertex starting from a fixed reference point.
         *
         *  Note that according to this rule, if AB crosses CD then in general CD does
         * not cross AB. However, this leads to the correct result when counting
         * polygon edge crossings. For example, suppose that A,B,C are three
         * consecutive vertices of a CCW polygon. If we now consider the edge
         * crossings of a segment BP as P sweeps around B, the crossing number changes
         * parity exactly when BP crosses BA or BC.
         *
         *  Useful properties of VertexCrossing (VC):
         *
         *  (1) VC(a,a,c,d) == VC(a,b,c,c) == false (2) VC(a,b,a,b) == VC(a,b,b,a) ==
         * true (3) VC(a,b,c,d) == VC(a,b,d,c) == VC(b,a,c,d) == VC(b,a,d,c) (3) If
         * exactly one of a,b Equals one of c,d, then exactly one of VC(a,b,c,d) and
         * VC(c,d,a,b) is true
         *
         * It is an error to call this method with 4 distinct vertices.
         */

        public static bool VertexCrossing(S2Point a, S2Point b, S2Point c, S2Point d)
        {
            // If A == B or C == D there is no intersection. We need to check this
            // case first in case 3 or more input points are identical.
            if (a.Equals(b) || c.Equals(d))
            {
                return(false);
            }

            // If any other pair of vertices is equal, there is a crossing if and only
            // if orderedCCW() indicates that the edge AB is further CCW around the
            // shared vertex than the edge CD.
            if (a.Equals(d))
            {
                return(S2.OrderedCcw(S2.Ortho(a), c, b, a));
            }
            if (b.Equals(c))
            {
                return(S2.OrderedCcw(S2.Ortho(b), d, a, b));
            }
            if (a.Equals(c))
            {
                return(S2.OrderedCcw(S2.Ortho(a), d, b, a));
            }
            if (b.Equals(d))
            {
                return(S2.OrderedCcw(S2.Ortho(b), c, a, b));
            }

            // assert (false);
            return(false);
        }
Пример #8
0
        /**
         * Return the area of this cell as accurately as possible. This method is more
         * expensive but it is accurate to 6 digits of precision even for leaf cells
         * (whose area is approximately 1e-18).
         */

        public double ExactArea()
        {
            var v0 = GetVertex(0);
            var v1 = GetVertex(1);
            var v2 = GetVertex(2);
            var v3 = GetVertex(3);

            return(S2.Area(v0, v1, v2) + S2.Area(v0, v2, v3));
        }
Пример #9
0
        /**
         * Given two edge chains (see WedgeRelation above), this function returns -1
         * if the region to the left of A intersects the region to the left of B,
         * and 0 otherwise. Note that regions are defined such that points along a
         * boundary are contained by one side or the other, not both. So for
         * example, if A,B,C are distinct points ordered CCW around a vertex O, then
         * the wedges BOA, AOC, and COB do not intersect.
         */

        public int Test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2)
        {
            // For A not to intersect B (where each loop interior is defined to be
            // its left side), the CCW edge order around ab1 must be a0 b2 b0 a2.
            // Note that it's important to write these conditions as negatives
            // (!OrderedCCW(a,b,c,o) rather than Ordered(c,b,a,o)) to get correct
            // results when two vertices are the same.
            return(S2.OrderedCcw(a0, b2, b0, ab1) && S2.OrderedCcw(b0, a2, a0, ab1) ? 0 : -1);
        }
Пример #10
0
        /*
         * Given two edges AB and CD such that robustCrossing() is true, return their
         * intersection point. Useful properties of getIntersection (GI):
         *
         * (1) GI(b,a,c,d) == GI(a,b,d,c) == GI(a,b,c,d) (2) GI(c,d,a,b) ==
         * GI(a,b,c,d)
         *
         * The returned intersection point X is guaranteed to be close to the edges AB
         * and CD, but if the edges intersect at a very small angle then X may not be
         * close to the true mathematical intersection point P. See the description of
         * "DEFAULT_INTERSECTION_TOLERANCE" below for details.
         */

        public static S2Point GetIntersection(S2Point a0, S2Point a1, S2Point b0, S2Point b1)
        {
            Preconditions.CheckArgument(RobustCrossing(a0, a1, b0, b1) > 0,
                                        "Input edges a0a1 and b0b1 muct have a true robustCrossing.");

            // We use robustCrossProd() to get accurate results even when two endpoints
            // are close together, or when the two line segments are nearly parallel.
            var aNorm = S2Point.Normalize(S2.RobustCrossProd(a0, a1));
            var bNorm = S2Point.Normalize(S2.RobustCrossProd(b0, b1));
            var x     = S2Point.Normalize(S2.RobustCrossProd(aNorm, bNorm));

            // Make sure the intersection point is on the correct side of the sphere.
            // Since all vertices are unit length, and edges are less than 180 degrees,
            // (a0 + a1) and (b0 + b1) both have positive dot product with the
            // intersection point. We use the sum of all vertices to make sure that the
            // result is unchanged when the edges are reversed or exchanged.
            if (x.DotProd(a0 + a1 + b0 + b1) < 0)
            {
                x = -x;
            }

            // The calculation above is sufficient to ensure that "x" is within
            // DEFAULT_INTERSECTION_TOLERANCE of the great circles through (a0,a1) and
            // (b0,b1).
            // However, if these two great circles are very close to parallel, it is
            // possible that "x" does not lie between the endpoints of the given line
            // segments. In other words, "x" might be on the great circle through
            // (a0,a1) but outside the range covered by (a0,a1). In this case we do
            // additional clipping to ensure that it does.

            if (S2.OrderedCcw(a0, x, a1, aNorm) && S2.OrderedCcw(b0, x, b1, bNorm))
            {
                return(x);
            }

            // Find the acceptable endpoint closest to x and return it. An endpoint is
            // acceptable if it lies between the endpoints of the other line segment.
            var r = new CloserResult(10, x);

            if (S2.OrderedCcw(b0, a0, b1, bNorm))
            {
                r.ReplaceIfCloser(x, a0);
            }
            if (S2.OrderedCcw(b0, a1, b1, bNorm))
            {
                r.ReplaceIfCloser(x, a1);
            }
            if (S2.OrderedCcw(a0, b0, a1, aNorm))
            {
                r.ReplaceIfCloser(x, b0);
            }
            if (S2.OrderedCcw(a0, b1, a1, aNorm))
            {
                r.ReplaceIfCloser(x, b1);
            }
            return(r.Vmin);
        }
Пример #11
0
        /**
         * Return true if the edge AB intersects the given edge of constant longitude.
         */

        private static bool IntersectsLngEdge(S2Point a, S2Point b,
                                              R1Interval lat, double lng)
        {
            // Return true if the segment AB intersects the given edge of constant
            // longitude. The nice thing about edges of constant longitude is that
            // they are straight lines on the sphere (geodesics).

            return(S2.SimpleCrossing(a, b, S2LatLng.FromRadians(lat.Lo, lng)
                                     .ToPoint(), S2LatLng.FromRadians(lat.Hi, lng).ToPoint()));
        }
Пример #12
0
        /**
         * This function handles the "slow path" of robustCrossing().
         */

        private int RobustCrossingInternal(S2Point d)
        {
            // ACB and BDA have the appropriate orientations, so now we check the
            // triangles CBD and DAC.
            var cCrossD = S2Point.CrossProd(c, d);
            var cbd     = -S2.RobustCcw(c, d, b, cCrossD);

            if (cbd != acb)
            {
                return(-1);
            }

            var dac = S2.RobustCcw(c, d, a, cCrossD);

            return((dac == acb) ? 1 : -1);
        }
Пример #13
0
        public void AddPoint(S2Point b)
        {
            // assert (S2.isUnitLength(b));

            var bLatLng = new S2LatLng(b);

            if (bound.IsEmpty)
            {
                bound = bound.AddPoint(bLatLng);
            }
            else
            {
                // We can't just call bound.addPoint(bLatLng) here, since we need to
                // ensure that all the longitudes between "a" and "b" are included.
                bound = bound.Union(S2LatLngRect.FromPointPair(aLatLng, bLatLng));

                // Check whether the Min/Max latitude occurs in the edge interior.
                // We find the normal to the plane containing AB, and then a vector
                // "dir" in this plane that also passes through the equator. We use
                // RobustCrossProd to ensure that the edge normal is accurate even
                // when the two points are very close together.
                var aCrossB = S2.RobustCrossProd(a, b);
                var dir     = S2Point.CrossProd(aCrossB, new S2Point(0, 0, 1));
                var da      = dir.DotProd(a);
                var db      = dir.DotProd(b);

                if (da * db < 0)
                {
                    // Minimum/maximum latitude occurs in the edge interior. This affects
                    // the latitude bounds but not the longitude bounds.
                    var absLat = Math.Acos(Math.Abs(aCrossB[2] / aCrossB.Norm));
                    var lat    = bound.Lat;
                    if (da < 0)
                    {
                        // It's possible that absLat < lat.lo() due to numerical errors.
                        lat = new R1Interval(lat.Lo, Math.Max(absLat, bound.Lat.Hi));
                    }
                    else
                    {
                        lat = new R1Interval(Math.Min(-absLat, bound.Lat.Lo), lat.Hi);
                    }
                    bound = new S2LatLngRect(lat, bound.Lng);
                }
            }
            a       = b;
            aLatLng = bLatLng;
        }
Пример #14
0
        /**
         * Return the maximum level such that the metric is at least the given
         * value, or zero if there is no such level. For example,
         * S2.kMinWidth.GetMaxLevel(0.1) returns the maximum level such that all
         * cells have a minimum width of 0.1 or larger. The return value is always a
         * valid level.
         */

        public int GetMaxLevel(double value)
        {
            if (value <= 0)
            {
                return(S2CellId.MaxLevel);
            }

            // This code is equivalent to computing a floating-point "level"
            // value and rounding down.
            var exponent = S2.Exp((1 << _dim) * _deriv / value);
            var level    = Math.Max(0,
                                    Math.Min(S2CellId.MaxLevel, ((exponent - 1) >> (_dim - 1))));

            // assert (level == 0 || getValue(level) >= value);
            // assert (level == S2CellId.MAX_LEVEL || getValue(level + 1) < value);
            return(level);
        }
Пример #15
0
        /**
         * Returns the point on edge AB closest to X. x, a and b must be of unit
         * length. Throws IllegalArgumentException if this is not the case.
         *
         */

        public static S2Point GetClosestPoint(S2Point x, S2Point a, S2Point b)
        {
            Preconditions.CheckArgument(S2.IsUnitLength(x));
            Preconditions.CheckArgument(S2.IsUnitLength(a));
            Preconditions.CheckArgument(S2.IsUnitLength(b));

            var crossProd = S2.RobustCrossProd(a, b);
            // Find the closest point to X along the great circle through AB.
            var p = x - (crossProd * x.DotProd(crossProd) / crossProd.Norm2);

            // If p is on the edge AB, then it's the closest point.
            if (S2.SimpleCcw(crossProd, a, p) && S2.SimpleCcw(p, b, crossProd))
            {
                return(S2Point.Normalize(p));
            }
            // Otherwise, the closest point is either A or B.
            return((x - a).Norm2 <= (x - b).Norm2 ? a : b);
        }
Пример #16
0
        /**
         * Returns true if two loops have the same boundary except for vertex
         * perturbations. More precisely, the vertices in the two loops must be in the
         * same cyclic order, and corresponding vertex pairs must be separated by no
         * more than maxError. Note: This method mostly useful only for testing
         * purposes.
         */

        internal bool BoundaryApproxEquals(S2Loop b, double maxError)
        {
            if (NumVertices != b.NumVertices)
            {
                return(false);
            }
            var maxVertices = NumVertices;
            var iThis       = _firstLogicalVertex;
            var iOther      = b._firstLogicalVertex;

            for (var i = 0; i < maxVertices; ++i, ++iThis, ++iOther)
            {
                if (!S2.ApproxEquals(Vertex(iThis), b.Vertex(iOther), maxError))
                {
                    return(false);
                }
            }
            return(true);
        }
Пример #17
0
        /**
         * Given two edge chains (see WedgeRelation above), this function returns +1
         * if A contains B, 0 if A and B are disjoint, and -1 if A intersects but
         * does not contain B.
         */

        public int Test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2)
        {
            // This is similar to WedgeContainsOrCrosses, except that we want to
            // distinguish cases (1) [A contains B], (3) [A and B are disjoint],
            // and (2,4,5,6) [A intersects but does not contain B].

            if (S2.OrderedCcw(a0, a2, b2, ab1))
            {
                // We are in case 1, 5, or 6, or case 2 if a2 == b2.
                return(S2.OrderedCcw(b2, b0, a0, ab1) ? 1 : -1); // Case 1 vs. 2,5,6.
            }
            // We are in cases 2, 3, or 4.
            if (!S2.OrderedCcw(a2, b0, b2, ab1))
            {
                return(0); // Case 3.
            }

            // We are in case 2 or 4, or case 3 if a2 == b0.
            return((a2.Equals(b0)) ? 0 : -1); // Case 3 vs. 2,4.
        }
Пример #18
0
        /**
         * Like SimpleCrossing, except that points that lie exactly on a line are
         * arbitrarily classified as being on one side or the other (according to the
         * rules of S2.robustCCW). It returns +1 if there is a crossing, -1 if there
         * is no crossing, and 0 if any two vertices from different edges are the
         * same. Returns 0 or -1 if either edge is degenerate. Properties of
         * robustCrossing:
         *
         *  (1) robustCrossing(b,a,c,d) == robustCrossing(a,b,c,d) (2)
         * robustCrossing(c,d,a,b) == robustCrossing(a,b,c,d) (3)
         * robustCrossing(a,b,c,d) == 0 if a==c, a==d, b==c, b==d (3)
         * robustCrossing(a,b,c,d) <= 0 if a==b or c==d
         *
         *  Note that if you want to check an edge against a *chain* of other edges,
         * it is much more efficient to use an EdgeCrosser (above).
         */

        public static int RobustCrossing(S2Point a, S2Point b, S2Point c, S2Point d)
        {
            // For there to be a crossing, the triangles ACB, CBD, BDA, DAC must
            // all have the same orientation (clockwise or counterclockwise).
            //
            // First we compute the orientation of ACB and BDA. We permute the
            // arguments to robustCCW so that we can reuse the cross-product of A and B.
            // Recall that when the arguments to robustCCW are permuted, the sign of the
            // result changes according to the sign of the permutation. Thus ACB and
            // ABC are oppositely oriented, while BDA and ABD are the same.
            var aCrossB = S2Point.CrossProd(a, b);
            var acb     = -S2.RobustCcw(a, b, c, aCrossB);
            var bda     = S2.RobustCcw(a, b, d, aCrossB);

            // If any two vertices are the same, the result is degenerate.
            if ((bda & acb) == 0)
            {
                return(0);
            }

            // If ABC and BDA have opposite orientations (the most common case),
            // there is no crossing.
            if (bda != acb)
            {
                return(-1);
            }

            // Otherwise we compute the orientations of CBD and DAC, and check whether
            // their orientations are compatible with the other two triangles.
            var cCrossD = S2Point.CrossProd(c, d);
            var cbd     = -S2.RobustCcw(c, d, b, cCrossD);

            if (cbd != acb)
            {
                return(-1);
            }

            var dac = S2.RobustCcw(c, d, a, cCrossD);

            return((dac == acb) ? 1 : -1);
        }
Пример #19
0
        /**
         * Given two edge chains (see WedgeRelation above), this function returns +1
         * if A contains B, 0 if B contains A or the two wedges do not intersect,
         * and -1 if the edge chains A and B cross each other (i.e. if A intersects
         * both the interior and exterior of the region to the left of B). In
         * degenerate cases where more than one of these conditions is satisfied,
         * the maximum possible result is returned. For example, if A == B then the
         * result is +1.
         */

        public int Test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2)
        {
            // There are 6 possible edge orderings at a shared vertex (all
            // of these orderings are circular, i.e. abcd == bcda):
            //
            // (1) a2 b2 b0 a0: A contains B
            // (2) a2 a0 b0 b2: B contains A
            // (3) a2 a0 b2 b0: A and B are disjoint
            // (4) a2 b0 a0 b2: A and B intersect in one wedge
            // (5) a2 b2 a0 b0: A and B intersect in one wedge
            // (6) a2 b0 b2 a0: A and B intersect in two wedges
            //
            // In cases (4-6), the boundaries of A and B cross (i.e. the boundary
            // of A intersects the interior and exterior of B and vice versa).
            // Thus we want to distinguish cases (1), (2-3), and (4-6).
            //
            // Note that the vertices may satisfy more than one of the edge
            // orderings above if two or more vertices are the same. The tests
            // below are written so that we take the most favorable
            // interpretation, i.e. preferring (1) over (2-3) over (4-6). In
            // particular note that if orderedCCW(a,b,c,o) returns true, it may be
            // possible that orderedCCW(c,b,a,o) is also true (if a == b or b == c).

            if (S2.OrderedCcw(a0, a2, b2, ab1))
            {
                // The cases with this vertex ordering are 1, 5, and 6,
                // although case 2 is also possible if a2 == b2.
                if (S2.OrderedCcw(b2, b0, a0, ab1))
                {
                    return(1); // Case 1 (A contains B)
                }

                // We are in case 5 or 6, or case 2 if a2 == b2.
                return((a2.Equals(b2)) ? 0 : -1); // Case 2 vs. 5,6.
            }
            // We are in case 2, 3, or 4.
            return(S2.OrderedCcw(a0, b0, a2, ab1) ? 0 : -1); // Case 2,3 vs. 4.
        }
Пример #20
0
        /**
         * This method is equivalent to calling the S2EdgeUtil.robustCrossing()
         * function (defined below) on the edges AB and CD. It returns +1 if there
         * is a crossing, -1 if there is no crossing, and 0 if two points from
         * different edges are the same. Returns 0 or -1 if either edge is
         * degenerate. As a side effect, it saves vertex D to be used as the next
         * vertex C.
         */

        public int RobustCrossing(S2Point d)
        {
            // For there to be an edge crossing, the triangles ACB, CBD, BDA, DAC must
            // all be oriented the same way (CW or CCW). We keep the orientation
            // of ACB as part of our state. When each new point D arrives, we
            // compute the orientation of BDA and check whether it matches ACB.
            // This checks whether the points C and D are on opposite sides of the
            // great circle through AB.

            // Recall that robustCCW is invariant with respect to rotating its
            // arguments, i.e. ABC has the same orientation as BDA.
            var bda = S2.RobustCcw(a, b, d, aCrossB);
            int result;

            if (bda == -acb && bda != 0)
            {
                // Most common case -- triangles have opposite orientations.
                result = -1;
            }
            else if ((bda & acb) == 0)
            {
                // At least one value is zero -- two vertices are identical.
                result = 0;
            }
            else
            {
                // assert (bda == acb && bda != 0);
                result = RobustCrossingInternal(d); // Slow path.
            }
            // Now save the current vertex D as the next vertex C, and also save the
            // orientation of the new triangle ACB (which is opposite to the current
            // triangle BDA).
            c   = d;
            acb = -bda;
            return(result);
        }
Пример #21
0
 private static void InitLookupCell(int level, int i, int j,
                                    int origOrientation, int pos, int orientation)
 {
     if (level == LookupBits)
     {
         var ij = (i << LookupBits) + j;
         LookupPos[(ij << 2) + origOrientation] = (pos << 2) + orientation;
         LookupIj[(pos << 2) + origOrientation] = (ij << 2) + orientation;
     }
     else
     {
         level++;
         i   <<= 1;
         j   <<= 1;
         pos <<= 2;
         // Initialize each sub-cell recursively.
         for (var subPos = 0; subPos < 4; subPos++)
         {
             var ij = S2.PosToIj(orientation, subPos);
             var orientationMask = S2.PosToOrientation(subPos);
             InitLookupCell(level, i + (ij >> 1), j + (ij & 1), origOrientation, pos + subPos, orientation ^ orientationMask);
         }
     }
 }
        /**
         * We start at the given edge and assemble a loop taking left turns whenever
         * possible. We stop the loop as soon as we encounter any vertex that we have
         * seen before *except* for the first vertex (v0). This ensures that only CCW
         * loops are constructed when possible.
         */

        private S2Loop AssembleLoop(S2Point v0, S2Point v1, System.Collections.Generic.IList <S2Edge> unusedEdges)
        {
            // The path so far.
            var path = new List <S2Point>();

            // Maps a vertex to its index in "path".
            var index = new Dictionary <S2Point, int>();

            path.Add(v0);
            path.Add(v1);

            index.Add(v1, 1);

            while (path.Count >= 2)
            {
                // Note that "v0" and "v1" become invalid if "path" is modified.
                v0 = path[path.Count - 2];
                v1 = path[path.Count - 1];

                var v2      = default(S2Point);
                var v2Found = false;
                HashBag <S2Point> vset;
                _edges.TryGetValue(v1, out vset);
                if (vset != null)
                {
                    foreach (var v in vset)
                    {
                        // We prefer the leftmost outgoing edge, ignoring any reverse edges.
                        if (v.Equals(v0))
                        {
                            continue;
                        }
                        if (!v2Found || S2.OrderedCcw(v0, v2, v, v1))
                        {
                            v2 = v;
                        }
                        v2Found = true;
                    }
                }
                if (!v2Found)
                {
                    // We've hit a dead end. Remove this edge and backtrack.
                    unusedEdges.Add(new S2Edge(v0, v1));
                    EraseEdge(v0, v1);
                    index.Remove(v1);
                    path.RemoveAt(path.Count - 1);
                }
                else if (!index.ContainsKey(v2))
                {
                    // This is the first time we've visited this vertex.
                    index.Add(v2, path.Count);
                    path.Add(v2);
                }
                else
                {
                    // We've completed a loop. Throw away any initial vertices that
                    // are not part of the loop.
                    var start = index[v2];
                    path = path.GetRange(start, path.Count - start);

                    if (_options.Validate && !S2Loop.IsValidLoop(path))
                    {
                        // We've constructed a loop that crosses itself, which can only happen
                        // if there is bad input data. Throw away the whole loop.
                        RejectLoop(path, path.Count, unusedEdges);
                        EraseLoop(path, path.Count);
                        return(null);
                    }
                    return(new S2Loop(path));
                }
            }
            return(null);
        }
Пример #23
0
        /**
         * Call this function when your chain 'jumps' to a new place.
         */

        public void RestartAt(S2Point c)
        {
            this.c = c;
            acb    = -S2.RobustCcw(a, b, c, aCrossB);
        }
Пример #24
0
        /**
         * Helper method to get area and optionally centroid.
         */

        private S2AreaCentroid GetAreaCentroid(bool doCentroid)
        {
            // Don't crash even if loop is not well-defined.
            if (NumVertices < 3)
            {
                return(new S2AreaCentroid(0D));
            }

            // The triangle area calculation becomes numerically unstable as the length
            // of any edge approaches 180 degrees. However, a loop may contain vertices
            // that are 180 degrees apart and still be valid, e.g. a loop that defines
            // the northern hemisphere using four points. We handle this case by using
            // triangles centered around an origin that is slightly displaced from the
            // first vertex. The amount of displacement is enough to get plenty of
            // accuracy for antipodal points, but small enough so that we still get
            // accurate areas for very tiny triangles.
            //
            // Of course, if the loop contains a point that is exactly antipodal from
            // our slightly displaced vertex, the area will still be unstable, but we
            // expect this case to be very unlikely (i.e. a polygon with two vertices on
            // opposite sides of the Earth with one of them displaced by about 2mm in
            // exactly the right direction). Note that the approximate point resolution
            // using the E7 or S2CellId representation is only about 1cm.

            var origin            = Vertex(0);
            var axis              = (origin.LargestAbsComponent + 1) % 3;
            var slightlyDisplaced = origin[axis] + S2.E * 1e-10;

            origin =
                new S2Point((axis == 0) ? slightlyDisplaced : origin.X,
                            (axis == 1) ? slightlyDisplaced : origin.Y, (axis == 2) ? slightlyDisplaced : origin.Z);
            origin = S2Point.Normalize(origin);

            double areaSum     = 0;
            var    centroidSum = new S2Point(0, 0, 0);

            for (var i = 1; i <= NumVertices; ++i)
            {
                areaSum += S2.SignedArea(origin, Vertex(i - 1), Vertex(i));
                if (doCentroid)
                {
                    // The true centroid is already premultiplied by the triangle area.
                    var trueCentroid = S2.TrueCentroid(origin, Vertex(i - 1), Vertex(i));
                    centroidSum = centroidSum + trueCentroid;
                }
            }
            // The calculated area at this point should be between -4*Pi and 4*Pi,
            // although it may be slightly larger or smaller than this due to
            // numerical errors.
            // assert (Math.abs(areaSum) <= 4 * S2.M_PI + 1e-12);

            if (areaSum < 0)
            {
                // If the area is negative, we have computed the area to the right of the
                // loop. The area to the left is 4*Pi - (-area). Amazingly, the centroid
                // does not need to be changed, since it is the negative of the integral
                // of position over the region to the right of the loop. This is the same
                // as the integral of position over the region to the left of the loop,
                // since the integral of position over the entire sphere is (0, 0, 0).
                areaSum += 4 * S2.Pi;
            }
            // The loop's sign() does not affect the return result and should be taken
            // into account by the caller.
            S2Point?centroid = null;

            if (doCentroid)
            {
                centroid = centroidSum;
            }
            return(new S2AreaCentroid(areaSum, centroid));
        }
Пример #25
0
        /**
         * Return the minimum distance from X to any point on the edge AB. The result
         * is very accurate for small distances but may have some numerical error if
         * the distance is large (approximately Pi/2 or greater). The case A == B is
         * handled correctly. Note: x, a and b must be of unit length. Throws
         * IllegalArgumentException if this is not the case.
         */

        public static S1Angle GetDistance(S2Point x, S2Point a, S2Point b)
        {
            return(GetDistance(x, a, b, S2.RobustCrossProd(a, b)));
        }
Пример #26
0
        /**
         * Return true if the edge AB intersects the given edge of constant latitude.
         */

        private static bool IntersectsLatEdge(S2Point a, S2Point b, double lat,
                                              S1Interval lng)
        {
            // Return true if the segment AB intersects the given edge of constant
            // latitude. Unfortunately, lines of constant latitude are curves on
            // the sphere. They can intersect a straight edge in 0, 1, or 2 points.
            // assert (S2.isUnitLength(a) && S2.isUnitLength(b));

            // First, compute the normal to the plane AB that points vaguely north.
            var z = S2Point.Normalize(S2.RobustCrossProd(a, b));

            if (z.Z < 0)
            {
                z = -z;
            }

            // Extend this to an orthonormal frame (x,y,z) where x is the direction
            // where the great circle through AB achieves its maximium latitude.
            var y = S2Point.Normalize(S2.RobustCrossProd(z, new S2Point(0, 0, 1)));
            var x = S2Point.CrossProd(y, z);
            // assert (S2.isUnitLength(x) && x.z >= 0);

            // Compute the angle "theta" from the x-axis (in the x-y plane defined
            // above) where the great circle intersects the given line of latitude.
            var sinLat = Math.Sin(lat);

            if (Math.Abs(sinLat) >= x.Z)
            {
                return(false); // The great circle does not reach the given latitude.
            }
            // assert (x.z > 0);
            var cosTheta = sinLat / x.Z;
            var sinTheta = Math.Sqrt(1 - cosTheta * cosTheta);
            var theta    = Math.Atan2(sinTheta, cosTheta);

            // The candidate intersection points are located +/- theta in the x-y
            // plane. For an intersection to be valid, we need to check that the
            // intersection point is contained in the interior of the edge AB and
            // also that it is contained within the given longitude interval "lng".

            // Compute the range of theta values spanned by the edge AB.
            var abTheta = S1Interval.FromPointPair(Math.Atan2(
                                                       a.DotProd(y), a.DotProd(x)), Math.Atan2(b.DotProd(y), b.DotProd(x)));

            if (abTheta.Contains(theta))
            {
                // Check if the intersection point is also in the given "lng" interval.
                var isect = (x * cosTheta) + (y * sinTheta);
                if (lng.Contains(Math.Atan2(isect.Y, isect.X)))
                {
                    return(true);
                }
            }
            if (abTheta.Contains(-theta))
            {
                // Check if the intersection point is also in the given "lng" interval.
                var intersection = (x * cosTheta) - (y * sinTheta);
                if (lng.Contains(Math.Atan2(intersection.Y, intersection.X)))
                {
                    return(true);
                }
            }
            return(false);
        }