pygeodesy 25.9.9__py2.py3-none-any.whl → 25.11.5__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pygeodesy/triaxials.py CHANGED
@@ -2,13 +2,14 @@
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
4
  u'''Triaxal ellipsoid classes I{ordered} L{Triaxial} and I{unordered} L{Triaxial_} and Jacobi
5
- conformal projections L{JacobiConformal} and L{JacobiConformalSpherical}, transcoded from
6
- I{Charles Karney}'s C++ class U{JacobiConformal<https://GeographicLib.SourceForge.io/C++/doc/
7
- classGeographicLib_1_1JacobiConformal.html#details>} to pure Python and miscellaneous classes
8
- L{BetaOmega2Tuple}, L{BetaOmega3Tuple}, L{Jacobi2Tuple} and L{TriaxialError}.
5
+ conformal projections L{ConformalTriaxial} and L{ConformalSphere}, transcoded from
6
+ I{Charles Karney}'s GeographicLib 2.5.2 C++ class U{JacobiConformal
7
+ <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1JacobiConformal.html#details>}
8
+ to pure Python and miscellaneous classes L{BetaOmega2Tuple}, L{BetaOmega3Tuple}, L{Conformal2Tuple}
9
+ and L{TriaxialError}.
9
10
 
10
11
  Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2024). For more information,
11
- see the U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation.
12
+ see the U{GeographicLib<https://GeographicLib.SourceForge.io>} 2.5.2 documentation.
12
13
 
13
14
  @see: U{Geodesics on a triaxial ellipsoid<https://WikiPedia.org/wiki/Geodesics_on_an_ellipsoid#
14
15
  Geodesics_on_a_triaxial_ellipsoid>} and U{Triaxial coordinate systems and their geometrical
@@ -32,7 +33,7 @@ from __future__ import division as _; del _ # noqa: E702 ;
32
33
 
33
34
  from pygeodesy.basics import _isin, isLatLon, isscalar
34
35
  from pygeodesy.constants import EPS, EPS0, EPS02, EPS4, INT0, PI2, PI_3, PI4, \
35
- _EPS2e4, _SQRT2_2, float0_, isfinite, isnear1, _over, \
36
+ _EPS2e4, float0_, _isfinite, isnear1, _over, _SQRT2_2, \
36
37
  _0_0, _0_5, _1_0, _N_1_0, _64_0, _4_0 # PYCHOK used!
37
38
  from pygeodesy.datums import Datum, _spherical_datum, _WGS84, Ellipsoid, _EWGS84, Fmt
38
39
  # from pygeodesy.ellipsoids import Ellipsoid, _EWGS84 # from .datums
@@ -44,6 +45,7 @@ from pygeodesy.interns import NN, _a_, _b_, _beta_, _c_, _distant_, _DMAIN_, \
44
45
  _finite_, _height_, _inside_, _near_, _negative_, \
45
46
  _not_, _NOTEQUAL_, _null_, _opposite_, _outside_, \
46
47
  _SPACE_, _spherical_, _too_, _x_, _y_
48
+ # from pygeodesy.karney import _norm2 # _MODS
47
49
  # from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS # from .vector3d
48
50
  from pygeodesy.named import _lazyNamedEnumItem as _lazy, _name__, _NamedEnum, \
49
51
  _NamedEnumItem, _Pass
@@ -52,14 +54,14 @@ from pygeodesy.namedTuples import LatLon3Tuple, _NamedTupleTo, Vector3Tuple, \
52
54
  from pygeodesy.props import Property_RO, property_ROver
53
55
  # from pygeodesy.streprs import Fmt # from .datums
54
56
  from pygeodesy.units import Degrees, Float, Height_, Meter, Meter2, Meter3, \
55
- Radians, Radius, Scalar_
57
+ Radians, Radius_, Scalar_
56
58
  from pygeodesy.utily import asin1, atan2, atan2d, km2m, m2km, SinCos2, sincos2d_
57
59
  from pygeodesy.vector3d import _otherV3d, Vector3d, _ALL_LAZY, _MODS
58
60
 
59
61
  from math import fabs, sqrt
60
62
 
61
63
  __all__ = _ALL_LAZY.triaxials
62
- __version__ = '25.05.12'
64
+ __version__ = '25.10.30'
63
65
 
64
66
  _not_ordered_ = _not_('ordered')
65
67
  _omega_ = 'omega'
@@ -68,7 +70,7 @@ _TRIPS = 359 # Eberly 1074?
68
70
 
69
71
  class _NamedTupleToX(_NamedTupleTo): # in .testNamedTuples
70
72
  '''(INTERNAL) Base class for L{BetaOmega2Tuple},
71
- L{BetaOmega3Tuple} and L{Jacobi2Tuple}.
73
+ L{BetaOmega3Tuple} and L{Conformal2Tuple}.
72
74
  '''
73
75
  def _toDegrees(self, name, **toDMS_kwds):
74
76
  '''(INTERNAL) Convert C{self[0:2]} to L{Degrees} or C{toDMS}.
@@ -159,30 +161,30 @@ class BetaOmega3Tuple(_NamedTupleToX):
159
161
  return BetaOmega2Tuple(*self[:2], name=self._name__(name))
160
162
 
161
163
 
162
- class Jacobi2Tuple(_NamedTupleToX):
163
- '''2-Tuple C{(x, y)} with a Jacobi Conformal C{x} and C{y}
164
+ class Conformal2Tuple(_NamedTupleToX):
165
+ '''2-Tuple C{(x, y)} with a I{Jacobi Conformal} C{x} and C{y}
164
166
  projection, both in L{Radians} (or L{Degrees}).
165
167
  '''
166
168
  _Names_ = (_x_, _y_)
167
169
  _Units_ = (_Pass, _Pass)
168
170
 
169
171
  def toDegrees(self, name=NN, **toDMS_kwds):
170
- '''Convert this L{Jacobi2Tuple} to L{Degrees} or C{toDMS}.
172
+ '''Convert this L{Conformal2Tuple} to L{Degrees} or C{toDMS}.
171
173
 
172
174
  @kwarg name: Optional C{B{name}=NN} (C{str}).
173
175
 
174
- @return: L{Jacobi2Tuple}C{(x, y)} with C{x} and C{y} both
176
+ @return: L{Conformal2Tuple}C{(x, y)} with C{x} and C{y} both
175
177
  in L{Degrees} or as L{toDMS} strings provided some
176
178
  B{C{toDMS_kwds}} keyword arguments are specified.
177
179
  '''
178
180
  return self._toDegrees(name, **toDMS_kwds)
179
181
 
180
182
  def toRadians(self, **name):
181
- '''Convert this L{Jacobi2Tuple} to L{Radians}.
183
+ '''Convert this L{Conformal2Tuple} to L{Radians}.
182
184
 
183
185
  @kwarg name: Optional C{B{name}=NN} (C{str}).
184
186
 
185
- @return: L{Jacobi2Tuple}C{(x, y)} with C{x} and C{y} both in L{Radians}.
187
+ @return: L{Conformal2Tuple}C{(x, y)} with C{x} and C{y} both in L{Radians}.
186
188
  '''
187
189
  return self._toRadians(name)
188
190
 
@@ -223,7 +225,7 @@ class Triaxial_(_NamedEnumItem):
223
225
  a = a_triaxial
224
226
  t = a._abc3
225
227
  except AttributeError:
226
- t = Radius(a=a), Radius(b=b), Radius(c=c)
228
+ t = Radius_(a=a), Radius_(b=b), Radius_(c=c)
227
229
  except (TypeError, ValueError) as x:
228
230
  raise TriaxialError(a=a, b=b, c=c, cause=x)
229
231
  if name:
@@ -232,9 +234,9 @@ class Triaxial_(_NamedEnumItem):
232
234
  a, b, c = self._abc3 = t
233
235
  if self._unordered: # == not isinstance(self, Triaxial)
234
236
  s, _, t = sorted(t)
235
- if not (isfinite(t) and s > 0):
237
+ if not (_isfinite(t) and s > 0):
236
238
  raise TriaxialError(a=a, b=b, c=c) # txt=_invalid_
237
- elif not (isfinite(a) and a >= b >= c > 0):
239
+ elif not (_isfinite(a) and a >= b >= c > 0): # see Triaxial2
238
240
  raise TriaxialError(a=a, b=b, c=c, txt=_not_ordered_)
239
241
  elif not (a > c and self._a2c2 > 0 and self.e2ac > 0):
240
242
  raise TriaxialError(a=a, c=c, e2ac=self.e2ac, txt=_spherical_)
@@ -249,6 +251,12 @@ class Triaxial_(_NamedEnumItem):
249
251
  a, _, _ = self._abc3
250
252
  return a
251
253
 
254
+ @Property_RO
255
+ def a2(self):
256
+ '''Get C{a**2}.
257
+ '''
258
+ return self.a**2
259
+
252
260
  @Property_RO
253
261
  def _a2b2(self):
254
262
  '''(INTERNAL) Get C{a**2 - b**2} == E_sub_e**2.
@@ -263,6 +271,13 @@ class Triaxial_(_NamedEnumItem):
263
271
  a, b, _ = self._abc3
264
272
  return (a / b)**2 if a != b else _1_0
265
273
 
274
+ @Property_RO
275
+ def _a2b2c23(self):
276
+ '''(INTERNAL) Get 3-tuple C{(a**2, b**2, c**2)}.
277
+ '''
278
+ a, b, c = self._abc3
279
+ return self.a2, self.b2, self.c2
280
+
266
281
  @Property_RO
267
282
  def _a2c2(self):
268
283
  '''(INTERNAL) Get C{a**2 - c**2} == E_sub_x**2.
@@ -275,12 +290,8 @@ class Triaxial_(_NamedEnumItem):
275
290
  '''Get the surface area (C{meter} I{squared}).
276
291
  '''
277
292
  c, b, a = sorted(self._abc3)
278
- if a > c:
279
- a = Triaxial(a, b, c).area if a > b else \
280
- Ellipsoid(a, b=c).areax # a == b
281
- else: # a == c == b
282
- a = Meter2(area=a**2 * PI4)
283
- return a
293
+ return Triaxial(a, b, c).area if a > c else \
294
+ Meter2(area=self.a2 * PI4) # a == c == b
284
295
 
285
296
  def area_p(self, p=1.6075):
286
297
  '''I{Approximate} the surface area (C{meter} I{squared}).
@@ -305,6 +316,19 @@ class Triaxial_(_NamedEnumItem):
305
316
  _, b, _ = self._abc3
306
317
  return b
307
318
 
319
+ @Property_RO
320
+ def b2(self):
321
+ '''Get C{b**2}.
322
+ '''
323
+ return self.b**2
324
+
325
+ @Property_RO
326
+ def _b2_a2(self):
327
+ '''(INTERNAL) Get C{(b / a)**2}.
328
+ '''
329
+ a, b, _ = self._abc3
330
+ return (b / a)**2 if a != b else _1_0
331
+
308
332
  @Property_RO
309
333
  def _b2c2(self):
310
334
  '''(INTERNAL) Get C{b**2 - c**2} == E_sub_y**2.
@@ -319,6 +343,19 @@ class Triaxial_(_NamedEnumItem):
319
343
  _, _, c = self._abc3
320
344
  return c
321
345
 
346
+ @Property_RO
347
+ def c2(self):
348
+ '''Get C{c**2}.
349
+ '''
350
+ return self.c**2
351
+
352
+ @Property_RO
353
+ def _c2_a2(self):
354
+ '''(INTERNAL) Get C{(c / a)**2}.
355
+ '''
356
+ a, _, c = self._abc3
357
+ return (c / a)**2 if a != c else _1_0
358
+
322
359
  @Property_RO
323
360
  def _c2_b2(self):
324
361
  '''(INTERNAL) Get C{(c / b)**2}.
@@ -330,35 +367,25 @@ class Triaxial_(_NamedEnumItem):
330
367
  def e2ab(self):
331
368
  '''Get the C{ab} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (b/a)**2}.
332
369
  '''
333
- return Float(e2ab=(_1_0 - self._1e2ab) or _0_0)
370
+ return Float(e2ab=(_1_0 - self._b2_a2) or _0_0)
334
371
 
335
- @Property_RO
336
- def _1e2ab(self):
337
- '''(INTERNAL) Get C{1 - e2ab} == C{(b/a)**2}.
338
- '''
339
- a, b, _ = self._abc3
340
- return (b / a)**2 if a != b else _1_0
372
+ # _1e2ab = _b2_a2 # C{1 - e2ab} == C{(b / a)**2}
341
373
 
342
374
  @Property_RO
343
375
  def e2ac(self):
344
376
  '''Get the C{ac} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (c/a)**2}.
345
377
  '''
346
- return Float(e2ac=(_1_0 - self._1e2ac) or _0_0)
378
+ return Float(e2ac=(_1_0 - self._c2_a2) or _0_0)
347
379
 
348
- @Property_RO
349
- def _1e2ac(self):
350
- '''(INTERNAL) Get C{1 - e2ac} == C{(c/a)**2}.
351
- '''
352
- a, _, c = self._abc3
353
- return (c / a)**2 if a != c else _1_0
380
+ # _1e2ac = _c2_a2 # C{1 - e2ac} == C{(c / a)**2}
354
381
 
355
382
  @Property_RO
356
383
  def e2bc(self):
357
384
  '''Get the C{bc} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (c/b)**2}.
358
385
  '''
359
- return Float(e2bc=(_1_0 - self._1e2bc) or _0_0)
386
+ return Float(e2bc=(_1_0 - self._c2_b2) or _0_0)
360
387
 
361
- _1e2bc = _c2_b2 # C{1 - e2bc} == C{(c/b)**2}
388
+ # _1e2bc = _c2_b2 # C{1 - e2bc} == C{(c / b)**2}
362
389
 
363
390
  @property_ROver
364
391
  def _Elliptic(self):
@@ -387,7 +414,7 @@ class Triaxial_(_NamedEnumItem):
387
414
  this triaxial (C{bool}).
388
415
  @kwarg eps: Tolerance for root finding and validation (C{scalar}), use a negative
389
416
  value to skip validation.
390
- @kwarg name: Optional C{B{name}="heigh4"} (C{str}).
417
+ @kwarg name: Optional C{B{name}="height4"} (C{str}).
391
418
 
392
419
  @return: L{Vector4Tuple}C{(x, y, z, h)} with the cartesian coordinates C{x}, C{y}
393
420
  and C{z} of the projection on or the intersection with and with the height
@@ -449,37 +476,78 @@ class Triaxial_(_NamedEnumItem):
449
476
  return float0_(s, c)
450
477
 
451
478
  def normal3d(self, x_xyz, y=None, z=None, length=_1_0):
452
- '''Get a 3-D vector at a cartesian I{on and perpendicular to} this triaxial's surface.
479
+ '''Get a 3-D vector I{on and perpendicular to} this triaxial's surface.
453
480
 
454
481
  @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
455
482
  L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
456
483
  @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
457
484
  otherwise.
458
485
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
459
- @kwarg length: Optional, signed length and in-/outward direction (C{scalar}).
486
+ @kwarg length: Optional, signed length in out-/inward direction (C{scalar}).
460
487
 
461
- @return: A C{Vector3d(x_, y_, z_)} normalized to B{C{length}}, pointing in- or
462
- outward for neg- respectively positive B{C{length}}.
488
+ @return: A C{Vector3d(x_, y_, z_)} normalized to B{C{length}}, pointing out-
489
+ or inward for postive respectively negative B{C{length}}.
463
490
 
464
491
  @raise TriaxialError: Zero length cartesian or vector.
465
492
 
466
- @note: Cartesian C{(B{x}, B{y}, B{z})} I{must be on} this triaxial's surface, use
467
- method L{Triaxial.sideOf} to validate.
493
+ @note: Cartesian C{(B{x}, B{y}, B{z})} I{must be on} this triaxial's surface,
494
+ use method L{Triaxial.sideOf} to validate.
468
495
 
469
496
  @see: Methods L{Triaxial.height4} and L{Triaxial.sideOf}.
470
497
  '''
471
498
  # n = 2 * (x / a2, y / b2, z / c2)
472
499
  # == 2 * (x, y * a2 / b2, z * a2 / c2) / a2 # iff ordered
473
- # == 2 * (x, y / _1e2ab, z / _1e2ac) / a2
474
- # == unit(x, y / _1e2ab, z / _1e2ac).times(length)
500
+ # == 2 * (x, y / _b2_a2, z / _c2_a2) / a2
501
+ # == unit(x, y / _b2_a2, z / _c2_a2).times(length)
475
502
  x, y, z = _otherV3d_(x_xyz, y, z).xyz3
476
- n = Vector3d(x, y / self._1e2ab,
477
- z / self._1e2ac, name__=self.normal3d)
503
+ n = Vector3d(x, y / self._b2_a2,
504
+ z / self._c2_a2, name__=self.normal3d)
478
505
  u = n.length
479
506
  if u < EPS0:
480
507
  raise TriaxialError(x=x_xyz, y=y, z=z, txt=_null_)
481
508
  return n.times(length / u)
482
509
 
510
+ def normal4(self, x_xyz, y=None, z=None, height=0, normal=True):
511
+ '''Compute a cartesian above or below this triaxial's surface.
512
+
513
+ @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian}, L{Ecef9Tuple},
514
+ L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
515
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
516
+ otherwise.
517
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
518
+ @kwarg height: The signed height (C{scalar}, same units as the triaxial axes).
519
+ @kwarg normal: If C{True}, the B{C{height}} is I{perpendicular, plumb} to the
520
+ triaxial's surface, otherwise C{radially} to the center of this
521
+ triaxial (C{bool}).
522
+ @kwarg name: Optional C{B{name}="normal4"} (C{str}).
523
+
524
+ @return: L{Vector4Tuple}C{(x, y, z, h)} with the cartesian coordinates C{x},
525
+ C{y} and C{z} and C{h} the I{signed, normal distance} to the triaxial's
526
+ surface in C{meter}, conventionally. Positive C{h} indicates, the
527
+ cartesian is outside the triaxial, negative C{h} means inside.
528
+
529
+ @raise TriaxialError: Zero length cartesian or vector.
530
+
531
+ @note: Cartesian C{(B{x}, B{y}, B{z})} I{must be on} this triaxial's surface,
532
+ use method L{Triaxial.sideOf} to validate.
533
+
534
+ @see: Methods L{Triaxial.normal3d} and L{Triaxial.height4}.
535
+ '''
536
+ v, h = _otherV3d(x_xyz, y, z), Height_(height, low=None)
537
+ if h:
538
+ if v.length < EPS0:
539
+ raise TriaxialError(x=x_xyz, y=y, z=z, txt=_null_)
540
+ if normal:
541
+ n = self.normal3d(v, length=h)
542
+ h = n.length
543
+ n += v
544
+ else:
545
+ h = h / v.length
546
+ n = v.times(h + _1_0)
547
+ else:
548
+ n = v
549
+ return Vector4Tuple(n.x, n.y, n.z, h, name__=self.normal4)
550
+
483
551
  def _order3(self, *abc, **reverse): # reverse=False
484
552
  '''(INTERNAL) Un-/Order C{a}, C{b} and C{c}.
485
553
 
@@ -557,7 +625,7 @@ class Triaxial_(_NamedEnumItem):
557
625
 
558
626
  @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
559
627
  L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
560
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar},
628
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
561
629
  ignored otherwise.
562
630
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
563
631
  @kwarg eps: On-surface tolerance (C{scalar}, distance I{squared}).
@@ -574,7 +642,7 @@ class Triaxial_(_NamedEnumItem):
574
642
  return INT0 if fabs(s) < eps else s
575
643
 
576
644
  def toEllipsoid(self, **name):
577
- '''Convert this triaxial to an L{Ellipsoid}, provided 2 axes match.
645
+ '''Convert this triaxial to a I{biaxial} L{Ellipsoid}, provided 2 axes match.
578
646
 
579
647
  @kwarg name: Optional C{B{name}=NN} (C{str}).
580
648
 
@@ -595,6 +663,8 @@ class Triaxial_(_NamedEnumItem):
595
663
  raise TriaxialError(a=a, b=b, c=c, txt=t)
596
664
  return Ellipsoid(a, b=b, name=self._name__(name))
597
665
 
666
+ toBiaxial = toEllipsoid
667
+
598
668
  def toStr(self, prec=9, **name): # PYCHOK signature
599
669
  '''Return this C{Triaxial} as a string.
600
670
 
@@ -605,11 +675,11 @@ class Triaxial_(_NamedEnumItem):
605
675
  @return: This C{Triaxial}'s attributes (C{str}).
606
676
  '''
607
677
  T = Triaxial_
608
- t = T.a,
609
- J = JacobiConformalSpherical
678
+ t = T.a, # props
679
+ J = ConformalSphere
610
680
  t += (J.ab, J.bc) if isinstance(self, J) else (T.b, T.c)
611
681
  t += T.e2ab, T.e2bc, T.e2ac
612
- J = JacobiConformal
682
+ J = ConformalTriaxial
613
683
  if isinstance(self, J):
614
684
  t += J.xyQ2,
615
685
  t += T.volume, T.area
@@ -655,9 +725,12 @@ class Triaxial(Triaxial_):
655
725
 
656
726
  @Property_RO
657
727
  def _a2b2_a2c2(self):
658
- '''@see: Methods C{.forwardBetaOmega} and C{._k2_kp2}.
728
+ '''@see: Methods C{.forwardBetaOmega} and property C{._k2E_kp2E}.
659
729
  '''
660
- return self._a2b2 / self._a2c2
730
+ s = self._a2c2
731
+ if s:
732
+ s = self._a2b2 / s
733
+ return s or _0_0
661
734
 
662
735
  @Property_RO
663
736
  def area(self):
@@ -667,9 +740,9 @@ class Triaxial(Triaxial_):
667
740
  '''
668
741
  a, b, c = self._abc3
669
742
  if a != b:
670
- kp2, k2 = self._k2_kp2 # swapped!
743
+ kp2, k2 = self._k2E_kp2E # swapped!
671
744
  aE = self._Elliptic(k2, _0_0, kp2, _1_0)
672
- c2 = self._1e2ac # cos(phi)**2 = (c/a)**2
745
+ c2 = self._c2_a2 # cos(phi)**2 = (c/a)**2
673
746
  s = sqrt(self.e2ac) # sin(phi)**2 = 1 - c2
674
747
  r = asin1(s) # phi = atan2(sqrt(c2), s)
675
748
  b *= fsum1f_(aE.fE(r) * s, (c / a) * (c / b),
@@ -685,8 +758,8 @@ class Triaxial(Triaxial_):
685
758
 
686
759
  @arg beta: Ellipsoidal latitude (C{radians} or L{Degrees}).
687
760
  @arg omega: Ellipsoidal longitude (C{radians} or L{Degrees}).
688
- @arg height: Height above or below the ellipsoid's surface (C{meter}, same
689
- units as this triaxial's C{a}, C{b} and C{c} semi-axes).
761
+ @arg height: Height above or below the ellipsoid's surface (C{meter},
762
+ same units as this triaxial's C{a}, semi-axes).
690
763
  @kwarg name: Optional C{B{name}=NN} (C{str}).
691
764
 
692
765
  @return: A L{Vector3Tuple}C{(x, y, z)}.
@@ -709,13 +782,13 @@ class Triaxial(Triaxial_):
709
782
  sb, cb = SinCos2(omega)
710
783
 
711
784
  r = self._a2b2_a2c2
712
- x *= cb * _sqrt0(ca**2 + sa**2 * r)
713
- y *= ca * sb
714
- z *= sa * _sqrt0(_1_0 - cb**2 * r)
785
+ x *= cb * (_sqrt0(ca**2 + sa**2 * r) if r else fabs(ca))
786
+ y *= ca * sb
787
+ z *= sa * (_sqrt0(_1_0 - cb**2 * r) if r else _1_0)
715
788
  return Vector3Tuple(x, y, z, **name)
716
789
 
717
790
  def forwardBetaOmega_(self, sbeta, cbeta, somega, comega, **name):
718
- '''Convert I{ellipsoidal} lat- and longitude C{beta} and C{omega}
791
+ '''Convert I{ellipsoidal} lat- C{beta} and longitude C{omega}
719
792
  to cartesian coordinates I{on the triaxial's surface}.
720
793
 
721
794
  @arg sbeta: Ellipsoidal latitude C{sin(beta)} (C{scalar}).
@@ -741,7 +814,7 @@ class Triaxial(Triaxial_):
741
814
 
742
815
  @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
743
816
  L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
744
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar},
817
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
745
818
  ignored otherwise.
746
819
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
747
820
  @kwarg normal_eps_name: Optional keyword arguments C{B{normal}=True},
@@ -759,7 +832,7 @@ class Triaxial(Triaxial_):
759
832
  @arg lat: Geodetic latitude (C{degrees}).
760
833
  @arg lon: Geodetic longitude (C{degrees}).
761
834
  @arg height: Height above the ellipsoid (C{meter}, same units
762
- as this triaxial's C{a}, C{b} and C{c} axes).
835
+ as this triaxial's semi-axes).
763
836
  @kwarg name: Optional C{B{name}=NN} (C{str}).
764
837
 
765
838
  @return: A L{Vector3Tuple}C{(x, y, z)}.
@@ -777,7 +850,7 @@ class Triaxial(Triaxial_):
777
850
  @arg slon: Geodetic longitude C{sin(lon)} (C{scalar}).
778
851
  @arg clon: Geodetic longitude C{cos(lon)} (C{scalar}).
779
852
  @arg height: Height above the ellipsoid (C{meter}, same units
780
- as this triaxial's axes C{a}, C{b} and C{c}).
853
+ as this triaxial's semi-axes).
781
854
  @kwarg name: Optional C{B{name}=NN} (C{str}).
782
855
 
783
856
  @return: A L{Vector3Tuple}C{(x, y, z)}.
@@ -795,11 +868,11 @@ class Triaxial(Triaxial_):
795
868
  ca_x_sb = ca * sb
796
869
  h = self._Height(height)
797
870
  # 1 - (1 - (c/a)**2) * sa**2 - (1 - (b/a)**2) * ca**2 * sb**2
798
- t = fsumf_(_1_0, -self.e2ac * sa**2, -self.e2ab * ca_x_sb**2)
799
- n = self.a / _sqrt0(t) # prime vertical
871
+ t = fsumf_(_1_0, -self.e2ac * sa**2, -self.e2ab * ca_x_sb**2)
872
+ n = _over(self.a, _sqrt0(t)) # prime vertical
800
873
  x = (h + n) * ca * cb
801
- y = (h + n * self._1e2ab) * ca_x_sb
802
- z = (h + n * self._1e2ac) * sa
874
+ y = (h + n * self._b2_a2) * ca_x_sb
875
+ z = (h + n * self._c2_a2) * sa
803
876
  return Vector3Tuple(x, y, z, **name)
804
877
 
805
878
  def _Height(self, height):
@@ -808,8 +881,8 @@ class Triaxial(Triaxial_):
808
881
  return Height_(height=height, low=-self.c, Error=TriaxialError)
809
882
 
810
883
  @Property_RO
811
- def _k2_kp2(self):
812
- '''(INTERNAL) Get C{k2} and C{kp2} for C{._xE}, C{._yE} and C{.area}.
884
+ def _k2E_kp2E(self):
885
+ '''(INTERNAL) Get elliptic C{k2} and C{kp2} for C{._xE}, C{._yE} and C{.area}.
813
886
  '''
814
887
  # k2 = a2b2 / a2c2 * c2_b2
815
888
  # kp2 = b2c2 / a2c2 * a2_b2
@@ -817,8 +890,9 @@ class Triaxial(Triaxial_):
817
890
  # xE = Elliptic(k2, -a2b2 / b2, kp2, a2_b2)
818
891
  # yE = Elliptic(kp2, +b2c2 / b2, k2, c2_b2)
819
892
  # aE = Elliptic(kp2, 0, k2, 1)
820
- return (self._a2b2_a2c2 * self._c2_b2,
821
- self._b2c2 / self._a2c2 * self._a2_b2)
893
+ k2 = self._c2_b2 * self._a2b2_a2c2
894
+ kp2 = (self._a2_b2 * self._b2c2 / self._a2c2) if k2 else _1_0
895
+ return (k2, kp2)
822
896
 
823
897
  def _radialTo3(self, sbeta, cbeta, somega, comega):
824
898
  '''(INTERNAL) Convert I{ellipsoidal} lat- and longitude C{beta} and
@@ -828,8 +902,8 @@ class Triaxial(Triaxial_):
828
902
  sa, ca = self._norm2(sbeta, cbeta)
829
903
  sb, cb = self._norm2(somega, comega)
830
904
 
831
- b2_a2 = self._1e2ab # == (b/a)**2
832
- c2_a2 = -self._1e2ac # == -(c/a)**2
905
+ b2_a2 = self._b2_a2 # == (b/a)**2
906
+ c2_a2 = -self._c2_a2 # == -(c/a)**2
833
907
  a2c2_a2 = self. e2ac # (a**2 - c**2) / a**2 == 1 - (c/a)**2
834
908
 
835
909
  x2 = _Fsumf_(_1_0, -b2_a2 * sa**2, c2_a2 * ca**2).fover(a2c2_a2)
@@ -847,34 +921,34 @@ class Triaxial(Triaxial_):
847
921
 
848
922
  @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
849
923
  L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
850
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar},
924
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
851
925
  ignored otherwise.
852
926
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
853
927
  @kwarg name: Optional C{B{name}=NN} (C{str}).
854
928
 
855
929
  @return: A L{BetaOmega3Tuple}C{(beta, omega, height)} with C{beta} and
856
930
  C{omega} in L{Radians} and (radial) C{height} in C{meter}, same
857
- units as this triaxial's axes.
931
+ units as this triaxial's semi-axes.
858
932
 
859
933
  @see: Methods L{Triaxial.forwardBetaOmega} and L{Triaxial.forwardBetaOmega_}
860
934
  and U{expressions (21-22)<https://www.Topo.Auth.GR/wp-content/uploads/
861
935
  sites/111/2021/12/09_Panou.pdf>}.
862
936
  '''
863
937
  v = _otherV3d_(x_xyz, y, z)
864
- a, b, h = self._reverseLatLon3(v, atan2, v, self.forwardBetaOmega_)
938
+ a, b, h = _reverseLatLon3(v, atan2, v, self.forwardBetaOmega_)
865
939
  return BetaOmega3Tuple(Radians(beta=a), Radians(omega=b), h, **name)
866
940
 
867
941
  def reverseCartesian(self, x_xyz, y=None, z=None, h=0, normal=True, eps=_EPS2e4, **name):
868
- '''Unproject" a cartesian I{on} this triaxial's surface to a cartesion I{off}
942
+ '''"Unproject" a cartesian I{on} this triaxial's surface to a cartesion I{off}
869
943
  this triaxial's surface.
870
944
 
871
945
  @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
872
946
  L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
873
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar},
947
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
874
948
  ignored otherwise.
875
949
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
876
950
  @arg h: Height above or below this triaxial's surface (C{meter}, same units
877
- as this triaxial's axes).
951
+ as this triaxial's semi-axes).
878
952
  @kwarg normal: If C{True}, the height is C{normal} to the surface, otherwise
879
953
  C{radially} to the center of this triaxial (C{bool}).
880
954
  @kwarg eps: Tolerance for on-surface test (C{scalar}), see method L{Triaxial.sideOf}.
@@ -904,40 +978,30 @@ class Triaxial(Triaxial_):
904
978
 
905
979
  @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
906
980
  L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
907
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar},
981
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
908
982
  ignored otherwise.
909
983
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
910
984
  @kwarg name: Optional C{B{name}=NN} (C{str}).
911
985
 
912
986
  @return: A L{LatLon3Tuple}C{(lat, lon, height)} with C{lat} and C{lon}
913
987
  in C{degrees} and (radial) C{height} in C{meter}, same units
914
- as this triaxial's axes.
988
+ as this triaxial's semi-axes.
915
989
 
916
990
  @see: Methods L{Triaxial.forwardLatLon} and L{Triaxial.forwardLatLon_}
917
991
  and U{expressions (4-5)<https://www.Topo.Auth.GR/wp-content/uploads/
918
992
  sites/111/2021/12/09_Panou.pdf>}.
919
993
  '''
920
994
  v = _otherV3d_(x_xyz, y, z)
921
- s = v.times_(self._1e2ac, # == 1 - e_sub_x**2
922
- self._1e2bc, # == 1 - e_sub_y**2
995
+ s = v.times_(self._c2_a2, # == 1 - e_sub_x**2
996
+ self._c2_b2, # == 1 - e_sub_y**2
923
997
  _1_0)
924
- a, b, h = self._reverseLatLon3(s, atan2d, v, self.forwardLatLon_)
998
+ a, b, h = _reverseLatLon3(s, atan2d, v, self.forwardLatLon_)
925
999
  return LatLon3Tuple(Degrees(lat=a), Degrees(lon=b), h, **name)
926
1000
 
927
- def _reverseLatLon3(self, s, atan2_, v, forward_):
928
- '''(INTERNAL) Helper for C{.reverseBetOmg} and C{.reverseLatLon}.
929
- '''
930
- x, y, z = s.xyz3
931
- d = hypot( x, y)
932
- a = atan2_(z, d)
933
- b = atan2_(y, x)
934
- h = v.minus_(*forward_(z, d, y, x)).length
935
- return a, b, h
936
-
937
1001
 
938
- class JacobiConformal(Triaxial):
939
- '''This is a conformal projection of a triaxial ellipsoid to a plane in which the
940
- C{X} and C{Y} grid lines are straight.
1002
+ class ConformalTriaxial(Triaxial):
1003
+ '''This is a I{Jacobi Conformal} projection of a triaxial ellipsoid to a plane
1004
+ where the C{X} and C{Y} grid lines are straight.
941
1005
 
942
1006
  Ellipsoidal coordinates I{beta} and I{omega} are converted to Jacobi Conformal
943
1007
  I{y} respectively I{x} separately. Jacobi's coordinates have been multiplied
@@ -948,7 +1012,7 @@ class JacobiConformal(Triaxial):
948
1012
  licensed under the MIT/X11 License.
949
1013
 
950
1014
  @note: This constructor can I{not be used to specify a sphere}, see alternate
951
- L{JacobiConformalSpherical}.
1015
+ L{ConformalSphere}.
952
1016
 
953
1017
  @see: L{Triaxial}, C++ class U{JacobiConformal<https://GeographicLib.SourceForge.io/
954
1018
  C++/doc/classGeographicLib_1_1JacobiConformal.html#details>}, U{Jacobi's conformal
@@ -961,12 +1025,12 @@ class JacobiConformal(Triaxial):
961
1025
  def _xE(self):
962
1026
  '''(INTERNAL) Get the x-elliptic function.
963
1027
  '''
964
- k2, kp2 = self._k2_kp2
1028
+ k2, kp2 = self._k2E_kp2E
965
1029
  # -a2b2 / b2 == (b2 - a2) / b2 == 1 - a2 / b2 == 1 - a2_b2
966
1030
  return self._Elliptic(k2, _1_0 - self._a2_b2, kp2, self._a2_b2)
967
1031
 
968
1032
  def xR(self, omega):
969
- '''Compute a Jacobi Conformal C{x} projection.
1033
+ '''Compute a I{Jacobi Conformal} C{x} projection.
970
1034
 
971
1035
  @arg omega: Ellipsoidal longitude (C{radians} or L{Degrees}).
972
1036
 
@@ -975,7 +1039,7 @@ class JacobiConformal(Triaxial):
975
1039
  return self.xR_(*SinCos2(omega))
976
1040
 
977
1041
  def xR_(self, somega, comega):
978
- '''Compute a Jacobi Conformal C{x} projection.
1042
+ '''Compute a I{Jacobi Conformal} C{x} projection.
979
1043
 
980
1044
  @arg somega: Ellipsoidal longitude C{sin(omega)} (C{scalar}).
981
1045
  @arg comega: Ellipsoidal longitude C{cos(omega)} (C{scalar}).
@@ -987,26 +1051,26 @@ class JacobiConformal(Triaxial):
987
1051
 
988
1052
  @Property_RO
989
1053
  def xyQ2(self):
990
- '''Get the Jacobi Conformal quadrant size (L{Jacobi2Tuple}C{(x, y)}).
1054
+ '''Get the I{Jacobi Conformal} quadrant size (L{Conformal2Tuple}C{(x, y)}).
991
1055
  '''
992
- return Jacobi2Tuple(Radians(x=self._a2_b2 * self._xE.cPi),
993
- Radians(y=self._c2_b2 * self._yE.cPi),
994
- name=JacobiConformal.xyQ2.name)
1056
+ return Conformal2Tuple(Radians(x=self._a2_b2 * self._xE.cPi),
1057
+ Radians(y=self._c2_b2 * self._yE.cPi),
1058
+ name=ConformalTriaxial.xyQ2.name)
995
1059
 
996
1060
  def xyR2(self, beta, omega, **name):
997
- '''Compute a Jacobi Conformal C{x} and C{y} projection.
1061
+ '''Compute a I{Jacobi Conformal} C{x} and C{y} projection.
998
1062
 
999
1063
  @arg beta: Ellipsoidal latitude (C{radians} or L{Degrees}).
1000
1064
  @arg omega: Ellipsoidal longitude (C{radians} or L{Degrees}).
1001
1065
  @kwarg name: Optional name (C{str}), overriding C{B{name}="xyR2"}.
1002
1066
 
1003
- @return: A L{Jacobi2Tuple}C{(x, y)}.
1067
+ @return: A L{Conformal2Tuple}C{(x, y)}.
1004
1068
  '''
1005
1069
  return self.xyR2_(*(SinCos2(beta) + SinCos2(omega)),
1006
1070
  name=_name__(name, name__=self.xyR2))
1007
1071
 
1008
1072
  def xyR2_(self, sbeta, cbeta, somega, comega, **name):
1009
- '''Compute a Jacobi Conformal C{x} and C{y} projection.
1073
+ '''Compute a I{Jacobi Conformal} C{x} and C{y} projection.
1010
1074
 
1011
1075
  @arg sbeta: Ellipsoidal latitude C{sin(beta)} (C{scalar}).
1012
1076
  @arg cbeta: Ellipsoidal latitude C{cos(beta)} (C{scalar}).
@@ -1014,22 +1078,22 @@ class JacobiConformal(Triaxial):
1014
1078
  @arg comega: Ellipsoidal longitude C{cos(omega)} (C{scalar}).
1015
1079
  @kwarg name: Optional name (C{str}), overriding C{B{name}="xyR2_"}.
1016
1080
 
1017
- @return: A L{Jacobi2Tuple}C{(x, y)}.
1081
+ @return: A L{Conformal2Tuple}C{(x, y)}.
1018
1082
  '''
1019
- return Jacobi2Tuple(self.xR_(somega, comega),
1020
- self.yR_(sbeta, cbeta),
1021
- name=_name__(name, name__=self.xyR2_))
1083
+ return Conformal2Tuple(self.xR_(somega, comega),
1084
+ self.yR_(sbeta, cbeta),
1085
+ name=_name__(name, name__=self.xyR2_))
1022
1086
 
1023
1087
  @Property_RO
1024
1088
  def _yE(self):
1025
- '''(INTERNAL) Get the x-elliptic function.
1089
+ '''(INTERNAL) Get the y-elliptic function.
1026
1090
  '''
1027
- kp2, k2 = self._k2_kp2 # swapped!
1091
+ kp2, k2 = self._k2E_kp2E # swapped!
1028
1092
  # b2c2 / b2 == (b2 - c2) / b2 == 1 - c2 / b2 == e2bc
1029
1093
  return self._Elliptic(k2, self.e2bc, kp2, self._c2_b2)
1030
1094
 
1031
1095
  def yR(self, beta):
1032
- '''Compute a Jacobi Conformal C{y} projection.
1096
+ '''Compute a I{Jacobi Conformal} C{y} projection.
1033
1097
 
1034
1098
  @arg beta: Ellipsoidal latitude (C{radians} or L{Degrees}).
1035
1099
 
@@ -1038,7 +1102,7 @@ class JacobiConformal(Triaxial):
1038
1102
  return self.yR_(*SinCos2(beta))
1039
1103
 
1040
1104
  def yR_(self, sbeta, cbeta):
1041
- '''Compute a Jacobi Conformal C{y} projection.
1105
+ '''Compute a I{Jacobi Conformal} C{y} projection.
1042
1106
 
1043
1107
  @arg sbeta: Ellipsoidal latitude C{sin(beta)} (C{scalar}).
1044
1108
  @arg cbeta: Ellipsoidal latitude C{cos(beta)} (C{scalar}).
@@ -1049,19 +1113,19 @@ class JacobiConformal(Triaxial):
1049
1113
  return Radians(y=self._yE.fPi(s, c) * self._c2_b2)
1050
1114
 
1051
1115
 
1052
- class JacobiConformalSpherical(JacobiConformal):
1053
- '''An alternate, I{spherical} L{JacobiConformal} projection.
1116
+ class ConformalSphere(ConformalTriaxial):
1117
+ '''An alternate, I{spherical Jacobi Conformal} projection.
1054
1118
 
1055
- @see: L{JacobiConformal} for other and more details.
1119
+ @see: L{ConformalTriaxial} for other and more details.
1056
1120
  '''
1057
1121
  _ab = _bc = 0
1058
1122
 
1059
1123
  def __init__(self, radius_triaxial, ab=0, bc=0, **name):
1060
- '''New L{JacobiConformalSpherical}.
1124
+ '''New L{ConformalSphere}.
1061
1125
 
1062
1126
  @arg radius_triaxial: Radius (C{scalar}, conventionally in
1063
- C{meter}) or an other L{JacobiConformalSpherical},
1064
- L{JacobiConformal} or ordered L{Triaxial}.
1127
+ C{meter}) or an other L{ConformalSphere},
1128
+ L{ConformalTriaxial} or ordered L{Triaxial}.
1065
1129
  @kwarg ab: Relative magnitude of C{B{a} - B{b}} (C{meter},
1066
1130
  same units as C{scalar B{radius}}.
1067
1131
  @kwarg bc: Relative magnitude of C{B{b} - B{c}} (C{meter},
@@ -1072,7 +1136,7 @@ class JacobiConformalSpherical(JacobiConformal):
1072
1136
  B{C{ab}}, negative B{C{bc}} or C{(B{ab}
1073
1137
  + B{bc})} not positive.
1074
1138
 
1075
- @note: If B{C{radius_triaxial}} is a L{JacobiConformalSpherical}
1139
+ @note: If B{C{radius_triaxial}} is a L{ConformalSphere}
1076
1140
  and if B{C{ab}} and B{C{bc}} are both zero or C{None},
1077
1141
  the B{C{radius_triaxial}}'s C{ab}, C{bc}, C{a}, C{b}
1078
1142
  and C{c} are copied.
@@ -1081,17 +1145,17 @@ class JacobiConformalSpherical(JacobiConformal):
1081
1145
  r = radius_triaxial
1082
1146
  if isinstance(r, Triaxial): # ordered only
1083
1147
  t = r._abc3
1084
- j = isinstance(r, JacobiConformalSpherical) and not bool(ab or bc)
1148
+ j = isinstance(r, ConformalSphere) and not bool(ab or bc)
1085
1149
  else:
1086
- t = (Radius(radius=r),) * 3
1150
+ t = (Radius_(radius=r),) * 3
1087
1151
  j = False
1088
1152
  self._ab = r.ab if j else Scalar_(ab=ab) # low=0
1089
1153
  self._bc = r.bc if j else Scalar_(bc=bc) # low=0
1090
1154
  if (self.ab + self.bc) <= 0:
1091
1155
  raise ValueError(_negative_)
1092
- a, _, c = self._abc3 = t
1093
- if not (a >= c and isfinite(self._a2b2)
1094
- and isfinite(self._a2c2)):
1156
+ a, _, c = self._abc3 = t
1157
+ if not (a >= c and _isfinite(self._a2b2)
1158
+ and _isfinite(self._a2c2)):
1095
1159
  raise ValueError(_not_(_finite_))
1096
1160
  except (TypeError, ValueError) as x:
1097
1161
  raise TriaxialError(radius_triaxial=r, ab=ab, bc=bc, cause=x)
@@ -1205,16 +1269,16 @@ def _hartzell3(pov, los, Tun): # in .Ellipsoid.hartzell4, .formy.hartzell
1205
1269
 
1206
1270
  a, b, c, T = Tun._ordered4
1207
1271
 
1208
- a2 = a**2 # largest, factored out
1209
- b2, p2 = (b**2, T._1e2ab) if b != a else (a2, _1_0)
1210
- c2, q2 = (c**2, T._1e2ac) if c != a else (a2, _1_0)
1272
+ a2 = T.a2 # largest, factored out
1273
+ b2, p2 = (T.b2, T._b2_a2) if b != a else (a2, _1_0)
1274
+ c2, q2 = (T.c2, T._c2_a2) if c != a else (a2, _1_0)
1211
1275
 
1212
1276
  p3 = T._order3d(p3)
1213
1277
  u3 = T._order3d(u3).unit() # unit vector, opposing signs
1214
1278
 
1215
- x2, y2, z2 = p3.x2y2z2 # p3.times_(p3).xyz3
1279
+ x2, y2, z2 = p3.x2y2z23 # p3.times_(p3).xyz3
1216
1280
  ux, vy, wz = u3.times_(p3).xyz3
1217
- u2, v2, w2 = u3.x2y2z2 # u3.times_(u3).xyz3
1281
+ u2, v2, w2 = u3.x2y2z23 # u3.times_(u3).xyz3
1218
1282
 
1219
1283
  t = (p2 * c2), c2, b2
1220
1284
  m = fdot(t, u2, v2, w2) # a2 factored out
@@ -1250,8 +1314,8 @@ def hartzell4(pov, los=False, tri_biax=_WGS84, **name):
1250
1314
  @kwarg los: Line-Of-Sight, I{direction} to the tri-/biaxial (L{Los}, L{Vector3d})
1251
1315
  or C{True} for the I{normal, perpendicular, plumb} to the surface of
1252
1316
  the tri-/biaxial or C{False} or C{None} to point to its center.
1253
- @kwarg tri_biax: A triaxial (L{Triaxial}, L{Triaxial_}, L{JacobiConformal} or
1254
- L{JacobiConformalSpherical}) or biaxial ellipsoid (L{Datum},
1317
+ @kwarg tri_biax: A triaxial (L{Triaxial}, L{Triaxial_}, L{ConformalTriaxial} or
1318
+ L{ConformalSphere}) or biaxial ellipsoid (L{Datum},
1255
1319
  L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple}) or spherical earth
1256
1320
  radius (C{scalar}, conventionally in C{meter}).
1257
1321
  @kwarg name: Optional name (C{str}), overriding C{B{name}="hartzell4"}.
@@ -1292,6 +1356,13 @@ def _hypot2_1(x, y, z=0):
1292
1356
  return fsumf_(_N_1_0, x*x, y*y, z*z)
1293
1357
 
1294
1358
 
1359
+ def _ordered(a, b, c):
1360
+ '''(INTERNAL) Is C{a >= b >= c > 0}?
1361
+ '''
1362
+ if not (_isfinite(a) and a >= b >= c > 0):
1363
+ raise TriaxialError(a=a, b=b, c=c, txt=_not_ordered_)
1364
+
1365
+
1295
1366
  def _otherV3d_(x_xyz, y, z, **name):
1296
1367
  '''(INTERNAL) Get a Vector3d from C{x_xyz}, C{y} and C{z}.
1297
1368
  '''
@@ -1359,7 +1430,7 @@ def _plumbTo4(x, y, a, b, eps=EPS):
1359
1430
  b, a, d, i = _plumbTo4(y, x, b, a, eps=eps)
1360
1431
  return a, b, d, i
1361
1432
 
1362
- if not (b > 0 and isfinite(a)):
1433
+ if not (b > 0 and _isfinite(a)):
1363
1434
  raise _ValueError(a=a, b=b)
1364
1435
 
1365
1436
  i = None
@@ -1379,7 +1450,7 @@ def _plumbTo4(x, y, a, b, eps=EPS):
1379
1450
  else: # x == 0
1380
1451
  if y < 0:
1381
1452
  b = -b
1382
- a = x # signed-0
1453
+ a = x # _copysign_0_0
1383
1454
  d = fabs(y - b)
1384
1455
 
1385
1456
  elif x: # y == 0
@@ -1393,11 +1464,11 @@ def _plumbTo4(x, y, a, b, eps=EPS):
1393
1464
  elif x < 0:
1394
1465
  a = -a
1395
1466
  if d is None:
1396
- b = y # signed-0
1467
+ b = y # _copysign_0_0
1397
1468
  d = fabs(x - a)
1398
1469
 
1399
1470
  else: # x == y == 0
1400
- a = x # signed-0
1471
+ a = x # _copysign_0_0
1401
1472
  d = b
1402
1473
 
1403
1474
  return a, b, d, i
@@ -1415,7 +1486,7 @@ def _plumbTo5(x, y, z, Tun, eps=EPS): # in .testTriaxials
1415
1486
  a, b, c, d, i = _plumbTo5(*t, eps=eps)
1416
1487
  return T._order3(a, b, c, reverse=True) + (d, i)
1417
1488
 
1418
- if not (c > 0 and isfinite(a)):
1489
+ if not (c > 0 and _isfinite(a)):
1419
1490
  raise _ValueError(a=a, b=b, c=c)
1420
1491
 
1421
1492
  if eps > 0:
@@ -1432,8 +1503,8 @@ def _plumbTo5(x, y, z, Tun, eps=EPS): # in .testTriaxials
1432
1503
  w = fabs(z / c)
1433
1504
  g = _hypot2_1(u, v, w)
1434
1505
  if fabs(g) > EPS02:
1435
- r = T._1e2ac # (c / a)**2
1436
- s = T._1e2bc # (c / b)**2
1506
+ r = T._c2_a2 # (c / a)**2
1507
+ s = T._c2_b2 # (c / b)**2
1437
1508
  t, i = _rootNd(_1_0 / r, _1_0 / s, u, v, w, g) # eps
1438
1509
  a = _over(x, t * r + _1_0)
1439
1510
  b = _over(y, t * s + _1_0)
@@ -1442,10 +1513,10 @@ def _plumbTo5(x, y, z, Tun, eps=EPS): # in .testTriaxials
1442
1513
  else: # on the ellipsoid
1443
1514
  a, b, c, d = x, y, z, _0_0
1444
1515
  else: # x == 0
1445
- a = x # 0
1516
+ a = x # = _copysign_0_0(x)
1446
1517
  b, c, d, i = _plumbTo4(y, z, b, c, eps=eps)
1447
1518
  elif x: # y == 0
1448
- b = y # 0
1519
+ b = y # = _copysign_0_0(y)
1449
1520
  a, c, d, i = _plumbTo4(x, z, a, c, eps=eps)
1450
1521
  else: # x == y == 0
1451
1522
  if z < 0:
@@ -1462,7 +1533,7 @@ def _plumbTo5(x, y, z, Tun, eps=EPS): # in .testTriaxials
1462
1533
  c *= sqrt(-s)
1463
1534
  d = hypot_(x - a, y - b, c)
1464
1535
  else:
1465
- c = z # signed-0
1536
+ c = z # _copysign_0_0(z)
1466
1537
  a, b, d, i = _plumbTo4(x, y, a, b, eps=eps)
1467
1538
 
1468
1539
  if val > 0:
@@ -1470,6 +1541,17 @@ def _plumbTo5(x, y, z, Tun, eps=EPS): # in .testTriaxials
1470
1541
  return a, b, c, d, i
1471
1542
 
1472
1543
 
1544
+ def _reverseLatLon3(s, atan2_, v, forward_):
1545
+ '''(INTERNAL) Helper for C{.reverseBetaOmega} and C{.reverseLatLon}.
1546
+ '''
1547
+ x, y, z = s.xyz3
1548
+ d = hypot( x, y)
1549
+ a = atan2_(z, d)
1550
+ b = atan2_(y, x)
1551
+ h = v.minus_(*forward_(z, d, y, x)).length or INT0
1552
+ return a, b, h
1553
+
1554
+
1473
1555
  def _rootNd(r, s, u, v, w, g, eps=EPS0):
1474
1556
  '''(INTERNAL) Robust 2-D or 3-D root finder: 2-D if C{s == v == 0} else 3-D root.
1475
1557