def reverse(self, xyz, y=None, z=None, M=False): '''Convert from local cartesian C{(x, y, z)} to geodetic C{(lat, lon, height)}. @arg xyz: Either an L{Ecef9Tuple}, an C{(x, y, z)} 3-tuple or C{scalar} local cartesian C{x} coordinate in C{meter}. @kwarg y: Local cartesian C{y} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{z}}. @kwarg z: Local cartesian C{z} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{y}}. @kwarg M: Optionally, return the rotation L{EcefMatrix} (C{bool}). @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with geodetic coordinates C{(lat, lon, height)} for the given geocentric ones C{(x, y, z)}, case C{C}, optional L{EcefMatrix} C{M} and C{datum} if available. @raise EcefError: If B{C{xyz}} not L{Ecef9Tuple} or C{scalar} C{x} or B{C{y}} and/or B{C{z}} not C{scalar} for C{scalar} B{C{xyz}}. @see: Note at method L{EcefKarney.reverse}. ''' xyz_n = _xyzn4(xyz, y, z, Error=EcefError) x, y, z = self.M.unrotate(xyz_n[:3], *self._t0[:3]) # .x, .y, .z t = self.ecef.reverse(x, y, z, M=M) m = self.M.multiply(t.M) if M else None r = Ecef9Tuple(x, y, z, t.lat, t.lon, t.height, t.C, m, self.ecef.datum) return self._xnamed(r, xyz_n[3])
def reverse(self, xyz, y=None, z=None, **no_M): # PYCHOK unused M '''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)} using I{Rey-Jer You}'s transformation. @arg xyz: Either an L{Ecef9Tuple}, an C{(x, y, z)} 3-tuple or C{scalar} ECEF C{x} coordinate in C{meter}. @kwarg y: ECEF C{y} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{z}}. @kwarg z: ECEF C{z} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{y}}. @kwarg no_M: Rotation matrix C{M} not available. @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with geodetic coordinates C{(lat, lon, height)} for the given geocentric ones C{(x, y, z)}, case C{C} 1, L{EcefMatrix} C{M} always C{None} and C{datum} if available. @raise EcefError: If B{C{xyz}} not L{Ecef9Tuple} or C{scalar} C{x} or B{C{y}} and/or B{C{z}} not C{scalar} for C{scalar} B{C{xyz}}. ''' x, y, z, name = _xyzn4(xyz, y, z, Error=EcefError) x2, y2, z2 = x**2, y**2, z**2 r2 = fsum_(x2, y2, z2) # = hypot3(x2, y2, z2)**2 E = self.ellipsoid e = sqrt(E.a2 - E.b2) e2 = e**2 u = sqrt(fsum_(r2, -e2, hypot(r2 - e2, 2 * e * z)) / 2) p = hypot(u, e) q = hypot(x, y) B = atan2(p * z, u * q) # beta0 = atan(p / u * z / q) sB, cB = sincos2(B) p *= E.a B += fsum_(u * E.b, -p, e2) * sB / (p / cB - e2 * cB) sB, cB = sincos2(B) h = hypot(z - E.b * sB, q - E.a * cB) if fsum_(x2, y2, z2 * E.a_b**2) < E.a2: h = -h # inside ellipsoid r = Ecef9Tuple( x, y, z, degrees(atan2(E.a * sB, E.b * cB)), # atan(E.a_b * tan(B)) degrees(atan2(y, x)), h, 1, # C=1 None, # M=None self.datum) return self._xnamed(r, name)
def reverse(self, xyz, y=None, z=None, **no_M): # PYCHOK unused M '''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)} transcribed from I{Chris Veness}' U{JavaScript <https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html>}. Uses B. R. Bowring’s formulation for μm precision in concise form: U{'The accuracy of geodetic latitude and height equations' <https://www.ResearchGate.net/publication/ 233668213_The_Accuracy_of_Geodetic_Latitude_and_Height_Equations>}, Survey Review, Vol 28, 218, Oct 1985. @arg xyz: Either an L{Ecef9Tuple}, an C{(x, y, z)} 3-tuple or C{scalar} ECEF C{x} coordinate in C{meter}. @kwarg y: ECEF C{y} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{z}}. @kwarg z: ECEF C{z} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{y}}. @kwarg no_M: Rotation matrix C{M} not available. @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with geodetic coordinates C{(lat, lon, height)} for the given geocentric ones C{(x, y, z)}, case C{C}, L{EcefMatrix} C{M} always C{None} and C{datum} if available. @raise EcefError: If B{C{xyz}} not L{Ecef9Tuple} or C{scalar} C{x} or B{C{y}} and/or B{C{z}} not C{scalar} for C{scalar} B{C{xyz}}. @see: Ralph M. Toms U{'An Efficient Algorithm for Geocentric to Geodetic Coordinate Conversion'<https://www.OSTI.gov/scitech/biblio/110235>}, Sept 1995 and U{'An Improved Algorithm for Geocentric to Geodetic Coordinate Conversion'<https://www.OSTI.gov/scitech/servlets/purl/231228>}, Apr 1996, both from Lawrence Livermore National Laboratory (LLNL). ''' x, y, z, name = _xyzn4(xyz, y, z, Error=EcefError) E = self.ellipsoid p = hypot(x, y) # distance from minor axis r = hypot(p, z) # polar radius if min(p, r) > EPS: # parametric latitude (Bowring eqn 17, replaced) t = (E.b * z) / (E.a * p) * (1 + E.e22 * E.b / r) c = 1 / hypot1(t) s = t * c # geodetic latitude (Bowring eqn 18) a = atan2(z + E.e22 * E.b * s**3, p - E.e2 * E.a * c**3) b = atan2(y, x) # ... and longitude # height above ellipsoid (Bowring eqn 7) sa, ca = sincos2(a) # r = E.a / E.e2s(sa) # length of normal terminated by minor axis # h = p * ca + z * sa - (E.a * E.a / r) h = fsum_(p * ca, z * sa, -E.a * E.e2s(sa)) C, lat, lon = 1, degrees90(a), degrees180(b) # see <https://GIS.StackExchange.com/questions/28446> elif p > EPS: # lat arbitrarily zero C, lat, lon, h = 2, 0.0, degrees180(atan2(y, x)), p - E.a else: # polar lat, lon arbitrarily zero C, lat, lon, h = 3, copysign(90.0, z), 0.0, abs(z) - E.b r = Ecef9Tuple(x, y, z, lat, lon, h, C, None, self.datum) return self._xnamed(r, name)
def reverse(self, xyz, y=None, z=None, M=False): '''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)}. @arg xyz: Either an L{Ecef9Tuple}, an C{(x, y, z)} 3-tuple or C{scalar} ECEF C{x} coordinate in C{meter}. @kwarg y: ECEF C{y} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{z}}. @kwarg z: ECEF C{z} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{y}}. @kwarg M: Optionally, return the rotation L{EcefMatrix} (C{bool}). @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with geodetic coordinates C{(lat, lon, height)} for the given geocentric ones C{(x, y, z)}, case C{C}, optional L{EcefMatrix} C{M} and C{datum} if available. @raise EcefError: If B{C{xyz}} not L{Ecef9Tuple} or C{scalar} C{x} or B{C{y}} and/or B{C{z}} not C{scalar} for C{scalar} B{C{xyz}}. @note: In general, there are multiple solutions and the result which minimizes C{height} is returned, i.e., C{(lat, lon)} corresponds to the closest point on the ellipsoid. If there are still multiple solutions with different latitudes (applies only if C{z} = 0), then the solution with C{lat} > 0 is returned. If there are still multiple solutions with different longitudes (applies only if C{x} = C{y} = 0) then C{lon} = 0 is returned. The returned C{height} value is not below M{−E.a * (1 − E.e2) / sqrt(1 − E.e2 * sin(lat)**2)}. The returned C{lon} is in the range [−180°, 180°]. Like C{forward} above, M{v1 = Transpose(M) ⋅ v0}. ''' x, y, z, name = _xyzn4(xyz, y, z, Error=EcefError) sb, cb, d = _sch3(y, x) h = hypot(d, z) # distance to earth center if h > self._hmax: # PYCHOK no cover # We are really far away (> 12M light years). Treat the earth # as a point and h, above, is an acceptable approximation to the # height. This avoids overflow, e.g., in the computation of d # below. It's possible that h has overflowed to INF, that's OK. # Treat finite x, y, but r overflows to +INF by scaling by 2. sb, cb, r = _sch3(y / 2, x / 2) sa, ca, _ = _sch3(z / 2, r) C = 1 elif self.e4a: # Treat prolate spheroids by swapping R and Z here and by switching # the arguments to phi = atan2(...) at the end. p = (d / self.a)**2 q = self.e2m * (z / self.a)**2 if self.f < 0: p, q = q, p r = p + q - self.e4a e = self.e4a * q if e or r > 0: # Avoid possible division by zero when r = 0 by multiplying # equations for s and t by r^3 and r, resp. s = e * p / 4 # s = r^3 * s u = r = r / 6 r2 = r**2 r3 = r * r2 t3 = s + r3 disc = s * (r3 + t3) if disc < 0: # t is complex, but the way u is defined the result is real. # There are three possible cube roots. We choose the root # which avoids cancellation. Note, disc < 0 implies r < 0. u += 2 * r * cos(atan2(sqrt(-disc), -t3) / 3) else: # Pick the sign on the sqrt to maximize abs(T3). This # minimizes loss of precision due to cancellation. The # result is unchanged because of the way the T is used # in definition of u. if disc > 0: t3 += copysign(sqrt(disc), t3) # t3 = (r * t)^3 # N.B. cbrt always returns the real root, cbrt(-8) = -2. t = cbrt(t3) # t = r * t # t can be zero; but then r2 / t -> 0. if t: u += t + r2 / t v = sqrt(u**2 + e) # guaranteed positive # Avoid loss of accuracy when u < 0. Underflow doesn't occur in # E.e4 * q / (v - u) because u ~ e^4 when q is small and u < 0. uv = (e / (v - u)) if u < 0 else (u + v) # u+v, guaranteed positive # Need to guard against w going negative due to roundoff in uv - q. w = max(0.0, self.e2a * (uv - q) / (2 * v)) # Rearrange expression for k to avoid loss of accuracy due to # subtraction. Division by 0 not possible because uv > 0, w >= 0. k1 = k2 = uv / (sqrt(uv + w**2) + w) if self.f < 0: k1 -= self.e2 else: k2 += self.e2 sa, ca, h = _sch3(z / k1, d / k2) h *= k1 - self.e2m C = 2 else: # e = self.e4a * q == 0 and r <= 0 # This leads to k = 0 (oblate, equatorial plane) and k + E.e^2 = 0 # (prolate, rotation axis) and the generation of 0/0 in the general # formulas for phi and h, using the general formula and division # by 0 in formula for h. Handle this case by taking the limits: # f > 0: z -> 0, k -> E.e2 * sqrt(q) / sqrt(E.e4 - p) # f < 0: r -> 0, k + E.e2 -> -E.e2 * sqrt(q) / sqrt(E.e4 - p) q = self.e4a - p if self.f < 0: p, q, e = q, p, -1 else: e = -self.e2m sa, ca, h = _sch3(sqrt(q / self.e2m), sqrt(p)) h *= self.a * e / self.e2a if z < 0: sa = -sa # for tiny negative z, not for prolate C = 3 else: # self.e4a == 0 # Treat the spherical case. Dealing with underflow in the general # case with E.e2 = 0 is difficult. Origin maps to North pole, same # as with ellipsoid. sa, ca, _ = _sch3(z if h else 1, d) h -= self.a C = 4 r = Ecef9Tuple(x, y, z, degrees(atan2(sa, ca)), degrees(atan2(sb, cb)), h, C, self._Matrix(sa, ca, sb, cb) if M else None, self.datum) return self._xnamed(r, name)