def InverseLine(self, lat1, lon1, lat2, lon2, caps = GeodesicCapability.STANDARD | GeodesicCapability.DISTANCE_IN): """Define a GeodesicLine object in terms of the invese geodesic problem :param lat1: latitude of the first point in degrees :param lon1: longitude of the first point in degrees :param lat2: latitude of the second point in degrees :param lon2: longitude of the second point in degrees :param caps: the :ref:`capabilities <outmask>` :return: a :class:`~geographiclib.geodesicline.GeodesicLine` This function sets point 3 of the GeodesicLine to correspond to point 2 of the inverse geodesic problem. The default value of *caps* is STANDARD | DISTANCE_IN, allowing direct geodesic problem to be solved. """ from geographiclib.geodesicline import GeodesicLine a12, _, salp1, calp1, _, _, _, _, _, _ = self._GenInverse( lat1, lon1, lat2, lon2, 0) azi1 = Math.atan2d(salp1, calp1) if caps & (Geodesic.OUT_MASK & Geodesic.DISTANCE_IN): caps |= Geodesic.DISTANCE line = GeodesicLine(self, lat1, lon1, azi1, caps, salp1, calp1) line.SetArc(a12) return line
def Inverse(self, lat1, lon1, lat2, lon2, outmask = GeodesicCapability.STANDARD): """Solve the inverse geodesic problem :param lat1: latitude of the first point in degrees :param lon1: longitude of the first point in degrees :param lat2: latitude of the second point in degrees :param lon2: longitude of the second point in degrees :param outmask: the :ref:`output mask <outmask>` :return: a :ref:`dict` Compute geodesic between (*lat1*, *lon1*) and (*lat2*, *lon2*). The default value of *outmask* is STANDARD, i.e., the *lat1*, *lon1*, *azi1*, *lat2*, *lon2*, *azi2*, *s12*, *a12* entries are returned. """ a12, s12, salp1,calp1, salp2,calp2, m12, M12, M21, S12 = self._GenInverse( lat1, lon1, lat2, lon2, outmask) outmask &= Geodesic.OUT_MASK if outmask & Geodesic.LONG_UNROLL: lon12, e = Math.AngDiff(lon1, lon2) lon2 = (lon1 + lon12) + e else: lon2 = Math.AngNormalize(lon2) result = {'lat1': Math.LatFix(lat1), 'lon1': lon1 if outmask & Geodesic.LONG_UNROLL else Math.AngNormalize(lon1), 'lat2': Math.LatFix(lat2), 'lon2': lon2} result['a12'] = a12 if outmask & Geodesic.DISTANCE: result['s12'] = s12 if outmask & Geodesic.AZIMUTH: result['azi1'] = Math.atan2d(salp1, calp1) result['azi2'] = Math.atan2d(salp2, calp2) if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12 if outmask & Geodesic.GEODESICSCALE: result['M12'] = M12; result['M21'] = M21 if outmask & Geodesic.AREA: result['S12'] = S12 return result
def _GenPosition(self, arcmode, s12_a12, outmask): """Private: General solution of position along geodesic""" from geographiclib.geodesic import Geodesic a12 = lat2 = lon2 = azi2 = s12 = m12 = M12 = M21 = S12 = Math.nan outmask &= self.caps & Geodesic.OUT_MASK if not (arcmode or (self.caps & (Geodesic.OUT_MASK & Geodesic.DISTANCE_IN))): # Uninitialized or impossible distance calculation requested return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 # Avoid warning about uninitialized B12. B12 = 0.0; AB1 = 0.0 if arcmode: # Interpret s12_a12 as spherical arc length sig12 = math.radians(s12_a12) ssig12, csig12 = Math.sincosd(s12_a12) else: # Interpret s12_a12 as distance tau12 = s12_a12 / (self._b * (1 + self._A1m1)) s = math.sin(tau12); c = math.cos(tau12) # tau2 = tau1 + tau12 B12 = - Geodesic._SinCosSeries(True, self._stau1 * c + self._ctau1 * s, self._ctau1 * c - self._stau1 * s, self._C1pa) sig12 = tau12 - (B12 - self._B11) ssig12 = math.sin(sig12); csig12 = math.cos(sig12) if abs(self.f) > 0.01: # Reverted distance series is inaccurate for |f| > 1/100, so correct # sig12 with 1 Newton iteration. The following table shows the # approximate maximum error for a = WGS_a() and various f relative to # GeodesicExact. # erri = the error in the inverse solution (nm) # errd = the error in the direct solution (series only) (nm) # errda = the error in the direct solution (series + 1 Newton) (nm) # # f erri errd errda # -1/5 12e6 1.2e9 69e6 # -1/10 123e3 12e6 765e3 # -1/20 1110 108e3 7155 # -1/50 18.63 200.9 27.12 # -1/100 18.63 23.78 23.37 # -1/150 18.63 21.05 20.26 # 1/150 22.35 24.73 25.83 # 1/100 22.35 25.03 25.31 # 1/50 29.80 231.9 30.44 # 1/20 5376 146e3 10e3 # 1/10 829e3 22e6 1.5e6 # 1/5 157e6 3.8e9 280e6 ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12 csig2 = self._csig1 * csig12 - self._ssig1 * ssig12 B12 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C1a) serr = ((1 + self._A1m1) * (sig12 + (B12 - self._B11)) - s12_a12 / self._b) sig12 = sig12 - serr / math.sqrt(1 + self._k2 * Math.sq(ssig2)) ssig12 = math.sin(sig12); csig12 = math.cos(sig12) # Update B12 below # real omg12, lam12, lon12 # real ssig2, csig2, sbet2, cbet2, somg2, comg2, salp2, calp2 # sig2 = sig1 + sig12 ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12 csig2 = self._csig1 * csig12 - self._ssig1 * ssig12 dn2 = math.sqrt(1 + self._k2 * Math.sq(ssig2)) if outmask & ( Geodesic.DISTANCE | Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE): if arcmode or abs(self.f) > 0.01: B12 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C1a) AB1 = (1 + self._A1m1) * (B12 - self._B11) # sin(bet2) = cos(alp0) * sin(sig2) sbet2 = self._calp0 * ssig2 # Alt: cbet2 = hypot(csig2, salp0 * ssig2) cbet2 = math.hypot(self._salp0, self._calp0 * csig2) if cbet2 == 0: # I.e., salp0 = 0, csig2 = 0. Break the degeneracy in this case cbet2 = csig2 = Geodesic.tiny_ # tan(alp0) = cos(sig2)*tan(alp2) salp2 = self._salp0; calp2 = self._calp0 * csig2 # No need to normalize if outmask & Geodesic.DISTANCE: s12 = self._b * ((1 + self._A1m1) * sig12 + AB1) if arcmode else s12_a12 if outmask & Geodesic.LONGITUDE: # tan(omg2) = sin(alp0) * tan(sig2) somg2 = self._salp0 * ssig2; comg2 = csig2 # No need to normalize E = Math.copysign(1, self._salp0) # East or west going? # omg12 = omg2 - omg1 omg12 = (E * (sig12 - (math.atan2( ssig2, csig2) - math.atan2( self._ssig1, self._csig1)) + (math.atan2(E * somg2, comg2) - math.atan2(E * self._somg1, self._comg1))) if outmask & Geodesic.LONG_UNROLL else math.atan2(somg2 * self._comg1 - comg2 * self._somg1, comg2 * self._comg1 + somg2 * self._somg1)) lam12 = omg12 + self._A3c * ( sig12 + (Geodesic._SinCosSeries(True, ssig2, csig2, self._C3a) - self._B31)) lon12 = math.degrees(lam12) lon2 = (self.lon1 + lon12 if outmask & Geodesic.LONG_UNROLL else Math.AngNormalize(Math.AngNormalize(self.lon1) + Math.AngNormalize(lon12))) if outmask & Geodesic.LATITUDE: lat2 = Math.atan2d(sbet2, self._f1 * cbet2) if outmask & Geodesic.AZIMUTH: # minus signs give range [-180, 180). 0- converts -0 to +0. azi2 = Math.atan2d(salp2, calp2) if outmask & (Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE): B22 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C2a) AB2 = (1 + self._A2m1) * (B22 - self._B21) J12 = (self._A1m1 - self._A2m1) * sig12 + (AB1 - AB2) if outmask & Geodesic.REDUCEDLENGTH: # Add parens around (_csig1 * ssig2) and (_ssig1 * csig2) to ensure # accurate cancellation in the case of coincident points. m12 = self._b * (( dn2 * (self._csig1 * ssig2) - self._dn1 * (self._ssig1 * csig2)) - self._csig1 * csig2 * J12) if outmask & Geodesic.GEODESICSCALE: t = (self._k2 * (ssig2 - self._ssig1) * (ssig2 + self._ssig1) / (self._dn1 + dn2)) M12 = csig12 + (t * ssig2 - csig2 * J12) * self._ssig1 / self._dn1 M21 = csig12 - (t * self._ssig1 - self._csig1 * J12) * ssig2 / dn2 if outmask & Geodesic.AREA: B42 = Geodesic._SinCosSeries(False, ssig2, csig2, self._C4a) # real salp12, calp12 if self._calp0 == 0 or self._salp0 == 0: # alp12 = alp2 - alp1, used in atan2 so no need to normalize salp12 = salp2 * self.calp1 - calp2 * self.salp1 calp12 = calp2 * self.calp1 + salp2 * self.salp1 else: # tan(alp) = tan(alp0) * sec(sig) # tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1) # = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2) # If csig12 > 0, write # csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) # else # csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1 # No need to normalize salp12 = self._calp0 * self._salp0 * ( self._csig1 * (1 - csig12) + ssig12 * self._ssig1 if csig12 <= 0 else ssig12 * (self._csig1 * ssig12 / (1 + csig12) + self._ssig1)) calp12 = (Math.sq(self._salp0) + Math.sq(self._calp0) * self._csig1 * csig2) S12 = (self._c2 * math.atan2(salp12, calp12) + self._A4 * (B42 - self._B41)) a12 = s12_a12 if arcmode else math.degrees(sig12) return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12
def GenInverse(self, lat1, lon1, lat2, lon2, outmask): """Private: General version of the inverse problem""" a12 = s12 = azi1 = azi2 = m12 = M12 = M21 = S12 = Math.nan # return vals outmask &= Geodesic.OUT_MASK # Compute longitude difference (AngDiff does this carefully). Result is # in [-180, 180] but -180 is only for west-going geodesics. 180 is for # east-going and meridional geodesics. # If very close to being on the same half-meridian, then make it so. lon12 = Math.AngRound(Math.AngDiff(lon1, lon2)) # Make longitude difference positive. lonsign = 1 if lon12 >= 0 else -1 lon12 *= lonsign # If really close to the equator, treat as on equator. lat1 = Math.AngRound(Math.LatFix(lat1)) lat2 = Math.AngRound(Math.LatFix(lat2)) # Swap points so that point with higher (abs) latitude is point 1 # If one latitude is a nan, then it becomes lat1. swapp = -1 if abs(lat1) < abs(lat2) else 1 if swapp < 0: lonsign *= -1 lat2, lat1 = lat1, lat2 # Make lat1 <= 0 latsign = 1 if lat1 < 0 else -1 lat1 *= latsign lat2 *= latsign # Now we have # # 0 <= lon12 <= 180 # -90 <= lat1 <= 0 # lat1 <= lat2 <= -lat1 # # longsign, swapp, latsign register the transformation to bring the # coordinates to this canonical form. In all cases, 1 means no change was # made. We make these transformations so that there are few cases to # check, e.g., on verifying quadrants in atan2. In addition, this # enforces some symmetries in the results returned. # real phi, sbet1, cbet1, sbet2, cbet2, s12x, m12x sbet1, cbet1 = Math.sincosd(lat1); sbet1 *= self._f1 # Ensure cbet1 = +epsilon at poles sbet1, cbet1 = Math.norm(sbet1, cbet1); cbet1 = max(Geodesic.tiny_, cbet1) sbet2, cbet2 = Math.sincosd(lat2); sbet2 *= self._f1 # Ensure cbet2 = +epsilon at poles sbet2, cbet2 = Math.norm(sbet2, cbet2); cbet2 = max(Geodesic.tiny_, cbet2) # If cbet1 < -sbet1, then cbet2 - cbet1 is a sensitive measure of the # |bet1| - |bet2|. Alternatively (cbet1 >= -sbet1), abs(sbet2) + sbet1 is # a better measure. This logic is used in assigning calp2 in Lambda12. # Sometimes these quantities vanish and in that case we force bet2 = +/- # bet1 exactly. An example where is is necessary is the inverse problem # 48.522876735459 0 -48.52287673545898293 179.599720456223079643 # which failed with Visual Studio 10 (Release and Debug) if cbet1 < -sbet1: if cbet2 == cbet1: sbet2 = sbet1 if sbet2 < 0 else -sbet1 else: if abs(sbet2) == -sbet1: cbet2 = cbet1 dn1 = math.sqrt(1 + self._ep2 * Math.sq(sbet1)) dn2 = math.sqrt(1 + self._ep2 * Math.sq(sbet2)) lam12 = math.radians(lon12) slam12, clam12 = Math.sincosd(lon12) # real a12, sig12, calp1, salp1, calp2, salp2 # index zero elements of these arrays are unused C1a = list(range(Geodesic.nC1_ + 1)) C2a = list(range(Geodesic.nC2_ + 1)) C3a = list(range(Geodesic.nC3_)) meridian = lat1 == -90 or slam12 == 0 if meridian: # Endpoints are on a single full meridian, so the geodesic might lie on # a meridian. calp1 = clam12; salp1 = slam12 # Head to the target longitude calp2 = 1; salp2 = 0 # At the target we're heading north # tan(bet) = tan(sig) * cos(alp) ssig1 = sbet1; csig1 = calp1 * cbet1 ssig2 = sbet2; csig2 = calp2 * cbet2 # sig12 = sig2 - sig1 sig12 = math.atan2(max(0.0, csig1 * ssig2 - ssig1 * csig2), csig1 * csig2 + ssig1 * ssig2) s12x, m12x, dummy, M12, M21 = self.Lengths( self._n, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, outmask | Geodesic.DISTANCE | Geodesic.REDUCEDLENGTH, C1a, C2a) # Add the check for sig12 since zero length geodesics might yield m12 < # 0. Test case was # # echo 20.001 0 20.001 0 | GeodSolve -i # # In fact, we will have sig12 > pi/2 for meridional geodesic which is # not a shortest path. if sig12 < 1 or m12x >= 0: if sig12 < 3 * Geodesic.tiny_: sig12 = m12x = s12x = 0 m12x *= self._b s12x *= self._b a12 = math.degrees(sig12) else: # m12 < 0, i.e., prolate and too close to anti-podal meridian = False # end if meridian: #real omg12 if (not meridian and sbet1 == 0 and # and sbet2 == 0 # Mimic the way Lambda12 works with calp1 = 0 (self._f <= 0 or lam12 <= math.pi - self._f * math.pi)): # Geodesic runs along equator calp1 = calp2 = 0; salp1 = salp2 = 1 s12x = self._a * lam12 sig12 = omg12 = lam12 / self._f1 m12x = self._b * math.sin(sig12) if outmask & Geodesic.GEODESICSCALE: M12 = M21 = math.cos(sig12) a12 = lon12 / self._f1 elif not meridian: # Now point1 and point2 belong within a hemisphere bounded by a # meridian and geodesic is neither meridional or equatorial. # Figure a starting point for Newton's method sig12, salp1, calp1, salp2, calp2, dnm = self.InverseStart( sbet1, cbet1, dn1, sbet2, cbet2, dn2, lam12, C1a, C2a) if sig12 >= 0: # Short lines (InverseStart sets salp2, calp2, dnm) s12x = sig12 * self._b * dnm m12x = (Math.sq(dnm) * self._b * math.sin(sig12 / dnm)) if outmask & Geodesic.GEODESICSCALE: M12 = M21 = math.cos(sig12 / dnm) a12 = math.degrees(sig12) omg12 = lam12 / (self._f1 * dnm) else: # Newton's method. This is a straightforward solution of f(alp1) = # lambda12(alp1) - lam12 = 0 with one wrinkle. f(alp) has exactly one # root in the interval (0, pi) and its derivative is positive at the # root. Thus f(alp) is positive for alp > alp1 and negative for alp < # alp1. During the course of the iteration, a range (alp1a, alp1b) is # maintained which brackets the root and with each evaluation of f(alp) # the range is shrunk if possible. Newton's method is restarted # whenever the derivative of f is negative (because the new value of # alp1 is then further from the solution) or if the new estimate of # alp1 lies outside (0,pi); in this case, the new starting guess is # taken to be (alp1a + alp1b) / 2. # real ssig1, csig1, ssig2, csig2, eps numit = 0 tripn = tripb = False # Bracketing range salp1a = Geodesic.tiny_; calp1a = 1 salp1b = Geodesic.tiny_; calp1b = -1 while numit < Geodesic.maxit2_: # the WGS84 test set: mean = 1.47, sd = 1.25, max = 16 # WGS84 and random input: mean = 2.85, sd = 0.60 (nlam12, salp2, calp2, sig12, ssig1, csig1, ssig2, csig2, eps, omg12, dv) = self.Lambda12( sbet1, cbet1, dn1, sbet2, cbet2, dn2, salp1, calp1, numit < Geodesic.maxit1_, C1a, C2a, C3a) v = nlam12 - lam12 # 2 * tol0 is approximately 1 ulp for a number in [0, pi]. # Reversed test to allow escape with NaNs if tripb or not (abs(v) >= (8 if tripn else 2) * Geodesic.tol0_): break # Update bracketing values if v > 0 and (numit > Geodesic.maxit1_ or calp1/salp1 > calp1b/salp1b): salp1b = salp1; calp1b = calp1 elif v < 0 and (numit > Geodesic.maxit1_ or calp1/salp1 < calp1a/salp1a): salp1a = salp1; calp1a = calp1 numit += 1 if numit < Geodesic.maxit1_ and dv > 0: dalp1 = -v/dv sdalp1 = math.sin(dalp1); cdalp1 = math.cos(dalp1) nsalp1 = salp1 * cdalp1 + calp1 * sdalp1 if nsalp1 > 0 and abs(dalp1) < math.pi: calp1 = calp1 * cdalp1 - salp1 * sdalp1 salp1 = nsalp1 salp1, calp1 = Math.norm(salp1, calp1) # In some regimes we don't get quadratic convergence because # slope -> 0. So use convergence conditions based on epsilon # instead of sqrt(epsilon). tripn = abs(v) <= 16 * Geodesic.tol0_ continue # Either dv was not postive or updated value was outside legal range. # Use the midpoint of the bracket as the next estimate. This # mechanism is not needed for the WGS84 ellipsoid, but it does catch # problems with more eccentric ellipsoids. Its efficacy is such for # the WGS84 test set with the starting guess set to alp1 = 90deg: # the WGS84 test set: mean = 5.21, sd = 3.93, max = 24 # WGS84 and random input: mean = 4.74, sd = 0.99 salp1 = (salp1a + salp1b)/2 calp1 = (calp1a + calp1b)/2 salp1, calp1 = Math.norm(salp1, calp1) tripn = False tripb = (abs(salp1a - salp1) + (calp1a - calp1) < Geodesic.tolb_ or abs(salp1 - salp1b) + (calp1 - calp1b) < Geodesic.tolb_) lengthmask = (outmask | (Geodesic.DISTANCE if (outmask & (Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE)) else Geodesic.EMPTY)) s12x, m12x, dummy, M12, M21 = self.Lengths( eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, lengthmask, C1a, C2a) m12x *= self._b s12x *= self._b a12 = math.degrees(sig12) omg12 = lam12 - omg12 # end elif not meridian if outmask & Geodesic.DISTANCE: s12 = 0 + s12x # Convert -0 to 0 if outmask & Geodesic.REDUCEDLENGTH: m12 = 0 + m12x # Convert -0 to 0 if outmask & Geodesic.AREA: # From Lambda12: sin(alp1) * cos(bet1) = sin(alp0) salp0 = salp1 * cbet1 calp0 = math.hypot(calp1, salp1 * sbet1) # calp0 > 0 # real alp12 if calp0 != 0 and salp0 != 0: # From Lambda12: tan(bet) = tan(sig) * cos(alp) ssig1 = sbet1; csig1 = calp1 * cbet1 ssig2 = sbet2; csig2 = calp2 * cbet2 k2 = Math.sq(calp0) * self._ep2 eps = k2 / (2 * (1 + math.sqrt(1 + k2)) + k2) # Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0). A4 = Math.sq(self._a) * calp0 * salp0 * self._e2 ssig1, csig1 = Math.norm(ssig1, csig1) ssig2, csig2 = Math.norm(ssig2, csig2) C4a = list(range(Geodesic.nC4_)) self.C4f(eps, C4a) B41 = Geodesic.SinCosSeries(False, ssig1, csig1, C4a) B42 = Geodesic.SinCosSeries(False, ssig2, csig2, C4a) S12 = A4 * (B42 - B41) else: # Avoid problems with indeterminate sig1, sig2 on equator S12 = 0 if (not meridian and omg12 < 0.75 * math.pi and # Long difference too big sbet2 - sbet1 < 1.75): # Lat difference too big # Use tan(Gamma/2) = tan(omg12/2) # * (tan(bet1/2)+tan(bet2/2))/(1+tan(bet1/2)*tan(bet2/2)) # with tan(x/2) = sin(x)/(1+cos(x)) somg12 = math.sin(omg12); domg12 = 1 + math.cos(omg12) dbet1 = 1 + cbet1; dbet2 = 1 + cbet2 alp12 = 2 * math.atan2( somg12 * ( sbet1 * dbet2 + sbet2 * dbet1 ), domg12 * ( sbet1 * sbet2 + dbet1 * dbet2 ) ) else: # alp12 = alp2 - alp1, used in atan2 so no need to normalize salp12 = salp2 * calp1 - calp2 * salp1 calp12 = calp2 * calp1 + salp2 * salp1 # The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz # salp12 = -0 and alp12 = -180. However this depends on the sign # being attached to 0 correctly. The following ensures the correct # behavior. if salp12 == 0 and calp12 < 0: salp12 = Geodesic.tiny_ * calp1 calp12 = -1 alp12 = math.atan2(salp12, calp12) S12 += self._c2 * alp12 S12 *= swapp * lonsign * latsign # Convert -0 to 0 S12 += 0 # Convert calp, salp to azimuth accounting for lonsign, swapp, latsign. if swapp < 0: salp2, salp1 = salp1, salp2 calp2, calp1 = calp1, calp2 if outmask & Geodesic.GEODESICSCALE: M21, M12 = M12, M21 salp1 *= swapp * lonsign; calp1 *= swapp * latsign salp2 *= swapp * lonsign; calp2 *= swapp * latsign if outmask & Geodesic.AZIMUTH: # minus signs give range [-180, 180). 0- converts -0 to +0. azi1 = Math.atan2d(salp1, calp1) azi2 = Math.atan2d(salp2, calp2) # Returned value in [0, 180] return a12, s12, azi1, azi2, m12, M12, M21, S12
def _GenPosition(self, arcmode, s12_a12, outmask): """Private: General solution of position along geodesic""" from geographiclib.geodesic import Geodesic a12 = lat2 = lon2 = azi2 = s12 = m12 = M12 = M21 = S12 = Math.nan outmask &= self.caps & Geodesic.OUT_MASK if not (arcmode or (self.caps & (Geodesic.OUT_MASK & Geodesic.DISTANCE_IN))): # Uninitialized or impossible distance calculation requested return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 # Avoid warning about uninitialized B12. B12 = 0.0 AB1 = 0.0 if arcmode: # Interpret s12_a12 as spherical arc length sig12 = math.radians(s12_a12) ssig12, csig12 = Math.sincosd(s12_a12) else: # Interpret s12_a12 as distance tau12 = s12_a12 / (self._b * (1 + self._A1m1)) s = math.sin(tau12) c = math.cos(tau12) # tau2 = tau1 + tau12 B12 = -Geodesic._SinCosSeries( True, self._stau1 * c + self._ctau1 * s, self._ctau1 * c - self._stau1 * s, self._C1pa) sig12 = tau12 - (B12 - self._B11) ssig12 = math.sin(sig12) csig12 = math.cos(sig12) if abs(self.f) > 0.01: # Reverted distance series is inaccurate for |f| > 1/100, so correct # sig12 with 1 Newton iteration. The following table shows the # approximate maximum error for a = WGS_a() and various f relative to # GeodesicExact. # erri = the error in the inverse solution (nm) # errd = the error in the direct solution (series only) (nm) # errda = the error in the direct solution (series + 1 Newton) (nm) # # f erri errd errda # -1/5 12e6 1.2e9 69e6 # -1/10 123e3 12e6 765e3 # -1/20 1110 108e3 7155 # -1/50 18.63 200.9 27.12 # -1/100 18.63 23.78 23.37 # -1/150 18.63 21.05 20.26 # 1/150 22.35 24.73 25.83 # 1/100 22.35 25.03 25.31 # 1/50 29.80 231.9 30.44 # 1/20 5376 146e3 10e3 # 1/10 829e3 22e6 1.5e6 # 1/5 157e6 3.8e9 280e6 ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12 csig2 = self._csig1 * csig12 - self._ssig1 * ssig12 B12 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C1a) serr = ((1 + self._A1m1) * (sig12 + (B12 - self._B11)) - s12_a12 / self._b) sig12 = sig12 - serr / math.sqrt(1 + self._k2 * Math.sq(ssig2)) ssig12 = math.sin(sig12) csig12 = math.cos(sig12) # Update B12 below # real omg12, lam12, lon12 # real ssig2, csig2, sbet2, cbet2, somg2, comg2, salp2, calp2 # sig2 = sig1 + sig12 ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12 csig2 = self._csig1 * csig12 - self._ssig1 * ssig12 dn2 = math.sqrt(1 + self._k2 * Math.sq(ssig2)) if outmask & (Geodesic.DISTANCE | Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE): if arcmode or abs(self.f) > 0.01: B12 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C1a) AB1 = (1 + self._A1m1) * (B12 - self._B11) # sin(bet2) = cos(alp0) * sin(sig2) sbet2 = self._calp0 * ssig2 # Alt: cbet2 = hypot(csig2, salp0 * ssig2) cbet2 = math.hypot(self._salp0, self._calp0 * csig2) if cbet2 == 0: # I.e., salp0 = 0, csig2 = 0. Break the degeneracy in this case cbet2 = csig2 = Geodesic.tiny_ # tan(alp0) = cos(sig2)*tan(alp2) salp2 = self._salp0 calp2 = self._calp0 * csig2 # No need to normalize if outmask & Geodesic.DISTANCE: s12 = self._b * ( (1 + self._A1m1) * sig12 + AB1) if arcmode else s12_a12 if outmask & Geodesic.LONGITUDE: # tan(omg2) = sin(alp0) * tan(sig2) somg2 = self._salp0 * ssig2 comg2 = csig2 # No need to normalize E = Math.copysign(1, self._salp0) # East or west going? # omg12 = omg2 - omg1 omg12 = (E * (sig12 - (math.atan2(ssig2, csig2) - math.atan2(self._ssig1, self._csig1)) + (math.atan2(E * somg2, comg2) - math.atan2(E * self._somg1, self._comg1))) if outmask & Geodesic.LONG_UNROLL else math.atan2( somg2 * self._comg1 - comg2 * self._somg1, comg2 * self._comg1 + somg2 * self._somg1)) lam12 = omg12 + self._A3c * (sig12 + (Geodesic._SinCosSeries( True, ssig2, csig2, self._C3a) - self._B31)) lon12 = math.degrees(lam12) lon2 = (self.lon1 + lon12 if outmask & Geodesic.LONG_UNROLL else Math.AngNormalize( Math.AngNormalize(self.lon1) + Math.AngNormalize(lon12))) if outmask & Geodesic.LATITUDE: lat2 = Math.atan2d(sbet2, self._f1 * cbet2) if outmask & Geodesic.AZIMUTH: # minus signs give range [-180, 180). 0- converts -0 to +0. azi2 = Math.atan2d(salp2, calp2) if outmask & (Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE): B22 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C2a) AB2 = (1 + self._A2m1) * (B22 - self._B21) J12 = (self._A1m1 - self._A2m1) * sig12 + (AB1 - AB2) if outmask & Geodesic.REDUCEDLENGTH: # Add parens around (_csig1 * ssig2) and (_ssig1 * csig2) to ensure # accurate cancellation in the case of coincident points. m12 = self._b * ( (dn2 * (self._csig1 * ssig2) - self._dn1 * (self._ssig1 * csig2)) - self._csig1 * csig2 * J12) if outmask & Geodesic.GEODESICSCALE: t = (self._k2 * (ssig2 - self._ssig1) * (ssig2 + self._ssig1) / (self._dn1 + dn2)) M12 = csig12 + (t * ssig2 - csig2 * J12) * self._ssig1 / self._dn1 M21 = csig12 - (t * self._ssig1 - self._csig1 * J12) * ssig2 / dn2 if outmask & Geodesic.AREA: B42 = Geodesic._SinCosSeries(False, ssig2, csig2, self._C4a) # real salp12, calp12 if self._calp0 == 0 or self._salp0 == 0: # alp12 = alp2 - alp1, used in atan2 so no need to normalize salp12 = salp2 * self.calp1 - calp2 * self.salp1 calp12 = calp2 * self.calp1 + salp2 * self.salp1 else: # tan(alp) = tan(alp0) * sec(sig) # tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1) # = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2) # If csig12 > 0, write # csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) # else # csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1 # No need to normalize salp12 = self._calp0 * self._salp0 * ( self._csig1 * (1 - csig12) + ssig12 * self._ssig1 if csig12 <= 0 else ssig12 * (self._csig1 * ssig12 / (1 + csig12) + self._ssig1)) calp12 = (Math.sq(self._salp0) + Math.sq(self._calp0) * self._csig1 * csig2) S12 = (self._c2 * math.atan2(salp12, calp12) + self._A4 * (B42 - self._B41)) a12 = s12_a12 if arcmode else math.degrees(sig12) return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12