public void Test_S2LatLngRect_GetDirectedHausdorffDistanceRandomPairs() { // Test random pairs. int kIters = 1000; for (int i = 0; i < kIters; ++i) { S2LatLngRect a = S2LatLngRect.FromPointPair(new S2LatLng(S2Testing.RandomPoint()), new S2LatLng(S2Testing.RandomPoint())); S2LatLngRect b = S2LatLngRect.FromPointPair(new S2LatLng(S2Testing.RandomPoint()), new S2LatLng(S2Testing.RandomPoint())); // a and b are *minimum* bounding rectangles of two random points, in // particular, their Voronoi diagrams are always of the same topology. We // take the "complements" of a and b for more thorough testing. S2LatLngRect a2 = new(a.Lat, a.Lng.Complement()); S2LatLngRect b2 = new(b.Lat, b.Lng.Complement()); // Note that "a" and "b" come from the same distribution, so there is no // need to test pairs such as (b, a), (b, a2), etc. VerifyGetDirectedHausdorffDistance(a, b); VerifyGetDirectedHausdorffDistance(a, b2); VerifyGetDirectedHausdorffDistance(a2, b); VerifyGetDirectedHausdorffDistance(a2, b2); } }
public void testGetDistanceRandomPairs() { // Test random pairs. for (var i = 0; i < 10000; ++i) { var a = S2LatLngRect.FromPointPair(new S2LatLng(randomPoint()), new S2LatLng(randomPoint())); var b = S2LatLngRect.FromPointPair(new S2LatLng(randomPoint()), new S2LatLng(randomPoint())); verifyGetDistance(a, b); var c = new S2LatLng(randomPoint()); verifyGetRectPointDistance(a, c); verifyGetRectPointDistance(b, c); } }
public void Test_S2LatLngRect_GetDistanceRandomPairs() { // Test random pairs. for (int i = 0; i < 10000; ++i) { S2LatLngRect a = S2LatLngRect.FromPointPair(new S2LatLng(S2Testing.RandomPoint()), new S2LatLng(S2Testing.RandomPoint())); S2LatLngRect b = S2LatLngRect.FromPointPair(new S2LatLng(S2Testing.RandomPoint()), new S2LatLng(S2Testing.RandomPoint())); VerifyGetDistance(a, b); S2LatLng c = new(S2Testing.RandomPoint()); VerifyGetRectPointDistance(a, c); VerifyGetRectPointDistance(b, c); } }
public void Test_S2LatLngRect_FromPointPair() { Assert.Equal(S2LatLngRect.FromPointPair(S2LatLng.FromDegrees(-35, -140), S2LatLng.FromDegrees(15, 155)), RectFromDegrees(-35, 155, 15, -140)); Assert.Equal(S2LatLngRect.FromPointPair(S2LatLng.FromDegrees(25, -70), S2LatLng.FromDegrees(-90, 80)), RectFromDegrees(-90, -70, 25, 80)); }
public void testBasic() { // Most of the S2LatLngRect methods have trivial implementations that // use the R1Interval and S1Interval classes, so most of the testing // is done in those unit tests. // Test basic properties of empty and full caps. var empty = S2LatLngRect.Empty; var full = S2LatLngRect.Full; assertTrue(empty.IsValid); assertTrue(empty.IsEmpty); assertTrue(full.IsValid); assertTrue(full.IsFull); // assertTrue various constructors and accessor methods. var d1 = rectFromDegrees(-90, 0, -45, 180); assertDoubleNear(d1.LatLo.Degrees, -90); assertDoubleNear(d1.LatHi.Degrees, -45); assertDoubleNear(d1.LngLo.Degrees, 0); assertDoubleNear(d1.LngHi.Degrees, 180); assertTrue(d1.Lat.Equals(new R1Interval(-S2.PiOver2, -S2.PiOver4))); assertTrue(d1.Lng.Equals(new S1Interval(0, S2.Pi))); // FromCenterSize() assertTrue( S2LatLngRect.FromCenterSize(S2LatLng.FromDegrees(80, 170), S2LatLng.FromDegrees(40, 60)) .ApproxEquals(rectFromDegrees(60, 140, 90, -160))); assertTrue(S2LatLngRect .FromCenterSize(S2LatLng.FromDegrees(10, 40), S2LatLng.FromDegrees(210, 400)).IsFull); assertTrue( S2LatLngRect.FromCenterSize(S2LatLng.FromDegrees(-90, 180), S2LatLng.FromDegrees(20, 50)) .ApproxEquals(rectFromDegrees(-90, 155, -80, -155))); // FromPoint(), FromPointPair() assertEquals(S2LatLngRect.FromPoint(d1.Lo), new S2LatLngRect(d1.Lo, d1.Lo)); assertEquals( S2LatLngRect.FromPointPair(S2LatLng.FromDegrees(-35, -140), S2LatLng.FromDegrees(15, 155)), rectFromDegrees(-35, 155, 15, -140)); assertEquals( S2LatLngRect.FromPointPair(S2LatLng.FromDegrees(25, -70), S2LatLng.FromDegrees(-90, 80)), rectFromDegrees(-90, -70, 25, 80)); // GetCenter(), GetVertex(), Contains(S2LatLng), InteriorContains(S2LatLng). var eqM180 = S2LatLng.FromRadians(0, -S2.Pi); var northPole = S2LatLng.FromRadians(S2.PiOver2, 0); var r1 = new S2LatLngRect(eqM180, northPole); assertEquals(r1.Center, S2LatLng.FromRadians(S2.PiOver4, -S2.PiOver2)); assertEquals(r1.GetVertex(0), S2LatLng.FromRadians(0, S2.Pi)); assertEquals(r1.GetVertex(1), S2LatLng.FromRadians(0, 0)); assertEquals(r1.GetVertex(2), S2LatLng.FromRadians(S2.PiOver2, 0)); assertEquals(r1.GetVertex(3), S2LatLng.FromRadians(S2.PiOver2, S2.Pi)); assertTrue(r1.Contains(S2LatLng.FromDegrees(30, -45))); assertTrue(!r1.Contains(S2LatLng.FromDegrees(30, 45))); assertTrue(!r1.InteriorContains(eqM180) && !r1.InteriorContains(northPole)); assertTrue(r1.Contains(new S2Point(0.5, -0.3, 0.1))); assertTrue(!r1.Contains(new S2Point(0.5, 0.2, 0.1))); // Make sure that GetVertex() returns vertices in CCW order. for (var i = 0; i < 4; ++i) { var lat = S2.PiOver4 * (i - 2); var lng = S2.PiOver2 * (i - 2) + 0.2; var r = new S2LatLngRect(new R1Interval(lat, lat + S2.PiOver4), new S1Interval( Math.IEEERemainder(lng, 2 * S2.Pi), Math.IEEERemainder(lng + S2.PiOver2, 2 * S2.Pi))); for (var k = 0; k < 4; ++k) { assertTrue( S2.SimpleCcw(r.GetVertex((k - 1) & 3).ToPoint(), r.GetVertex(k).ToPoint(), r.GetVertex((k + 1) & 3).ToPoint())); } } // Contains(S2LatLngRect), InteriorContains(S2LatLngRect), // Intersects(), InteriorIntersects(), Union(), Intersection(). // // Much more testing of these methods is done in s1interval_unittest // and r1interval_unittest. var r1Mid = rectFromDegrees(45, -90, 45, -90); var reqM180 = new S2LatLngRect(eqM180, eqM180); var rNorthPole = new S2LatLngRect(northPole, northPole); testIntervalOps(r1, r1Mid, "TTTT", r1, r1Mid); testIntervalOps(r1, reqM180, "TFTF", r1, reqM180); testIntervalOps(r1, rNorthPole, "TFTF", r1, rNorthPole); assertTrue(r1.Equals(rectFromDegrees(0, -180, 90, 0))); testIntervalOps(r1, rectFromDegrees(-10, -1, 1, 20), "FFTT", rectFromDegrees(-10, -180, 90, 20), rectFromDegrees(0, -1, 1, 0)); testIntervalOps(r1, rectFromDegrees(-10, -1, 0, 20), "FFTF", rectFromDegrees(-10, -180, 90, 20), rectFromDegrees(0, -1, 0, 0)); testIntervalOps(r1, rectFromDegrees(-10, 0, 1, 20), "FFTF", rectFromDegrees(-10, -180, 90, 20), rectFromDegrees(0, 0, 1, 0)); testIntervalOps(rectFromDegrees(-15, -160, -15, -150), rectFromDegrees(20, 145, 25, 155), "FFFF", rectFromDegrees(-15, 145, 25, -150), empty); testIntervalOps(rectFromDegrees(70, -10, 90, -140), rectFromDegrees(60, 175, 80, 5), "FFTT", rectFromDegrees(60, -180, 90, 180), rectFromDegrees(70, 175, 80, 5)); // assertTrue that the intersection of two rectangles that overlap in // latitude // but not longitude is valid, and vice versa. testIntervalOps(rectFromDegrees(12, 30, 60, 60), rectFromDegrees(0, 0, 30, 18), "FFFF", rectFromDegrees(0, 0, 60, 60), empty); testIntervalOps(rectFromDegrees(0, 0, 18, 42), rectFromDegrees(30, 12, 42, 60), "FFFF", rectFromDegrees(0, 0, 42, 60), empty); // AddPoint() var p = S2LatLngRect.Empty; p = p.AddPoint(S2LatLng.FromDegrees(0, 0)); p = p.AddPoint(S2LatLng.FromRadians(0, -S2.PiOver2)); p = p.AddPoint(S2LatLng.FromRadians(S2.PiOver4, -S2.Pi)); p = p.AddPoint(new S2Point(0, 0, 1)); assertTrue(p.Equals(r1)); // Expanded() assertTrue( rectFromDegrees(70, 150, 80, 170).Expanded(S2LatLng.FromDegrees(20, 30)).ApproxEquals( rectFromDegrees(50, 120, 90, -160))); assertTrue(S2LatLngRect.Empty.Expanded(S2LatLng.FromDegrees(20, 30)).IsEmpty); assertTrue(S2LatLngRect.Full.Expanded(S2LatLng.FromDegrees(20, 30)).IsFull); assertTrue( rectFromDegrees(-90, 170, 10, 20).Expanded(S2LatLng.FromDegrees(30, 80)).ApproxEquals( rectFromDegrees(-90, -180, 40, 180))); // ConvolveWithCap() var llr1 = new S2LatLngRect(S2LatLng.FromDegrees(0, 170), S2LatLng.FromDegrees(0, -170)) .ConvolveWithCap(S1Angle.FromDegrees(15)); var llr2 = new S2LatLngRect(S2LatLng.FromDegrees(-15, 155), S2LatLng.FromDegrees(15, -155)); assertTrue(llr1.ApproxEquals(llr2)); llr1 = new S2LatLngRect(S2LatLng.FromDegrees(60, 150), S2LatLng.FromDegrees(80, 10)) .ConvolveWithCap(S1Angle.FromDegrees(15)); llr2 = new S2LatLngRect(S2LatLng.FromDegrees(45, -180), S2LatLng.FromDegrees(90, 180)); assertTrue(llr1.ApproxEquals(llr2)); // GetCapBound(), bounding cap at center is smaller: assertTrue(new S2LatLngRect(S2LatLng.FromDegrees(-45, -45), S2LatLng.FromDegrees(45, 45)).CapBound.ApproxEquals(S2Cap.FromAxisHeight(new S2Point(1, 0, 0), 0.5))); // GetCapBound(), bounding cap at north pole is smaller: assertTrue(new S2LatLngRect(S2LatLng.FromDegrees(88, -80), S2LatLng.FromDegrees(89, 80)).CapBound.ApproxEquals(S2Cap.FromAxisAngle(new S2Point(0, 0, 1), S1Angle.FromDegrees(2)))); // GetCapBound(), longitude span > 180 degrees: assertTrue( new S2LatLngRect(S2LatLng.FromDegrees(-30, -150), S2LatLng.FromDegrees(-10, 50)).CapBound .ApproxEquals(S2Cap.FromAxisAngle(new S2Point(0, 0, -1), S1Angle.FromDegrees(80)))); // Contains(S2Cell), MayIntersect(S2Cell), Intersects(S2Cell) // Special cases. testCellOps(empty, S2Cell.FromFacePosLevel(3, (byte)0, 0), 0); testCellOps(full, S2Cell.FromFacePosLevel(2, (byte)0, 0), 4); testCellOps(full, S2Cell.FromFacePosLevel(5, (byte)0, 25), 4); // This rectangle includes the first quadrant of face 0. It's expanded // slightly because cell bounding rectangles are slightly conservative. var r4 = rectFromDegrees(-45.1, -45.1, 0.1, 0.1); testCellOps(r4, S2Cell.FromFacePosLevel(0, (byte)0, 0), 3); testCellOps(r4, S2Cell.FromFacePosLevel(0, (byte)0, 1), 4); testCellOps(r4, S2Cell.FromFacePosLevel(1, (byte)0, 1), 0); // This rectangle intersects the first quadrant of face 0. var r5 = rectFromDegrees(-10, -45, 10, 0); testCellOps(r5, S2Cell.FromFacePosLevel(0, (byte)0, 0), 3); testCellOps(r5, S2Cell.FromFacePosLevel(0, (byte)0, 1), 3); testCellOps(r5, S2Cell.FromFacePosLevel(1, (byte)0, 1), 0); // Rectangle consisting of a single point. testCellOps(rectFromDegrees(4, 4, 4, 4), S2Cell.FromFacePosLevel(0, (byte)0, 0), 3); // Rectangles that intersect the bounding rectangle of a face // but not the face itself. testCellOps(rectFromDegrees(41, -87, 42, -79), S2Cell.FromFacePosLevel(2, (byte)0, 0), 1); testCellOps(rectFromDegrees(-41, 160, -40, -160), S2Cell.FromFacePosLevel(5, (byte)0, 0), 1); { // This is the leaf cell at the top right hand corner of face 0. // It has two angles of 60 degrees and two of 120 degrees. var cell0tr = new S2Cell(new S2Point(1 + 1e-12, 1, 1)); var bound0tr = cell0tr.RectBound; var v0 = new S2LatLng(cell0tr.GetVertexRaw(0)); testCellOps( rectFromDegrees(v0.Lat.Degrees - 1e-8, v0.Lng.Degrees - 1e-8, v0.Lat.Degrees - 2e-10, v0.Lng.Degrees + 1e-10), cell0tr, 1); } // Rectangles that intersect a face but where no vertex of one region // is contained by the other region. The first one passes through // a corner of one of the face cells. testCellOps(rectFromDegrees(-37, -70, -36, -20), S2Cell.FromFacePosLevel(5, (byte)0, 0), 2); { // These two intersect like a diamond and a square. var cell202 = S2Cell.FromFacePosLevel(2, (byte)0, 2); var bound202 = cell202.RectBound; testCellOps( rectFromDegrees(bound202.Lo.Lat.Degrees + 3, bound202.Lo.Lng.Degrees + 3, bound202.Hi.Lat.Degrees - 3, bound202.Hi.Lng.Degrees - 3), cell202, 2); } }
async void Load_Click(object sender, RoutedEventArgs e) { var picker = new Windows.Storage.Pickers.FileOpenPicker(); picker.ViewMode = Windows.Storage.Pickers.PickerViewMode.Thumbnail; picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary; picker.FileTypeFilter.Add(".geojson"); picker.FileTypeFilter.Add(".geoJSON"); picker.FileTypeFilter.Add(".csv"); Windows.Storage.StorageFile file = await picker.PickSingleFileAsync(); if (file != null) { try { using (StreamReader sr = new StreamReader(file.Path)) { string content = sr.ReadToEnd(); if (file.FileType == ".csv") { string[] lines = content.Split('\n'); for (int i = 0; i < lines.Length; i++) { var item = lines[i]; if (String.IsNullOrWhiteSpace(item)) { continue; } string[] info = item.Split(','); double lat = 0; double lon = 0; if (!Double.TryParse(info[info.Length - 2], out lon) || !Double.TryParse(info[info.Length - 3], out lat)) { if (i == 0) { continue; } var messageDialog = new MessageDialog("Zeile " + i + " (" + item + ") hat nicht das richtige Format!"); messageDialog.Commands.Add(new UICommand("OK", new UICommandInvokedHandler(this.CommandInvokedHandler))); messageDialog.DefaultCommandIndex = 0; messageDialog.Title = "Fehlerhafte Datei"; await messageDialog.ShowAsync(); return; } StringBuilder sb = new StringBuilder(""); for (int j = 0; j < info.Length - 3; j++) { sb.Append(info[j]); } PointOfInterest poi = new PointOfInterest() { Location = new Geopoint(new BasicGeoposition() { Latitude = lat, Longitude = lon }), DisplayName = sb.ToString(), NormalizedAnchorPoint = AnchorPoints[2], Flag = PointOfInterest.PORTAL, Leaf = FindExactCell(new BasicGeoposition() { Latitude = lat, Longitude = lon }, 30).Id.Id }; portals.Add(poi); //DataAccess.AddData(poi); } } else { var features = JsonConvert.DeserializeObject <FeatureCollection>(content).Features; foreach (var p in features) { double lon = ((Point)p.Geometry).Coordinates.Longitude; double lat = ((Point)p.Geometry).Coordinates.Latitude; int flag = PointOfInterest.PORTAL; if (p.Properties.ContainsKey("Flag")) { flag = (int)p.Properties["Flag"]; } PointOfInterest poi = new PointOfInterest() { Location = new Geopoint(new BasicGeoposition() { Latitude = lat, Longitude = lon }), DisplayName = (string)p.Properties["name"], NormalizedAnchorPoint = AnchorPoints[flag], Flag = flag, Leaf = FindExactCell(new BasicGeoposition() { Latitude = lat, Longitude = lon }, 30).Id.Id }; portals.Add(poi); //DataAccess.AddData(poi); } } AddLayers(); } } catch (UnauthorizedAccessException) { Windows.Storage.StorageFolder installedLocation = Windows.ApplicationModel.Package.Current.InstalledLocation; var messageDialog = new MessageDialog("Zugriff auf den Pfad " + file.Path + " wurde verweigert.\nBitte gewähren Sie Zugriff unter \n\nEinstellungen -> Privatsphäre -> Dateisystem\n\noder bewegen Sie die Datei an diesen Ort:\n" + installedLocation.Path); messageDialog.Commands.Add(new UICommand("OK", new UICommandInvokedHandler(this.CommandInvokedHandler))); messageDialog.DefaultCommandIndex = 0; messageDialog.Title = "Keine Zugriffsrechte"; await messageDialog.ShowAsync(); return; } // outermost bounds double maxLat = -360; double maxLon = -360; double minLat = 360; double minLon = 360; // Find out all Lvl17 cells foreach (var p in portals) { double lat = p.Location.Position.Latitude; double lon = p.Location.Position.Longitude; if (lon < minLon) { minLon = lon; } if (lon > maxLon) { maxLon = lon; } if (lat < minLat) { minLat = lat; } if (lat > maxLat) { maxLat = lat; } var cell = FindExactCell(p.Location.Position, 17); MapPolygon polygon = new MapPolygon { Path = new Geopath(new List <BasicGeoposition>() { FromS2Point(cell.GetVertex(0)), FromS2Point(cell.GetVertex(1)), FromS2Point(cell.GetVertex(2)), FromS2Point(cell.GetVertex(3)) }), ZIndex = 1, FillColor = Color.FromArgb(50, 50, 50, 50), StrokeColor = Colors.Black, StrokeThickness = 2, StrokeDashed = false }; lvl17Cells.Add(polygon); AddCell(polygon); } // Find out Lvl14 cells var area = S2LatLngRect.FromPointPair( S2LatLng.FromDegrees(minLat, minLon), S2LatLng.FromDegrees(maxLat, maxLon) ); var cells14 = new List <S2CellId>(); lvl14Coverer.GetCovering(area, cells14); for (int i = 0; i < cells14.Count; i++) { S2Cell cell = new S2Cell(cells14[i]); MapPolygon polygon = new MapPolygon { Path = new Geopath(new List <BasicGeoposition>() { FromS2Point(cell.GetVertex(0)), FromS2Point(cell.GetVertex(1)), FromS2Point(cell.GetVertex(2)), FromS2Point(cell.GetVertex(3)) }), ZIndex = 1, FillColor = Color.FromArgb(50, 0, 0, 50), StrokeColor = Colors.Blue, StrokeThickness = 2, StrokeDashed = false }; lvl14Cells.Add(polygon); AddCell(polygon); } BasicGeoposition newCenter = new BasicGeoposition(); newCenter.Latitude = (minLat + maxLat) / 2; newCenter.Longitude = (minLon + maxLon) / 2; PokeMap.Center = new Geopoint(newCenter); } }
// 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; }