private static S1Angle bruteForceRectPointDistance(S2LatLngRect a, S2LatLng b) { if (a.Contains(b)) { return(S1Angle.FromRadians(0)); } var bToLoLat = getDistance(b, a.LatLo, a.Lng); var bToHiLat = getDistance(b, a.LatHi, a.Lng); var bToLoLng = S2EdgeUtil.GetDistance(b.ToPoint(), new S2LatLng(a.LatLo, a.LngLo).ToPoint(), new S2LatLng(a.LatHi, a.LngLo).ToPoint()); var bToHiLng = S2EdgeUtil.GetDistance(b.ToPoint(), new S2LatLng(a.LatLo, a.LngHi).ToPoint(), new S2LatLng(a.LatHi, a.LngHi).ToPoint()); return(S1Angle.Min(bToLoLat, S1Angle.Min(bToHiLat, S1Angle.Min(bToLoLng, bToHiLng)))); }
private static S1Angle bruteForceRectPointDistance(S2LatLngRect a, S2LatLng b) { if (a.Contains(b)) { return S1Angle.FromRadians(0); } var bToLoLat = getDistance(b, a.LatLo, a.Lng); var bToHiLat = getDistance(b, a.LatHi, a.Lng); var bToLoLng = S2EdgeUtil.GetDistance(b.ToPoint(), new S2LatLng(a.LatLo, a.LngLo).ToPoint(), new S2LatLng(a.LatHi, a.LngLo).ToPoint()); var bToHiLng = S2EdgeUtil.GetDistance(b.ToPoint(), new S2LatLng(a.LatLo, a.LngHi).ToPoint(), new S2LatLng(a.LatHi, a.LngHi).ToPoint()); return S1Angle.Min(bToLoLat, S1Angle.Min(bToHiLat, S1Angle.Min(bToLoLng, bToHiLng))); }
// This method is called to add a vertex to the chain when the vertex is // represented as an S2LatLng. Repeated vertices are ignored. public void AddLatLng(S2LatLng b_latlng) { AddInternal(b_latlng.ToPoint(), b_latlng); }
// Common back end for AddPoint() and AddLatLng(). b and b_latlng // must refer to the same vertex. private void AddInternal(S2Point b, S2LatLng b_latlng) { // Simple consistency check to verify that b and b_latlng are alternate // representations of the same vertex. System.Diagnostics.Debug.Assert(S2.ApproxEquals(b, b_latlng.ToPoint())); if (bound_.IsEmpty()) { bound_ = bound_.AddPoint(b_latlng); } else { // First compute the cross product N = A x B robustly. This is the normal // to the great circle through A and B. We don't use S2.RobustCrossProd() // since that method returns an arbitrary vector orthogonal to A if the two // vectors are proportional, and we want the zero vector in that case. var n = (a_ - b).CrossProd(a_ + b); // N = 2 * (A x B) // The relative error in N gets large as its norm gets very small (i.e., // when the two points are nearly identical or antipodal). We handle this // by choosing a maximum allowable error, and if the error is greater than // this we fall back to a different technique. Since it turns out that // the other sources of error in converting the normal to a maximum // latitude add up to at most 1.16 * S2Constants.DoubleEpsilon (see below), and it is // desirable to have the total error be a multiple of S2Constants.DoubleEpsilon, we have // chosen to limit the maximum error in the normal to 3.84 * S2Constants.DoubleEpsilon. // It is possible to show that the error is less than this when // // n.Norm >= 8 * Math.Sqrt(3) / (3.84 - 0.5 - Math.Sqrt(3)) * S2Constants.DoubleEpsilon // = 1.91346e-15 (about 8.618 * S2Constants.DoubleEpsilon) var n_norm = n.Norm(); if (n_norm < 1.91346e-15) { // A and B are either nearly identical or nearly antipodal (to within // 4.309 * S2Constants.DoubleEpsilon, or about 6 nanometers on the earth's surface). if (a_.DotProd(b) < 0) { // The two points are nearly antipodal. The easiest solution is to // assume that the edge between A and B could go in any direction // around the sphere. bound_ = S2LatLngRect.Full; } else { // The two points are nearly identical (to within 4.309 * S2Constants.DoubleEpsilon). // In this case we can just use the bounding rectangle of the points, // since after the expansion done by GetBound() this rectangle is // guaranteed to include the (lat,lng) values of all points along AB. bound_ = bound_.Union(S2LatLngRect.FromPointPair(a_latlng_, b_latlng)); } } else { // Compute the longitude range spanned by AB. var lng_ab = S1Interval.FromPointPair(a_latlng_.LngRadians, b_latlng.LngRadians); if (lng_ab.GetLength() >= Math.PI - 2 * S2.DoubleEpsilon) { // The points lie on nearly opposite lines of longitude to within the // maximum error of the calculation. (Note that this test relies on // the fact that Math.PI is slightly less than the true value of Pi, and // that representable values near Math.PI are 2 * S2Constants.DoubleEpsilon apart.) // The easiest solution is to assume that AB could go on either side // of the pole. lng_ab = S1Interval.Full; } // Next we compute the latitude range spanned by the edge AB. We start // with the range spanning the two endpoints of the edge: var lat_ab = R1Interval.FromPointPair(a_latlng_.LatRadians, b_latlng.LatRadians); // This is the desired range unless the edge AB crosses the plane // through N and the Z-axis (which is where the great circle through A // and B attains its minimum and maximum latitudes). To test whether AB // crosses this plane, we compute a vector M perpendicular to this // plane and then project A and B onto it. var m = n.CrossProd(new S2Point(0, 0, 1)); var m_a = m.DotProd(a_); var m_b = m.DotProd(b); // We want to test the signs of "m_a" and "m_b", so we need to bound // the error in these calculations. It is possible to show that the // total error is bounded by // // (1 + Math.Sqrt(3)) * S2Constants.DoubleEpsilon * n_norm + 8 * Math.Sqrt(3) * (S2Constants.DoubleEpsilon**2) // = 6.06638e-16 * n_norm + 6.83174e-31 double m_error = 6.06638e-16 * n_norm + 6.83174e-31; if (m_a * m_b < 0 || Math.Abs(m_a) <= m_error || Math.Abs(m_b) <= m_error) { // Minimum/maximum latitude *may* occur in the edge interior. // // The maximum latitude is 90 degrees minus the latitude of N. We // compute this directly using atan2 in order to get maximum accuracy // near the poles. // // Our goal is compute a bound that contains the computed latitudes of // all S2Points P that pass the point-in-polygon containment test. // There are three sources of error we need to consider: // - the directional error in N (at most 3.84 * S2Constants.DoubleEpsilon) // - converting N to a maximum latitude // - computing the latitude of the test point P // The latter two sources of error are at most 0.955 * S2Constants.DoubleEpsilon // individually, but it is possible to show by a more complex analysis // that together they can add up to at most 1.16 * S2Constants.DoubleEpsilon, for a // total error of 5 * S2Constants.DoubleEpsilon. // // We add 3 * S2Constants.DoubleEpsilon to the bound here, and GetBound() will pad // the bound by another 2 * S2Constants.DoubleEpsilon. var max_lat = Math.Min( Math.Atan2(Math.Sqrt(n[0] * n[0] + n[1] * n[1]), Math.Abs(n[2])) + 3 * S2.DoubleEpsilon, S2.M_PI_2); // In order to get tight bounds when the two points are close together, // we also bound the min/max latitude relative to the latitudes of the // endpoints A and B. First we compute the distance between A and B, // and then we compute the maximum change in latitude between any two // points along the great circle that are separated by this distance. // This gives us a latitude change "budget". Some of this budget must // be spent getting from A to B; the remainder bounds the round-trip // distance (in latitude) from A or B to the min or max latitude // attained along the edge AB. // // There is a maximum relative error of 4.5 * DBL_EPSILON in computing // the squared distance (a_ - b), which means a maximum error of (4.5 // / 2 + 0.5) == 2.75 * DBL_EPSILON in computing Norm(). The sin() // and multiply each have a relative error of 0.5 * DBL_EPSILON which // we round up to a total of 4 * DBL_EPSILON. var lat_budget_z = 0.5 * (a_ - b).Norm() * Math.Sin(max_lat); const double folded = (1 + 4 * S2.DoubleEpsilon); var lat_budget = 2 * Math.Asin(Math.Min(folded * lat_budget_z, 1.0)); var max_delta = 0.5 * (lat_budget - lat_ab.GetLength()) + S2.DoubleEpsilon; // Test whether AB passes through the point of maximum latitude or // minimum latitude. If the dot product(s) are small enough then the // result may be ambiguous. if (m_a <= m_error && m_b >= -m_error) { lat_ab = new R1Interval(lat_ab.Lo, Math.Min(max_lat, lat_ab.Hi + max_delta)); } if (m_b <= m_error && m_a >= -m_error) { lat_ab = new R1Interval(Math.Max(-max_lat, lat_ab.Lo - max_delta), lat_ab.Lo); } } bound_ = bound_.Union(new S2LatLngRect(lat_ab, lng_ab)); } } a_ = b; a_latlng_ = b_latlng; }