def test_Planimeter19(self): # Coverage tests, includes Planimeter19 - Planimeter20 (degenerate # polygons) + extra cases. PlanimeterTest.polygon.Clear() num, perimeter, area = PlanimeterTest.polygon.Compute(False, True) self.assertTrue(area == 0) self.assertTrue(perimeter == 0) num, perimeter, area = PlanimeterTest.polygon.TestPoint( 1, 1, False, True) self.assertTrue(area == 0) self.assertTrue(perimeter == 0) num, perimeter, area = PlanimeterTest.polygon.TestEdge( 90, 1000, False, True) self.assertTrue(Math.isnan(area)) self.assertTrue(Math.isnan(perimeter)) PlanimeterTest.polygon.AddPoint(1, 1) num, perimeter, area = PlanimeterTest.polygon.Compute(False, True) self.assertTrue(area == 0) self.assertTrue(perimeter == 0) PlanimeterTest.polyline.Clear() num, perimeter, area = PlanimeterTest.polyline.Compute(False, True) self.assertTrue(perimeter == 0) num, perimeter, area = PlanimeterTest.polyline.TestPoint( 1, 1, False, True) self.assertTrue(perimeter == 0) num, perimeter, area = PlanimeterTest.polyline.TestEdge( 90, 1000, False, True) self.assertTrue(Math.isnan(perimeter)) PlanimeterTest.polyline.AddPoint(1, 1) num, perimeter, area = PlanimeterTest.polyline.Compute(False, True) self.assertTrue(perimeter == 0) PlanimeterTest.polygon.AddPoint(1, 1) num, perimeter, area = PlanimeterTest.polyline.TestEdge( 90, 1000, False, True) self.assertAlmostEqual(perimeter, 1000, delta=1e-10) num, perimeter, area = PlanimeterTest.polyline.TestPoint( 2, 2, False, True) self.assertAlmostEqual(perimeter, 156876.149, delta=0.5e-3)
def test_GeodSolve55(self): # Check fix for nan + point on equator or pole not returning all nans in # Geodesic::Inverse, found 2015-09-23. inv = Geodesic.WGS84.Inverse(Math.nan, 0, 0, 90) self.assertTrue(Math.isnan(inv["azi1"])) self.assertTrue(Math.isnan(inv["azi2"])) self.assertTrue(Math.isnan(inv["s12"])) inv = Geodesic.WGS84.Inverse(Math.nan, 0, 90, 9) self.assertTrue(Math.isnan(inv["azi1"])) self.assertTrue(Math.isnan(inv["azi2"])) self.assertTrue(Math.isnan(inv["s12"]))
def test_GeodSolve80(self): # Some tests to add code coverage: computing scale in special cases + zero # length geodesic (includes GeodSolve80 - GeodSolve83) + using an incapable # line. inv = Geodesic.WGS84.Inverse(0, 0, 0, 90, Geodesic.GEODESICSCALE) self.assertAlmostEqual(inv["M12"], -0.00528427534, delta=0.5e-10) self.assertAlmostEqual(inv["M21"], -0.00528427534, delta=0.5e-10) inv = Geodesic.WGS84.Inverse(0, 0, 1e-6, 1e-6, Geodesic.GEODESICSCALE) self.assertAlmostEqual(inv["M12"], 1, delta=0.5e-10) self.assertAlmostEqual(inv["M21"], 1, delta=0.5e-10) inv = Geodesic.WGS84.Inverse(20.001, 0, 20.001, 0, Geodesic.ALL) self.assertAlmostEqual(inv["a12"], 0, delta=1e-13) self.assertAlmostEqual(inv["s12"], 0, delta=1e-8) self.assertAlmostEqual(inv["azi1"], 180, delta=1e-13) self.assertAlmostEqual(inv["azi2"], 180, delta=1e-13) self.assertAlmostEqual(inv["m12"], 0, delta=1e-8) self.assertAlmostEqual(inv["M12"], 1, delta=1e-15) self.assertAlmostEqual(inv["M21"], 1, delta=1e-15) self.assertAlmostEqual(inv["S12"], 0, delta=1e-10) self.assertTrue(Math.copysign(1, inv["a12"]) > 0) self.assertTrue(Math.copysign(1, inv["s12"]) > 0) self.assertTrue(Math.copysign(1, inv["m12"]) > 0) inv = Geodesic.WGS84.Inverse(90, 0, 90, 180, Geodesic.ALL) self.assertAlmostEqual(inv["a12"], 0, delta=1e-13) self.assertAlmostEqual(inv["s12"], 0, delta=1e-8) self.assertAlmostEqual(inv["azi1"], 0, delta=1e-13) self.assertAlmostEqual(inv["azi2"], 180, delta=1e-13) self.assertAlmostEqual(inv["m12"], 0, delta=1e-8) self.assertAlmostEqual(inv["M12"], 1, delta=1e-15) self.assertAlmostEqual(inv["M21"], 1, delta=1e-15) self.assertAlmostEqual(inv["S12"], 127516405431022.0, delta=0.5) # An incapable line which can't take distance as input line = Geodesic.WGS84.Line(1, 2, 90, Geodesic.LATITUDE) dir = line.Position(1000, Geodesic.EMPTY) self.assertTrue(Math.isnan(dir["a12"]))
def test_Planimeter0(self): # Check fix for pole-encircling bug found 2011-03-16 points = [[89, 0], [89, 90], [89, 180], [89, 270]] num, perimeter, area = PlanimeterTest.Planimeter(points) self.assertAlmostEqual(perimeter, 631819.8745, delta=1e-4) self.assertAlmostEqual(area, 24952305678.0, delta=1) points = [[-89, 0], [-89, 90], [-89, 180], [-89, 270]] num, perimeter, area = PlanimeterTest.Planimeter(points) self.assertAlmostEqual(perimeter, 631819.8745, delta=1e-4) self.assertAlmostEqual(area, -24952305678.0, delta=1) points = [[0, -1], [-1, 0], [0, 1], [1, 0]] num, perimeter, area = PlanimeterTest.Planimeter(points) self.assertAlmostEqual(perimeter, 627598.2731, delta=1e-4) self.assertAlmostEqual(area, 24619419146.0, delta=1) points = [[90, 0], [0, 0], [0, 90]] num, perimeter, area = PlanimeterTest.Planimeter(points) self.assertAlmostEqual(perimeter, 30022685, delta=1) self.assertAlmostEqual(area, 63758202715511.0, delta=1) num, perimeter, area = PlanimeterTest.PolyLength(points) self.assertAlmostEqual(perimeter, 20020719, delta=1) self.assertTrue(Math.isnan(area))
def test_Planimeter0(self): # Check fix for pole-encircling bug found 2011-03-16 points = [[89, 0], [89, 90], [89, 180], [89, 270]] num, perimeter, area = PlanimeterTest.Planimeter(points) self.assertAlmostEqual(perimeter, 631819.8745, delta = 1e-4) self.assertAlmostEqual(area, 24952305678.0, delta = 1) points = [[-89, 0], [-89, 90], [-89, 180], [-89, 270]] num, perimeter, area = PlanimeterTest.Planimeter(points) self.assertAlmostEqual(perimeter, 631819.8745, delta = 1e-4) self.assertAlmostEqual(area, -24952305678.0, delta = 1) points = [[0, -1], [-1, 0], [0, 1], [1, 0]] num, perimeter, area = PlanimeterTest.Planimeter(points) self.assertAlmostEqual(perimeter, 627598.2731, delta = 1e-4) self.assertAlmostEqual(area, 24619419146.0, delta = 1) points = [[90, 0], [0, 0], [0, 90]] num, perimeter, area = PlanimeterTest.Planimeter(points) self.assertAlmostEqual(perimeter, 30022685, delta = 1) self.assertAlmostEqual(area, 63758202715511.0, delta = 1) num, perimeter, area = PlanimeterTest.PolyLength(points) self.assertAlmostEqual(perimeter, 20020719, delta = 1) self.assertTrue(Math.isnan(area))
def __init__(self, geod, lat1, lon1, azi1, caps = GeodesicCapability.STANDARD | GeodesicCapability.DISTANCE_IN, salp1 = Math.nan, calp1 = Math.nan): """Construct a GeodesicLine object :param geod: a :class:`~geographiclib.geodesic.Geodesic` object :param lat1: latitude of the first point in degrees :param lon1: longitude of the first point in degrees :param azi1: azimuth at the first point in degrees :param caps: the :ref:`capabilities <outmask>` This creates an object allowing points along a geodesic starting at (*lat1*, *lon1*), with azimuth *azi1* to be found. The default value of *caps* is STANDARD | DISTANCE_IN. The optional parameters *salp1* and *calp1* should not be supplied; they are part of the private interface. """ from geographiclib.geodesic import Geodesic self.a = geod.a """The equatorial radius in meters (readonly)""" self.f = geod.f """The flattening (readonly)""" self._b = geod._b self._c2 = geod._c2 self._f1 = geod._f1 self.caps = (caps | Geodesic.LATITUDE | Geodesic.AZIMUTH | Geodesic.LONG_UNROLL) """the capabilities (readonly)""" # Guard against underflow in salp0 self.lat1 = Math.LatFix(lat1) """the latitude of the first point in degrees (readonly)""" self.lon1 = lon1 """the longitude of the first point in degrees (readonly)""" if Math.isnan(salp1) or Math.isnan(calp1): self.azi1 = Math.AngNormalize(azi1) self.salp1, self.calp1 = Math.sincosd(Math.AngRound(azi1)) else: self.azi1 = azi1 """the azimuth at the first point in degrees (readonly)""" self.salp1 = salp1 """the sine of the azimuth at the first point (readonly)""" self.calp1 = calp1 """the cosine of the azimuth at the first point (readonly)""" # real cbet1, sbet1 sbet1, cbet1 = Math.sincosd(Math.AngRound(lat1)); sbet1 *= self._f1 # Ensure cbet1 = +epsilon at poles sbet1, cbet1 = Math.norm(sbet1, cbet1); cbet1 = max(Geodesic.tiny_, cbet1) self._dn1 = math.sqrt(1 + geod._ep2 * Math.sq(sbet1)) # Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), self._salp0 = self.salp1 * cbet1 # alp0 in [0, pi/2 - |bet1|] # Alt: calp0 = hypot(sbet1, calp1 * cbet1). The following # is slightly better (consider the case salp1 = 0). self._calp0 = math.hypot(self.calp1, self.salp1 * sbet1) # Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1). # sig = 0 is nearest northward crossing of equator. # With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line). # With bet1 = pi/2, alp1 = -pi, sig1 = pi/2 # With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2 # Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1). # With alp0 in (0, pi/2], quadrants for sig and omg coincide. # No atan2(0,0) ambiguity at poles since cbet1 = +epsilon. # With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi. self._ssig1 = sbet1; self._somg1 = self._salp0 * sbet1 self._csig1 = self._comg1 = (cbet1 * self.calp1 if sbet1 != 0 or self.calp1 != 0 else 1) # sig1 in (-pi, pi] self._ssig1, self._csig1 = Math.norm(self._ssig1, self._csig1) # No need to normalize # self._somg1, self._comg1 = Math.norm(self._somg1, self._comg1) self._k2 = Math.sq(self._calp0) * geod._ep2 eps = self._k2 / (2 * (1 + math.sqrt(1 + self._k2)) + self._k2) if self.caps & Geodesic.CAP_C1: self._A1m1 = Geodesic._A1m1f(eps) self._C1a = list(range(Geodesic.nC1_ + 1)) Geodesic._C1f(eps, self._C1a) self._B11 = Geodesic._SinCosSeries( True, self._ssig1, self._csig1, self._C1a) s = math.sin(self._B11); c = math.cos(self._B11) # tau1 = sig1 + B11 self._stau1 = self._ssig1 * c + self._csig1 * s self._ctau1 = self._csig1 * c - self._ssig1 * s # Not necessary because C1pa reverts C1a # _B11 = -_SinCosSeries(true, _stau1, _ctau1, _C1pa) if self.caps & Geodesic.CAP_C1p: self._C1pa = list(range(Geodesic.nC1p_ + 1)) Geodesic._C1pf(eps, self._C1pa) if self.caps & Geodesic.CAP_C2: self._A2m1 = Geodesic._A2m1f(eps) self._C2a = list(range(Geodesic.nC2_ + 1)) Geodesic._C2f(eps, self._C2a) self._B21 = Geodesic._SinCosSeries( True, self._ssig1, self._csig1, self._C2a) if self.caps & Geodesic.CAP_C3: self._C3a = list(range(Geodesic.nC3_)) geod._C3f(eps, self._C3a) self._A3c = -self.f * self._salp0 * geod._A3f(eps) self._B31 = Geodesic._SinCosSeries( True, self._ssig1, self._csig1, self._C3a) if self.caps & Geodesic.CAP_C4: self._C4a = list(range(Geodesic.nC4_)) geod._C4f(eps, self._C4a) # Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0) self._A4 = Math.sq(self.a) * self._calp0 * self._salp0 * geod._e2 self._B41 = Geodesic._SinCosSeries( False, self._ssig1, self._csig1, self._C4a) self.s13 = Math.nan """the distance between point 1 and point 3 in meters (readonly)""" self.a13 = Math.nan """the arc length between point 1 and point 3 in degrees (readonly)"""
def test_GeodSolve84(self): # Tests for python implementation to check fix for range errors with # {fmod,sin,cos}(inf) (includes GeodSolve84 - GeodSolve91). dir = Geodesic.WGS84.Direct(0, 0, 90, Math.inf) self.assertTrue(Math.isnan(dir["lat2"])) self.assertTrue(Math.isnan(dir["lon2"])) self.assertTrue(Math.isnan(dir["azi2"])) dir = Geodesic.WGS84.Direct(0, 0, 90, Math.nan) self.assertTrue(Math.isnan(dir["lat2"])) self.assertTrue(Math.isnan(dir["lon2"])) self.assertTrue(Math.isnan(dir["azi2"])) dir = Geodesic.WGS84.Direct(0, 0, Math.inf, 1000) self.assertTrue(Math.isnan(dir["lat2"])) self.assertTrue(Math.isnan(dir["lon2"])) self.assertTrue(Math.isnan(dir["azi2"])) dir = Geodesic.WGS84.Direct(0, 0, Math.nan, 1000) self.assertTrue(Math.isnan(dir["lat2"])) self.assertTrue(Math.isnan(dir["lon2"])) self.assertTrue(Math.isnan(dir["azi2"])) dir = Geodesic.WGS84.Direct(0, Math.inf, 90, 1000) self.assertTrue(dir["lat1"] == 0) self.assertTrue(Math.isnan(dir["lon2"])) self.assertTrue(dir["azi2"] == 90) dir = Geodesic.WGS84.Direct(0, Math.nan, 90, 1000) self.assertTrue(dir["lat1"] == 0) self.assertTrue(Math.isnan(dir["lon2"])) self.assertTrue(dir["azi2"] == 90) dir = Geodesic.WGS84.Direct(Math.inf, 0, 90, 1000) self.assertTrue(Math.isnan(dir["lat2"])) self.assertTrue(Math.isnan(dir["lon2"])) self.assertTrue(Math.isnan(dir["azi2"])) dir = Geodesic.WGS84.Direct(Math.nan, 0, 90, 1000) self.assertTrue(Math.isnan(dir["lat2"])) self.assertTrue(Math.isnan(dir["lon2"])) self.assertTrue(Math.isnan(dir["azi2"]))
def test_GeodSolve14(self): # Check fix for inverse ignoring lon12 = nan inv = Geodesic.WGS84.Inverse(0, 0, 1, Math.nan) self.assertTrue(Math.isnan(inv["azi1"])) self.assertTrue(Math.isnan(inv["azi2"])) self.assertTrue(Math.isnan(inv["s12"]))
def __init__(self, geod, lat1, lon1, azi1, caps=GeodesicCapability.STANDARD | GeodesicCapability.DISTANCE_IN, salp1=Math.nan, calp1=Math.nan): """Construct a GeodesicLine object :param geod: a :class:`~geographiclib.geodesic.Geodesic` object :param lat1: latitude of the first point in degrees :param lon1: longitude of the first point in degrees :param azi1: azimuth at the first point in degrees :param caps: the :ref:`capabilities <outmask>` This creates an object allowing points along a geodesic starting at (*lat1*, *lon1*), with azimuth *azi1* to be found. The default value of *caps* is STANDARD | DISTANCE_IN. The optional parameters *salp1* and *calp1* should not be supplied; they are part of the private interface. """ from geographiclib.geodesic import Geodesic self.a = geod.a """The equatorial radius in meters (readonly)""" self.f = geod.f """The flattening (readonly)""" self._b = geod._b self._c2 = geod._c2 self._f1 = geod._f1 self.caps = (caps | Geodesic.LATITUDE | Geodesic.AZIMUTH | Geodesic.LONG_UNROLL) """the capabilities (readonly)""" # Guard against underflow in salp0 self.lat1 = Math.LatFix(lat1) """the latitude of the first point in degrees (readonly)""" self.lon1 = lon1 """the longitude of the first point in degrees (readonly)""" if Math.isnan(salp1) or Math.isnan(calp1): self.azi1 = Math.AngNormalize(azi1) self.salp1, self.calp1 = Math.sincosd(Math.AngRound(azi1)) else: self.azi1 = azi1 """the azimuth at the first point in degrees (readonly)""" self.salp1 = salp1 """the sine of the azimuth at the first point (readonly)""" self.calp1 = calp1 """the cosine of the azimuth at the first point (readonly)""" # real cbet1, sbet1 sbet1, cbet1 = Math.sincosd(Math.AngRound(lat1)) sbet1 *= self._f1 # Ensure cbet1 = +epsilon at poles sbet1, cbet1 = Math.norm(sbet1, cbet1) cbet1 = max(Geodesic.tiny_, cbet1) self._dn1 = math.sqrt(1 + geod._ep2 * Math.sq(sbet1)) # Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), self._salp0 = self.salp1 * cbet1 # alp0 in [0, pi/2 - |bet1|] # Alt: calp0 = hypot(sbet1, calp1 * cbet1). The following # is slightly better (consider the case salp1 = 0). self._calp0 = math.hypot(self.calp1, self.salp1 * sbet1) # Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1). # sig = 0 is nearest northward crossing of equator. # With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line). # With bet1 = pi/2, alp1 = -pi, sig1 = pi/2 # With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2 # Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1). # With alp0 in (0, pi/2], quadrants for sig and omg coincide. # No atan2(0,0) ambiguity at poles since cbet1 = +epsilon. # With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi. self._ssig1 = sbet1 self._somg1 = self._salp0 * sbet1 self._csig1 = self._comg1 = (cbet1 * self.calp1 if sbet1 != 0 or self.calp1 != 0 else 1) # sig1 in (-pi, pi] self._ssig1, self._csig1 = Math.norm(self._ssig1, self._csig1) # No need to normalize # self._somg1, self._comg1 = Math.norm(self._somg1, self._comg1) self._k2 = Math.sq(self._calp0) * geod._ep2 eps = self._k2 / (2 * (1 + math.sqrt(1 + self._k2)) + self._k2) if self.caps & Geodesic.CAP_C1: self._A1m1 = Geodesic._A1m1f(eps) self._C1a = list(range(Geodesic.nC1_ + 1)) Geodesic._C1f(eps, self._C1a) self._B11 = Geodesic._SinCosSeries(True, self._ssig1, self._csig1, self._C1a) s = math.sin(self._B11) c = math.cos(self._B11) # tau1 = sig1 + B11 self._stau1 = self._ssig1 * c + self._csig1 * s self._ctau1 = self._csig1 * c - self._ssig1 * s # Not necessary because C1pa reverts C1a # _B11 = -_SinCosSeries(true, _stau1, _ctau1, _C1pa) if self.caps & Geodesic.CAP_C1p: self._C1pa = list(range(Geodesic.nC1p_ + 1)) Geodesic._C1pf(eps, self._C1pa) if self.caps & Geodesic.CAP_C2: self._A2m1 = Geodesic._A2m1f(eps) self._C2a = list(range(Geodesic.nC2_ + 1)) Geodesic._C2f(eps, self._C2a) self._B21 = Geodesic._SinCosSeries(True, self._ssig1, self._csig1, self._C2a) if self.caps & Geodesic.CAP_C3: self._C3a = list(range(Geodesic.nC3_)) geod._C3f(eps, self._C3a) self._A3c = -self.f * self._salp0 * geod._A3f(eps) self._B31 = Geodesic._SinCosSeries(True, self._ssig1, self._csig1, self._C3a) if self.caps & Geodesic.CAP_C4: self._C4a = list(range(Geodesic.nC4_)) geod._C4f(eps, self._C4a) # Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0) self._A4 = Math.sq(self.a) * self._calp0 * self._salp0 * geod._e2 self._B41 = Geodesic._SinCosSeries(False, self._ssig1, self._csig1, self._C4a) self.s13 = Math.nan """the distance between point 1 and point 3 in meters (readonly)""" self.a13 = Math.nan """the arc length between point 1 and point 3 in degrees (readonly)"""