/** * 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)); } }
/** * 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); }
/** * 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); }
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; } }
/** * 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); }
/** * 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); }
/** * 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)); }
/** * 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); }
/* * 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); }
/** * 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())); }
/** * 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); }
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; }
/** * 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); }
/** * 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); }
/** * 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); }
/** * 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. }
/** * 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); }
/** * 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. }
/** * 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); }
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); }
/** * 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); }
/** * 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)); }
/** * 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))); }
/** * 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); }