pygeodesy 24.3.24__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-24.3.24.dist-info/METADATA +272 -0
- PyGeodesy-24.3.24.dist-info/RECORD +115 -0
- PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
- PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
- pygeodesy/LICENSE +21 -0
- pygeodesy/__init__.py +615 -0
- pygeodesy/__main__.py +103 -0
- pygeodesy/albers.py +867 -0
- pygeodesy/auxilats/_CX_4.py +218 -0
- pygeodesy/auxilats/_CX_6.py +314 -0
- pygeodesy/auxilats/_CX_8.py +475 -0
- pygeodesy/auxilats/__init__.py +54 -0
- pygeodesy/auxilats/__main__.py +86 -0
- pygeodesy/auxilats/auxAngle.py +548 -0
- pygeodesy/auxilats/auxDLat.py +302 -0
- pygeodesy/auxilats/auxDST.py +296 -0
- pygeodesy/auxilats/auxLat.py +848 -0
- pygeodesy/auxilats/auxily.py +272 -0
- pygeodesy/azimuthal.py +1150 -0
- pygeodesy/basics.py +892 -0
- pygeodesy/booleans.py +2031 -0
- pygeodesy/cartesianBase.py +1062 -0
- pygeodesy/clipy.py +704 -0
- pygeodesy/constants.py +516 -0
- pygeodesy/css.py +660 -0
- pygeodesy/datums.py +752 -0
- pygeodesy/deprecated/__init__.py +61 -0
- pygeodesy/deprecated/bases.py +40 -0
- pygeodesy/deprecated/classes.py +262 -0
- pygeodesy/deprecated/consterns.py +54 -0
- pygeodesy/deprecated/datum.py +40 -0
- pygeodesy/deprecated/functions.py +375 -0
- pygeodesy/deprecated/nvector.py +48 -0
- pygeodesy/deprecated/rhumbBase.py +32 -0
- pygeodesy/deprecated/rhumbaux.py +33 -0
- pygeodesy/deprecated/rhumbsolve.py +33 -0
- pygeodesy/deprecated/rhumbx.py +33 -0
- pygeodesy/dms.py +986 -0
- pygeodesy/ecef.py +1348 -0
- pygeodesy/elevations.py +279 -0
- pygeodesy/ellipsoidalBase.py +1224 -0
- pygeodesy/ellipsoidalBaseDI.py +913 -0
- pygeodesy/ellipsoidalExact.py +343 -0
- pygeodesy/ellipsoidalGeodSolve.py +343 -0
- pygeodesy/ellipsoidalKarney.py +403 -0
- pygeodesy/ellipsoidalNvector.py +685 -0
- pygeodesy/ellipsoidalVincenty.py +590 -0
- pygeodesy/ellipsoids.py +2476 -0
- pygeodesy/elliptic.py +1198 -0
- pygeodesy/epsg.py +243 -0
- pygeodesy/errors.py +804 -0
- pygeodesy/etm.py +1190 -0
- pygeodesy/fmath.py +1013 -0
- pygeodesy/formy.py +1818 -0
- pygeodesy/frechet.py +865 -0
- pygeodesy/fstats.py +760 -0
- pygeodesy/fsums.py +1898 -0
- pygeodesy/gars.py +358 -0
- pygeodesy/geodesicw.py +581 -0
- pygeodesy/geodesicx/_C4_24.py +1699 -0
- pygeodesy/geodesicx/_C4_27.py +2395 -0
- pygeodesy/geodesicx/_C4_30.py +3301 -0
- pygeodesy/geodesicx/__init__.py +48 -0
- pygeodesy/geodesicx/__main__.py +91 -0
- pygeodesy/geodesicx/gx.py +1382 -0
- pygeodesy/geodesicx/gxarea.py +535 -0
- pygeodesy/geodesicx/gxbases.py +154 -0
- pygeodesy/geodesicx/gxline.py +669 -0
- pygeodesy/geodsolve.py +426 -0
- pygeodesy/geohash.py +914 -0
- pygeodesy/geoids.py +1884 -0
- pygeodesy/hausdorff.py +892 -0
- pygeodesy/heights.py +1155 -0
- pygeodesy/interns.py +687 -0
- pygeodesy/iters.py +545 -0
- pygeodesy/karney.py +919 -0
- pygeodesy/ktm.py +633 -0
- pygeodesy/latlonBase.py +1766 -0
- pygeodesy/lazily.py +960 -0
- pygeodesy/lcc.py +684 -0
- pygeodesy/ltp.py +1107 -0
- pygeodesy/ltpTuples.py +1563 -0
- pygeodesy/mgrs.py +721 -0
- pygeodesy/named.py +1324 -0
- pygeodesy/namedTuples.py +683 -0
- pygeodesy/nvectorBase.py +695 -0
- pygeodesy/osgr.py +781 -0
- pygeodesy/points.py +1686 -0
- pygeodesy/props.py +628 -0
- pygeodesy/resections.py +1048 -0
- pygeodesy/rhumb/__init__.py +46 -0
- pygeodesy/rhumb/aux_.py +397 -0
- pygeodesy/rhumb/bases.py +1148 -0
- pygeodesy/rhumb/ekx.py +563 -0
- pygeodesy/rhumb/solve.py +572 -0
- pygeodesy/simplify.py +647 -0
- pygeodesy/solveBase.py +472 -0
- pygeodesy/sphericalBase.py +724 -0
- pygeodesy/sphericalNvector.py +1264 -0
- pygeodesy/sphericalTrigonometry.py +1447 -0
- pygeodesy/streprs.py +627 -0
- pygeodesy/trf.py +2079 -0
- pygeodesy/triaxials.py +1484 -0
- pygeodesy/units.py +969 -0
- pygeodesy/unitsBase.py +349 -0
- pygeodesy/ups.py +538 -0
- pygeodesy/utily.py +1231 -0
- pygeodesy/utm.py +762 -0
- pygeodesy/utmups.py +318 -0
- pygeodesy/utmupsBase.py +517 -0
- pygeodesy/vector2d.py +785 -0
- pygeodesy/vector3d.py +968 -0
- pygeodesy/vector3dBase.py +1049 -0
- pygeodesy/webmercator.py +383 -0
- pygeodesy/wgrs.py +439 -0
pygeodesy/dms.py
ADDED
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''Parsers and formatters of angles in degrees, minutes and seconds or radians.
|
|
5
|
+
|
|
6
|
+
Functions to parse and format bearing, compass, lat- and longitudes in various forms of
|
|
7
|
+
degrees, minutes and seconds with or without degrees, minute and second symbols plus a
|
|
8
|
+
compass point suffix, including parsing of C{decimal} and C{sexagecimal} degrees.
|
|
9
|
+
|
|
10
|
+
Set env variable C{PYGEODESY_FMT_FORM} to any C{F_...} form to override default C{F_DMS}
|
|
11
|
+
formatting of lat- and longitudes or to an empty string to restore the default.
|
|
12
|
+
|
|
13
|
+
After I{(C) Chris Veness 2011-2015} published under the same MIT Licence**, see
|
|
14
|
+
U{Latitude/Longitude<https://www.Movable-Type.co.UK/scripts/latlong.html>} and
|
|
15
|
+
U{Vector-based geodesy<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}.
|
|
16
|
+
|
|
17
|
+
@var F_D: Format degrees as unsigned "deg°" with symbol, plus compass point suffix C{N, S, E} or C{W} (C{str}).
|
|
18
|
+
@var F_DM: Format degrees as unsigned "deg°min′" with symbols, plus suffix (C{str}).
|
|
19
|
+
@var F_DMS: Format degrees as unsigned "deg°min′sec″" with symbols, plus suffix (C{str}).
|
|
20
|
+
@var F_DEG: Format degrees as unsigned "[D]DD" I{without} symbol, plus suffix (C{str}).
|
|
21
|
+
@var F_MIN: Format degrees as unsigned "[D]DDMM" I{without} symbols, plus suffix (C{str}).
|
|
22
|
+
@var F_SEC: Format degrees as unsigned "[D]DDMMSS" I{without} symbols, plus suffix (C{str}).
|
|
23
|
+
@var F_D60: Format degrees as unsigned "[D]DD.MMSS" C{sexagecimal} I{without} symbols, plus suffix (C{str}).
|
|
24
|
+
@var F__E: Format degrees as unsigned "%E" I{without} symbols, plus suffix (C{str}).
|
|
25
|
+
@var F__F: Format degrees as unsigned "%F" I{without} symbols, plus suffix (C{str}).
|
|
26
|
+
@var F__G: Format degrees as unsigned "%G" I{without} symbols, plus suffix (C{str}).
|
|
27
|
+
@var F_RAD: Convert degrees to radians and format as unsigned "RR" with symbol, plus suffix (C{str}).
|
|
28
|
+
|
|
29
|
+
@var F_D_: Format degrees as signed "-/deg°" with symbol, I{without} suffix (C{str}).
|
|
30
|
+
@var F_DM_: Format degrees as signed "-/deg°min′" with symbols, I{without} suffix (C{str}).
|
|
31
|
+
@var F_DMS_: Format degrees as signed "-/deg°min′sec″" with symbols, I{without} suffix (C{str}).
|
|
32
|
+
@var F_DEG_: Format degrees as signed "-/[D]DD" I{without} symbol, I{without} suffix (C{str}).
|
|
33
|
+
@var F_MIN_: Format degrees as signed "-/[D]DDMM" I{without} symbols, I{without} suffix (C{str}).
|
|
34
|
+
@var F_SEC_: Format degrees as signed "-/[D]DDMMSS" I{without} symbols, I{without} suffix (C{str}).
|
|
35
|
+
@var F_D60_: Format degrees as signed "-/[D]DD.MMSS" C{sexagecimal} I{without} symbols, I{without} suffix (C{str}).
|
|
36
|
+
@var F__E_: Format degrees as signed "-/%E" I{without} symbols, I{without} suffix (C{str}).
|
|
37
|
+
@var F__F_: Format degrees as signed "-/%F" I{without} symbols, I{without} suffix (C{str}).
|
|
38
|
+
@var F__G_: Format degrees as signed "-/%G" I{without} symbols, I{without} suffix (C{str}).
|
|
39
|
+
@var F_RAD_: Convert degrees to radians and format as signed "-/RR" I{without} symbol, I{without} suffix (C{str}).
|
|
40
|
+
|
|
41
|
+
@var F_D__: Format degrees as signed "-/+deg°" with symbol, I{without} suffix (C{str}).
|
|
42
|
+
@var F_DM__: Format degrees as signed "-/+deg°min′" with symbols, I{without} suffix (C{str}).
|
|
43
|
+
@var F_DMS__: Format degrees as signed "-/+deg°min′sec″" with symbols, I{without} suffix (C{str}).
|
|
44
|
+
@var F_DEG__: Format degrees as signed "-/+[D]DD" I{without} symbol, I{without} suffix (C{str}).
|
|
45
|
+
@var F_MIN__: Format degrees as signed "-/+[D]DDMM" I{without} symbols, without suffix (C{str}).
|
|
46
|
+
@var F_SEC__: Format degrees as signed "-/+[D]DDMMSS" I{without} symbols, I{without} suffix (C{str}).
|
|
47
|
+
@var F_D60__: Format degrees as signed "-/+[D]DD.MMSS" C{sexagecimal} I{without} symbols, I{without} suffix (C{str}).
|
|
48
|
+
@var F__E__: Format degrees as signed "-/+%E" I{without} symbols, I{without} suffix (C{str}).
|
|
49
|
+
@var F__F__: Format degrees as signed "-/+%F" I{without} symbols, I{without} suffix (C{str}).
|
|
50
|
+
@var F__G__: Format degrees as signed "-/+%G" I{without} symbols, I{without} suffix (C{str}).
|
|
51
|
+
@var F_RAD__: Convert degrees to radians and format as signed "-/+RR" I{without} symbol, I{without} suffix (C{str}).
|
|
52
|
+
|
|
53
|
+
@var S_DEG: Degrees symbol, default C{"°"}
|
|
54
|
+
@var S_MIN: Minutes symbol, default C{"′"} aka I{PRIME}
|
|
55
|
+
@var S_SEC: Seconds symbol, default C{"″"} aka I{DOUBLE_PRIME}
|
|
56
|
+
@var S_RAD: Radians symbol, default C{""} aka L{pygeodesy.NN}
|
|
57
|
+
@var S_DMS: If C{True} include, otherwise cancel all DMS symbols, default C{True}.
|
|
58
|
+
@var S_SEP: Separator between C{deg°|min′|sec″|suffix}, default C{""} aka L{pygeodesy.NN}
|
|
59
|
+
|
|
60
|
+
@note: In Python 2-, L{S_DEG}, L{S_MIN}, L{S_SEC}, L{S_RAD} and L{S_SEP} may be multi-byte,
|
|
61
|
+
non-ascii characters and if so, I{not} C{unicode}.
|
|
62
|
+
'''
|
|
63
|
+
|
|
64
|
+
from pygeodesy.basics import copysign0, isLatLon, isodd, issequence, isstr, map2, \
|
|
65
|
+
neg as _neg # in .ups
|
|
66
|
+
from pygeodesy.constants import _umod_360, _0_0, _0_5, _60_0, _360_0, _3600_0
|
|
67
|
+
from pygeodesy.errors import ParseError, RangeError, _TypeError, _ValueError,\
|
|
68
|
+
_parseX, rangerrors, _xkwds, _xkwds_get
|
|
69
|
+
from pygeodesy.interns import NN, _arg_, _COMMA_, _d_, _DASH_, _deg_, _degrees_, _DOT_, \
|
|
70
|
+
_0_, _e_, _E_, _EW_, _f_, _F_, _g_, _MINUS_, _N_, _NE_, _NS_, \
|
|
71
|
+
_NSEW_, _NW_, _of_, _PERCENTDOTSTAR_, _PLUS_, _PLUSMINUS_, \
|
|
72
|
+
_QUOTE1_, _QUOTE2_, _radians_, _S_, _SE_, _SPACE_, _SW_, _W_
|
|
73
|
+
from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _getenv
|
|
74
|
+
# from pygeodesy.namedTuples import LatLon2Tuple # _MODS
|
|
75
|
+
# from pygeodesy.props import _throwarning # _MODS
|
|
76
|
+
from pygeodesy.streprs import Fmt, fstr, fstrzs, _0wpF
|
|
77
|
+
# from pygeodesy.units import Precision_ # _MODS
|
|
78
|
+
# from pygeodesy.utily import _Wrap # _MODS
|
|
79
|
+
|
|
80
|
+
from math import fabs, modf, radians
|
|
81
|
+
try:
|
|
82
|
+
from string import letters as _LETTERS
|
|
83
|
+
except ImportError: # Python 3+
|
|
84
|
+
from string import ascii_letters as _LETTERS
|
|
85
|
+
|
|
86
|
+
__all__ = _ALL_LAZY.dms
|
|
87
|
+
__version__ = '24.03.21'
|
|
88
|
+
|
|
89
|
+
_beyond_ = 'beyond'
|
|
90
|
+
_DDDMMSS_ = 'DDDMMSS'
|
|
91
|
+
_deg_min_ = 'deg+min'
|
|
92
|
+
_keyword_ = 'keyword'
|
|
93
|
+
_SDIGITS_ = '-0123456789+'
|
|
94
|
+
_sexagecimal_ = 'sexagecimal'
|
|
95
|
+
_SEXAGECIMUL = 1.e4 # sexagecimal C{D.MMSSss} into decimal C{DMMSS.ss}
|
|
96
|
+
|
|
97
|
+
F_D, F_DM, F_DMS, F_DEG, F_MIN, F_SEC, F_D60, F__E, F__F, F__G, F_RAD = _F_s = (
|
|
98
|
+
_d_, 'dm', 'dms', _deg_, 'min', 'sec', 'd60', _e_, _f_, _g_, 'rad')
|
|
99
|
+
F_D_, F_DM_, F_DMS_, F_DEG_, F_MIN_, F_SEC_, F_D60_, F__E_, F__F_, F__G_, F_RAD_ = (NN(
|
|
100
|
+
_MINUS_, _) for _ in _F_s)
|
|
101
|
+
F_D__, F_DM__, F_DMS__, F_DEG__, F_MIN__, F_SEC__, F_D60__, F__E__, F__F__, F__G__, F_RAD__ = (NN(
|
|
102
|
+
_PLUS_, _) for _ in _F_s)
|
|
103
|
+
del _F_s
|
|
104
|
+
_F_DMS = _getenv('PYGEODESY_FMT_FORM', NN) or F_DMS
|
|
105
|
+
|
|
106
|
+
_F_case = {F_D: F_D, F_DEG: F_D, _degrees_: F_D, # unsigned _F_s
|
|
107
|
+
F_DM: F_DM, F_MIN: F_DM, _deg_min_: F_DM,
|
|
108
|
+
F_D60: F_D60, F_RAD: F_RAD, _radians_: F_RAD,
|
|
109
|
+
F__E: F__E, F__F: F__F, F__G: F__G} # default F_DMS
|
|
110
|
+
_F_prec = {F_D: 6, F_DM: 4, F_DMS: 2, # default precs
|
|
111
|
+
F_DEG: 6, F_MIN: 4, F_SEC: 2, F_D60: 0,
|
|
112
|
+
F__E: 8, F__F: 8, F__G: 8, F_RAD: 5}
|
|
113
|
+
_F_symb = set((F_D, F_DM, F_DMS, _deg_min_)) # == {} pychok -Tb
|
|
114
|
+
|
|
115
|
+
S_DEG = _DEGREES_ = '°' # ord() = 176
|
|
116
|
+
S_MIN = _MINUTES_ = '′' # PRIME
|
|
117
|
+
S_SEC = _SECONDS_ = '″' # DOUBLE_PRIME
|
|
118
|
+
S_RAD = _RADIANS_ = NN # PYCHOK radians symbol ""
|
|
119
|
+
S_DMS = True # include DMS symbols
|
|
120
|
+
S_SEP = NN # separator between deg|min|sec|suffix ""
|
|
121
|
+
S_NUL = NN # empty string, kept INTERNAL
|
|
122
|
+
|
|
123
|
+
# note: ord(_DEGREES_) == ord('°') == 176, ord('˚') == 730
|
|
124
|
+
_S_norm = {S_DEG: _DEGREES_, '˚': _DEGREES_, '^': _DEGREES_, # _d_: _DEGREES_,
|
|
125
|
+
S_MIN: _MINUTES_, '’': _MINUTES_, _QUOTE1_: _MINUTES_, # _r_: _RADIANS_
|
|
126
|
+
S_SEC: _SECONDS_, '”': _SECONDS_, _QUOTE2_: _SECONDS_}
|
|
127
|
+
|
|
128
|
+
_WINDS = (_N_, 'NbE', 'NNE', 'NEbN', _NE_, 'NEbE', 'ENE', 'EbN',
|
|
129
|
+
_E_, 'EbS', 'ESE', 'SEbE', _SE_, 'SEbS', 'SSE', 'SbE',
|
|
130
|
+
_S_, 'SbW', 'SSW', 'SWbS', _SW_, 'SWbW', 'WSW', 'WbS',
|
|
131
|
+
_W_, 'WbN', 'WNW', 'NWbW', _NW_, 'NWbN', 'NNW', 'NbW')
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _D603(sep, s_D=_DOT_, s_M=None, s_S=S_NUL, s_DMS=S_DMS, **unused):
|
|
135
|
+
'''(INTERNAL) Get the overridden or default pseudo-C{DMS} symbols.
|
|
136
|
+
'''
|
|
137
|
+
if s_DMS:
|
|
138
|
+
M = sep if s_M is None else s_M
|
|
139
|
+
return s_D, (M or S_NUL), s_S
|
|
140
|
+
else: # no overriden symbols
|
|
141
|
+
return _DOT_, sep, S_NUL
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _DMS3(form, s_D=S_DEG, s_M=S_MIN, s_S=S_SEC, s_DMS=S_DMS, **unused):
|
|
145
|
+
'''(INTERNAL) Get the overridden or default C{DMS} symbols.
|
|
146
|
+
'''
|
|
147
|
+
return (s_D, s_M, s_S) if s_DMS and form in _F_symb else (S_NUL, S_NUL, S_NUL)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _dms3(d, ddd, p, w):
|
|
151
|
+
'''(INTERNAL) Format C{d} as (deg, min, sec) C{str}s with leading zeros.
|
|
152
|
+
'''
|
|
153
|
+
d, s = divmod(round(d * _3600_0, p), _3600_0)
|
|
154
|
+
m, s = divmod(s, _60_0)
|
|
155
|
+
return (_0wpF(ddd, 0, d),
|
|
156
|
+
_0wpF( 2, 0, m),
|
|
157
|
+
_0wpF(w+2, p, s))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _fstrzs(t, **unused):
|
|
161
|
+
'''(INTERNAL) Pass-thru version of C{.streprs.fstrzs}.
|
|
162
|
+
'''
|
|
163
|
+
return t
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _split3(strDMS, suffix=_NSEW_):
|
|
167
|
+
'''(INTERNAL) Return sign, stripped B{C{strDMS}} and compass point.
|
|
168
|
+
'''
|
|
169
|
+
t = strDMS.strip()
|
|
170
|
+
s = t[:1] # sign or digit
|
|
171
|
+
P = t[-1:] # compass point or digit or dot
|
|
172
|
+
t = t.lstrip(_PLUSMINUS_).rstrip(suffix).strip()
|
|
173
|
+
return s, t, P
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _toDMS(deg, form, prec, sep, ddd, suff, s_D_M_S): # MCCABE 13 in .units
|
|
177
|
+
'''(INTERNAL) Convert C{deg} to C{str}, with/-out sign, DMS symbols and/or suffix.
|
|
178
|
+
'''
|
|
179
|
+
f = form
|
|
180
|
+
try:
|
|
181
|
+
deg = float(deg)
|
|
182
|
+
except (TypeError, ValueError) as x:
|
|
183
|
+
raise _ValueError(deg=deg, form=f, prec=prec, cause=x)
|
|
184
|
+
|
|
185
|
+
if f[:1] in _PLUSMINUS_: # signed
|
|
186
|
+
sign = _MINUS_ if deg < 0 else (
|
|
187
|
+
_PLUS_ if deg > 0 and f[:1] == _PLUS_ else NN)
|
|
188
|
+
f = f.lstrip(_PLUSMINUS_)
|
|
189
|
+
suff = NN # no suffix if signed
|
|
190
|
+
else: # suffixed
|
|
191
|
+
sign = NN # no sign if suffixed
|
|
192
|
+
if suff and sep: # no sep if no suffix
|
|
193
|
+
suff = NN(sep, suff)
|
|
194
|
+
try:
|
|
195
|
+
F = _F_case[f] # .strip()
|
|
196
|
+
except KeyError:
|
|
197
|
+
f = f.lower() # .strip()
|
|
198
|
+
F = _F_case.get(f, F_DMS)
|
|
199
|
+
|
|
200
|
+
if prec is None:
|
|
201
|
+
z = p = _F_prec.get(F, 6)
|
|
202
|
+
else:
|
|
203
|
+
z = int(prec)
|
|
204
|
+
p = abs(z)
|
|
205
|
+
w = p + (1 if p else 0)
|
|
206
|
+
z = fstrzs if z > 1 else _fstrzs
|
|
207
|
+
d = fabs(deg)
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
if F is F_DMS: # 'deg+min+sec', default
|
|
211
|
+
D, M, S = _DMS3(f, **s_D_M_S)
|
|
212
|
+
d, m, s = _dms3(d, ddd, p, w)
|
|
213
|
+
t = NN(sign, d, D, sep,
|
|
214
|
+
m, M, sep,
|
|
215
|
+
z(s), S, suff)
|
|
216
|
+
|
|
217
|
+
elif F is F_DM: # 'deg+min'
|
|
218
|
+
D, M, _ = _DMS3(f, **s_D_M_S)
|
|
219
|
+
d, m = divmod(round(d * _60_0, p), _60_0)
|
|
220
|
+
t = NN(sign, _0wpF(ddd, 0, d), D, sep,
|
|
221
|
+
z(_0wpF(w+2, p, m)), M, suff)
|
|
222
|
+
|
|
223
|
+
elif F is F_D: # 'deg'
|
|
224
|
+
D, _, _ = _DMS3(f, **s_D_M_S)
|
|
225
|
+
t = NN(sign, z(_0wpF(w+ddd, p, d)), D, suff)
|
|
226
|
+
|
|
227
|
+
elif F is F_D60: # 'deg.MM|SSss|'
|
|
228
|
+
D, M, S = _D603(sep, **s_D_M_S)
|
|
229
|
+
d, m, s = _dms3(d, ddd, p, w)
|
|
230
|
+
t = z(s).split(_DOT_) + [S, suff]
|
|
231
|
+
t = NN(sign, d, D, m, M, *t)
|
|
232
|
+
|
|
233
|
+
elif F is F_RAD:
|
|
234
|
+
R = _xkwds_get(s_D_M_S, s_R=S_RAD)
|
|
235
|
+
r = NN(_PERCENTDOTSTAR_, _F_) % (p, radians(d))
|
|
236
|
+
t = NN(sign, z(r), R, suff)
|
|
237
|
+
|
|
238
|
+
else: # F in (F__E, F__F, F__G)
|
|
239
|
+
D = _xkwds_get(s_D_M_S, s_D=S_NUL)
|
|
240
|
+
d = NN(_PERCENTDOTSTAR_, F) % (p, d) # XXX f?
|
|
241
|
+
t = NN(sign, z(d, ap1z=F is F__G), D, suff)
|
|
242
|
+
|
|
243
|
+
except Exception as x:
|
|
244
|
+
raise _ValueError(deg=deg, form=form, F=F, prec=prec, suff=suff, cause=x)
|
|
245
|
+
|
|
246
|
+
return t # NOT unicode in Python 2-
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def bearingDMS(bearing, form=F_D, prec=None, sep=S_SEP, **s_D_M_S):
|
|
250
|
+
'''Convert bearing to a string (without compass point suffix).
|
|
251
|
+
|
|
252
|
+
@arg bearing: Bearing from North (compass C{degrees360}).
|
|
253
|
+
@kwarg form: Format specifier for B{C{deg}} (C{str} or L{F_D},
|
|
254
|
+
L{F_DM}, L{F_DMS}, L{F_DEG}, L{F_MIN}, L{F_SEC},
|
|
255
|
+
L{F_D60}, L{F__E}, L{F__F}, L{F__G}, L{F_RAD},
|
|
256
|
+
L{F_D_}, L{F_DM_}, L{F_DMS_}, L{F_DEG_}, L{F_MIN_},
|
|
257
|
+
L{F_SEC_}, L{F_D60_}, L{F__E_}, L{F__F_}, L{F__G_},
|
|
258
|
+
L{F_RAD_}, L{F_D__}, L{F_DM__}, L{F_DMS__}, L{F_DEG__},
|
|
259
|
+
L{F_MIN__}, L{F_SEC__}, L{F_D60__}, L{F__E__},
|
|
260
|
+
L{F__F__}, L{F__G__} or L{F_RAD__}).
|
|
261
|
+
@kwarg prec: Number of decimal digits (0..9 or C{None} for default).
|
|
262
|
+
Trailing zero decimals are stripped for B{C{prec}}
|
|
263
|
+
values of 1 and above, but kept for negative B{C{prec}}.
|
|
264
|
+
@kwarg sep: Separator between degrees, minutes, seconds, suffix (C{str}).
|
|
265
|
+
@kwarg s_D_M_S: Optional keyword arguments C{B{s_D}=str}, C{B{s_M}=str},
|
|
266
|
+
C{B{s_S}=str} and C{B{s_DMS}=True} to override any or
|
|
267
|
+
cancel all DMS symbols, defaults L{S_DEG}, L{S_MIN}
|
|
268
|
+
respectively L{S_SEC}.
|
|
269
|
+
|
|
270
|
+
@return: Compass degrees per the specified B{C{form}} (C{str}).
|
|
271
|
+
|
|
272
|
+
@see: Function L{pygeodesy.toDMS}.
|
|
273
|
+
'''
|
|
274
|
+
return _toDMS(_umod_360(bearing), form, prec, sep, 1, NN, s_D_M_S)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _clip(angle, limit, units):
|
|
278
|
+
'''(INTERNAL) Helper for C{clipDegrees} and C{clipRadians}.
|
|
279
|
+
'''
|
|
280
|
+
c = min(limit, max(-limit, angle))
|
|
281
|
+
if c != angle and rangerrors():
|
|
282
|
+
t = _SPACE_(fstr(angle, prec=6, ints=True), _beyond_,
|
|
283
|
+
copysign0(limit, angle), units)
|
|
284
|
+
raise RangeError(t, txt=None)
|
|
285
|
+
return c
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def clipDegrees(deg, limit):
|
|
289
|
+
'''Clip a lat- or longitude to the given range.
|
|
290
|
+
|
|
291
|
+
@arg deg: Unclipped lat- or longitude (C{scalar degrees}).
|
|
292
|
+
@arg limit: Valid C{-/+B{limit}} range (C{degrees}).
|
|
293
|
+
|
|
294
|
+
@return: Clipped value (C{degrees}).
|
|
295
|
+
|
|
296
|
+
@raise RangeError: If B{C{deg}} outside the valid C{-/+B{limit}}
|
|
297
|
+
range and L{pygeodesy.rangerrors} set to C{True}.
|
|
298
|
+
'''
|
|
299
|
+
return _clip(deg, limit, _degrees_) if limit and limit > 0 else deg
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def clipRadians(rad, limit):
|
|
303
|
+
'''Clip a lat- or longitude to the given range.
|
|
304
|
+
|
|
305
|
+
@arg rad: Unclipped lat- or longitude (C{radians}).
|
|
306
|
+
@arg limit: Valid C{-/+B{limit}} range (C{radians}).
|
|
307
|
+
|
|
308
|
+
@return: Clipped value (C{radians}).
|
|
309
|
+
|
|
310
|
+
@raise RangeError: If B{C{rad}} outside the valid C{-/+B{limit}}
|
|
311
|
+
range and L{pygeodesy.rangerrors} set to C{True}.
|
|
312
|
+
'''
|
|
313
|
+
return _clip(rad, limit, _radians_) if limit and limit > 0 else rad
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def compassDMS(bearing, form=F_D, prec=None, sep=S_SEP, **s_D_M_S):
|
|
317
|
+
'''Convert bearing to a string suffixed with compass point.
|
|
318
|
+
|
|
319
|
+
@arg bearing: Bearing from North (compass C{degrees360}).
|
|
320
|
+
@kwarg form: Format specifier for B{C{deg}} (C{str} or L{F_D},
|
|
321
|
+
L{F_DM}, L{F_DMS}, L{F_DEG}, L{F_MIN}, L{F_SEC},
|
|
322
|
+
L{F_D60}, L{F__E}, L{F__F}, L{F__G}, L{F_RAD},
|
|
323
|
+
L{F_D_}, L{F_DM_}, L{F_DMS_}, L{F_DEG_}, L{F_MIN_},
|
|
324
|
+
L{F_SEC_}, L{F_D60_}, L{F__E_}, L{F__F_}, L{F__G_},
|
|
325
|
+
L{F_RAD_}, L{F_D__}, L{F_DM__}, L{F_DMS__}, L{F_DEG__},
|
|
326
|
+
L{F_MIN__}, L{F_SEC__}, L{F_D60__}, L{F__E__},
|
|
327
|
+
L{F__F__}, L{F__G__} or L{F_RAD__}).
|
|
328
|
+
@kwarg prec: Number of decimal digits (0..9 or C{None} for default).
|
|
329
|
+
Trailing zero decimals are stripped for B{C{prec}}
|
|
330
|
+
values of 1 and above, but kept for negative B{C{prec}}.
|
|
331
|
+
@kwarg sep: Separator between degrees, minutes, seconds, suffix (C{str}).
|
|
332
|
+
@kwarg s_D_M_S: Optional keyword arguments C{B{s_D}=str}, C{B{s_M}=str}
|
|
333
|
+
C{B{s_S}=str} and C{B{s_DMS}=True} to override any or
|
|
334
|
+
cancel all DMS symbols, defaults L{S_DEG}, L{S_MIN}
|
|
335
|
+
respectively L{S_SEC}.
|
|
336
|
+
|
|
337
|
+
@return: Compass degrees and point in the specified form (C{str}).
|
|
338
|
+
|
|
339
|
+
@see: Function L{pygeodesy.toDMS}.
|
|
340
|
+
'''
|
|
341
|
+
b = _umod_360(bearing)
|
|
342
|
+
return _toDMS(b, form, prec, sep, 1, compassPoint(b), s_D_M_S)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def compassPoint(bearing, prec=3):
|
|
346
|
+
'''Convert a C{bearing} from North to a compass point.
|
|
347
|
+
|
|
348
|
+
@arg bearing: Bearing (compass C{degrees360}).
|
|
349
|
+
@kwarg prec: Precision, number of compass point characters:
|
|
350
|
+
1 for cardinal or basic winds,
|
|
351
|
+
2 for intercardinal or ordinal or principal winds,
|
|
352
|
+
3 for secondary-intercardinal or half-winds or
|
|
353
|
+
4 for quarter-winds).
|
|
354
|
+
|
|
355
|
+
@return: Compass point (1-, 2-, 3- or 4-letter C{str}).
|
|
356
|
+
|
|
357
|
+
@raise ValueError: Invalid B{C{bearing}} or B{C{prec}}.
|
|
358
|
+
|
|
359
|
+
@see: U{Dms.compassPoint
|
|
360
|
+
<https://GitHub.com/ChrisVeness/geodesy/blob/master/dms.js>}
|
|
361
|
+
and U{Compass rose<https://WikiPedia.org/wiki/Compass_rose>}.
|
|
362
|
+
'''
|
|
363
|
+
try: # like .streprs.enstr2
|
|
364
|
+
b = _umod_360(bearing)
|
|
365
|
+
p = _MODS.units.Precision_(prec, low=1, high=4) \
|
|
366
|
+
if prec != 3 else int(prec)
|
|
367
|
+
m = 2 << p
|
|
368
|
+
w = 32 // m # if m in (4, 8, 16, 32)
|
|
369
|
+
# not round(b), half-even rounding in Python 3+, but
|
|
370
|
+
# round-away-from-zero as int(b + copysign0(_0_5, b))
|
|
371
|
+
w *= int(b * m / _360_0 + _0_5) % m
|
|
372
|
+
return _WINDS[w]
|
|
373
|
+
except Exception as x:
|
|
374
|
+
raise _ValueError(bearing=bearing, prec=prec, cause=x)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def degDMS(deg, prec=6, s_D=S_DEG, s_M=S_MIN, s_S=S_SEC, neg=_MINUS_, pos=NN):
|
|
378
|
+
'''Convert degrees to a string in degrees, minutes I{or} seconds.
|
|
379
|
+
|
|
380
|
+
@arg deg: Value in degrees (C{scalar degrees}).
|
|
381
|
+
@kwarg prec: Number of decimal digits (0..9 or C{None} for default).
|
|
382
|
+
Trailing zero decimals are stripped for B{C{prec}}
|
|
383
|
+
values of 1 and above, but kept for negative B{C{prec}}.
|
|
384
|
+
@kwarg s_D: D symbol for degrees (C{str}).
|
|
385
|
+
@kwarg s_M: M symbol for minutes (C{str}) or C{""}.
|
|
386
|
+
@kwarg s_S: S symbol for seconds (C{str}) or C{""}.
|
|
387
|
+
@kwarg neg: Optional sign for negative (C{'-'}).
|
|
388
|
+
@kwarg pos: Optional sign for positive (C{''}).
|
|
389
|
+
|
|
390
|
+
@return: I{Either} degrees, minutes I{or} seconds (C{str}).
|
|
391
|
+
|
|
392
|
+
@see: Function L{pygeodesy.toDMS}.
|
|
393
|
+
'''
|
|
394
|
+
try:
|
|
395
|
+
deg = float(deg)
|
|
396
|
+
except (TypeError, ValueError) as x:
|
|
397
|
+
raise _ValueError(deg=deg, prec=prec, cause=x)
|
|
398
|
+
|
|
399
|
+
d, s = fabs(deg), s_D
|
|
400
|
+
if d < 1:
|
|
401
|
+
if s_M:
|
|
402
|
+
d *= _60_0
|
|
403
|
+
if d < 1 and s_S:
|
|
404
|
+
d *= _60_0
|
|
405
|
+
s = s_S
|
|
406
|
+
else:
|
|
407
|
+
s = s_M
|
|
408
|
+
elif s_S:
|
|
409
|
+
d *= _3600_0
|
|
410
|
+
s = s_S
|
|
411
|
+
|
|
412
|
+
z = int(prec)
|
|
413
|
+
t = Fmt.F(d, prec=abs(z))
|
|
414
|
+
if z > 1:
|
|
415
|
+
t = fstrzs(t)
|
|
416
|
+
n = neg if deg < 0 else pos
|
|
417
|
+
return NN(n, t, s) # NOT unicode in Python 2-
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def latDMS(deg, form=_F_DMS, prec=None, sep=S_SEP, **s_D_M_S):
|
|
421
|
+
'''Convert latitude to a string, optionally suffixed with N or S.
|
|
422
|
+
|
|
423
|
+
@arg deg: Latitude to be formatted (C{scalar degrees}).
|
|
424
|
+
@kwarg form: Format specifier for B{C{deg}} (C{str} or L{F_D},
|
|
425
|
+
L{F_DM}, L{F_DMS}, L{F_DEG}, L{F_MIN}, L{F_SEC},
|
|
426
|
+
L{F_D60}, L{F__E}, L{F__F}, L{F__G}, L{F_RAD},
|
|
427
|
+
L{F_D_}, L{F_DM_}, L{F_DMS_}, L{F_DEG_}, L{F_MIN_},
|
|
428
|
+
L{F_SEC_}, L{F_D60_}, L{F__E_}, L{F__F_}, L{F__G_},
|
|
429
|
+
L{F_RAD_}, L{F_D__}, L{F_DM__}, L{F_DMS__}, L{F_DEG__},
|
|
430
|
+
L{F_MIN__}, L{F_SEC__}, L{F_D60__}, L{F__E__},
|
|
431
|
+
L{F__F__}, L{F__G__} or L{F_RAD__}).
|
|
432
|
+
@kwarg prec: Number of decimal digits (0..9 or C{None} for default).
|
|
433
|
+
Trailing zero decimals are stripped for B{C{prec}}
|
|
434
|
+
values of 1 and above, but kept for negative B{C{prec}}.
|
|
435
|
+
@kwarg sep: Separator between degrees, minutes, seconds, suffix (C{str}).
|
|
436
|
+
@kwarg s_D_M_S: Optional keyword arguments C{B{s_D}=str}, C{B{s_M}=str}
|
|
437
|
+
C{B{s_S}=str} and C{B{s_DMS}=True} to override any or
|
|
438
|
+
cancel all DMS symbols, defaults L{S_DEG}, L{S_MIN}
|
|
439
|
+
respectively L{S_SEC}.
|
|
440
|
+
|
|
441
|
+
@return: Degrees in the specified form (C{str}).
|
|
442
|
+
|
|
443
|
+
@see: Functions L{pygeodesy.toDMS} and L{pygeodesy.lonDMS}.
|
|
444
|
+
'''
|
|
445
|
+
p = _S_ if deg < 0 else _N_
|
|
446
|
+
return _toDMS(deg, form, prec, sep, 2, p, s_D_M_S)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def latlonDMS(lls, **m_form_prec_sep_s_D_M_S):
|
|
450
|
+
'''Convert one or more C{LatLon} instances to strings.
|
|
451
|
+
|
|
452
|
+
@arg lls: Single (C{LatLon}) or list, sequence, tuple, etc. (C{LatLon}s).
|
|
453
|
+
@kwarg m_form_prec_sep_s_D_M_S: Optional keyword arguments C{B{m}eter},
|
|
454
|
+
C{B{form}at}, C{B{prec}ision}, B{C{s_D}}, B{C{s_M}}, B{C{s_S}},
|
|
455
|
+
B{C{s_DMS}} and I{DEPRECATED} C{B{sep}=None}, see method
|
|
456
|
+
C{LatLon.toStr} and functions L{pygeodesy.latDMS} and
|
|
457
|
+
L{pygeodesy.lonDMS} for more details.
|
|
458
|
+
|
|
459
|
+
@return: A C{tuple} of C{str}s if B{C{lls}} is a list, sequence,
|
|
460
|
+
tuple, etc. of C{LatLon} instances or a single C{str}
|
|
461
|
+
if B{C{lls}} is a single C{LatLon}.
|
|
462
|
+
|
|
463
|
+
@see: Functions L{pygeodesy.latlonDMS_}, L{pygeodesy.latDMS},
|
|
464
|
+
L{pygeodesy.lonDMS} and L{pygeodesy.toDMS} and method
|
|
465
|
+
C{LatLon.toStr}.
|
|
466
|
+
|
|
467
|
+
@note: Keyword argument C{B{sep}=None} to join a C{str}ing
|
|
468
|
+
from the returned C{tuple} has been I{DEPRECATED},
|
|
469
|
+
use C{B{sep}.join(B{latlonDMS_}(...))} instead.
|
|
470
|
+
'''
|
|
471
|
+
sep, kwds = _latlonDMS_sep2(latlonDMS, **m_form_prec_sep_s_D_M_S)
|
|
472
|
+
if isLatLon(lls):
|
|
473
|
+
t = lls.toStr(**kwds)
|
|
474
|
+
elif issequence(lls):
|
|
475
|
+
t = tuple(ll.toStr(**kwds) for ll in lls)
|
|
476
|
+
if sep: # XXX TO BE REMOVED
|
|
477
|
+
t = sep.join(t)
|
|
478
|
+
else:
|
|
479
|
+
raise _TypeError(lls=lls, **m_form_prec_sep_s_D_M_S)
|
|
480
|
+
return t
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def latlonDMS_(*lls, **m_form_prec_sep_s_D_M_S):
|
|
484
|
+
'''Convert one or more C{LatLon} instances to strings.
|
|
485
|
+
|
|
486
|
+
@arg lls: The instances (C{LatLon}s), all positional arguments.
|
|
487
|
+
@kwarg m_form_prec_sep_s_D_M_S: Optional keyword arguments
|
|
488
|
+
C{B{m}eter}, C{B{form}at}, C{B{prec}ision}, B{C{s_D}},
|
|
489
|
+
B{C{s_M}}, B{C{s_S}}, B{C{s_DMS}} and I{DEPRECATED}
|
|
490
|
+
C{B{sep}=None}, see method C{LatLon.toStr} and
|
|
491
|
+
functions L{pygeodesy.latDMS} and L{pygeodesy.lonDMS}
|
|
492
|
+
for more details.
|
|
493
|
+
|
|
494
|
+
@return: A C{tuple} of C{str}s if 2 or more C{LatLon} instances
|
|
495
|
+
or a single C{str} if only a single C{LatLon} instance
|
|
496
|
+
is given in B{C{lls}}.
|
|
497
|
+
|
|
498
|
+
@see: Functions L{pygeodesy.latlonDMS}, L{pygeodesy.latDMS} and
|
|
499
|
+
L{pygeodesy.lonDMS} and L{pygeodesy.toDMS} and method
|
|
500
|
+
C{LatLon.toStr}.
|
|
501
|
+
|
|
502
|
+
@note: Keyword argument C{B{sep}=None} to join a C{str}ing
|
|
503
|
+
from the returned C{tuple} has been I{DEPRECATED},
|
|
504
|
+
use C{B{sep}.join(B{latlonDMS_}(...))} instead.
|
|
505
|
+
'''
|
|
506
|
+
sep, kwds = _latlonDMS_sep2(latlonDMS, **m_form_prec_sep_s_D_M_S)
|
|
507
|
+
if not lls:
|
|
508
|
+
raise _ValueError(lls=lls, **m_form_prec_sep_s_D_M_S)
|
|
509
|
+
elif len(lls) < 2:
|
|
510
|
+
lls, sep = lls[0], None
|
|
511
|
+
t = latlonDMS(lls, **kwds)
|
|
512
|
+
return sep.join(t) if sep else t
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _latlonDMS_sep2(where, sep=None, **kwds):
|
|
516
|
+
'''DEPRECATED, instead use: %r.join(%s(...))'''
|
|
517
|
+
if sep:
|
|
518
|
+
k = _SPACE_(_keyword_, _arg_, Fmt.EQUAL(sep=repr(sep)), _of_)
|
|
519
|
+
n = where.__name__
|
|
520
|
+
t = _latlonDMS_sep2.__doc__ % (sep, n)
|
|
521
|
+
_MODS.props._throwarning(k, n, t)
|
|
522
|
+
return sep, kwds
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def lonDMS(deg, form=_F_DMS, prec=None, sep=S_SEP, **s_D_M_S):
|
|
526
|
+
'''Convert longitude to a string, optionally suffixed with E or W.
|
|
527
|
+
|
|
528
|
+
@arg deg: Longitude to be formatted (C{scalar degrees}).
|
|
529
|
+
@kwarg form: Format specifier for B{C{deg}} (C{str} or L{F_D},
|
|
530
|
+
L{F_DM}, L{F_DMS}, L{F_DEG}, L{F_MIN}, L{F_SEC},
|
|
531
|
+
L{F_D60}, L{F__E}, L{F__F}, L{F__G}, L{F_RAD},
|
|
532
|
+
L{F_D_}, L{F_DM_}, L{F_DMS_}, L{F_DEG_}, L{F_MIN_},
|
|
533
|
+
L{F_SEC_}, L{F_D60_}, L{F__E_}, L{F__F_}, L{F__G_},
|
|
534
|
+
L{F_RAD_}, L{F_D__}, L{F_DM__}, L{F_DMS__}, L{F_DEG__},
|
|
535
|
+
L{F_MIN__}, L{F_SEC__}, L{F_D60__}, L{F__E__},
|
|
536
|
+
L{F__F__}, L{F__G__} or L{F_RAD__}).
|
|
537
|
+
@kwarg prec: Number of decimal digits (0..9 or C{None} for default).
|
|
538
|
+
Trailing zero decimals are stripped for B{C{prec}}
|
|
539
|
+
values of 1 and above, but kept for negative B{C{prec}}.
|
|
540
|
+
@kwarg sep: Separator between degrees, minutes, seconds, suffix (C{str}).
|
|
541
|
+
@kwarg s_D_M_S: Optional keyword arguments C{B{s_D}=str}, C{B{s_M}=str}
|
|
542
|
+
C{B{s_S}=str} and C{B{s_DMS}=True} to override any or
|
|
543
|
+
cancel all DMS symbols, defaults L{S_DEG}, L{S_MIN}
|
|
544
|
+
respectively L{S_SEC}.
|
|
545
|
+
|
|
546
|
+
@return: Degrees in the specified form (C{str}).
|
|
547
|
+
|
|
548
|
+
@see: Functions L{pygeodesy.toDMS} and L{pygeodesy.latDMS}.
|
|
549
|
+
'''
|
|
550
|
+
p = _W_ if deg < 0 else _E_
|
|
551
|
+
return _toDMS(deg, form, prec, sep, 3, p, s_D_M_S)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def normDMS(strDMS, norm=None, **s_D_M_S):
|
|
555
|
+
'''Normalize all degrees, minutes and seconds (DMS) I{symbols} in
|
|
556
|
+
a string to the default symbols L{S_DEG}, L{S_MIN}, L{S_SEC}.
|
|
557
|
+
|
|
558
|
+
@arg strDMS: Original DMS string (C{str}).
|
|
559
|
+
@kwarg norm: Optional replacement symbol (C{str}) or C{None} for
|
|
560
|
+
the default DMS symbols). Use C{B{norm}=""} to
|
|
561
|
+
remove all DMS symbols.
|
|
562
|
+
@kwarg s_D_M_S: Optional, alternate DMS symbols C{B{s_D}=str},
|
|
563
|
+
C{B{s_M}=str}, C{B{s_S}=str} and/or C{B{s_R}=str}
|
|
564
|
+
for radians, each to be replaced by B{C{norm}}.
|
|
565
|
+
|
|
566
|
+
@return: Normalized DMS (C{str}).
|
|
567
|
+
'''
|
|
568
|
+
def _s2S2(s_D=S_DEG, s_M=S_MIN, s_S=S_SEC, s_R=S_RAD):
|
|
569
|
+
d = {s_D: S_DEG, s_M: S_MIN, s_S: S_SEC, s_R: S_RAD}
|
|
570
|
+
for s, S in _xkwds(d, **_S_norm).items():
|
|
571
|
+
if s:
|
|
572
|
+
yield s, S
|
|
573
|
+
|
|
574
|
+
# XXX strDMS isn't unicode in Python 2- and looping
|
|
575
|
+
# thru strDMS will yield each byte, hence the loop
|
|
576
|
+
# thru _s2S2 and replacing the DMS symbols in strDMS
|
|
577
|
+
|
|
578
|
+
if norm is None: # back to default DMS
|
|
579
|
+
for s, S in _s2S2(**s_D_M_S):
|
|
580
|
+
if s != S:
|
|
581
|
+
strDMS = strDMS.replace(s, S)
|
|
582
|
+
|
|
583
|
+
else: # replace or remove all DMS
|
|
584
|
+
n = norm or NN
|
|
585
|
+
for s, _ in _s2S2(**s_D_M_S):
|
|
586
|
+
if s != n:
|
|
587
|
+
strDMS = strDMS.replace(s, n)
|
|
588
|
+
if n:
|
|
589
|
+
strDMS = strDMS.rstrip(n) # XXX not .strip?
|
|
590
|
+
|
|
591
|
+
return strDMS # NOT unicode in Python 2-
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def parseDDDMMSS(strDDDMMSS, suffix=_NSEW_, sep=S_SEP, clip=0, sexagecimal=False): # MCCABE 14
|
|
595
|
+
'''Parse a lat- or longitude represention forms as [D]DDMMSS in degrees.
|
|
596
|
+
|
|
597
|
+
@arg strDDDMMSS: Degrees in any of several forms (C{str}) and types (C{float},
|
|
598
|
+
C{int}, other).
|
|
599
|
+
@kwarg suffix: Optional, valid compass points (C{str}, C{tuple}).
|
|
600
|
+
@kwarg sep: Optional separator between "[D]DD", "MM", "SS", B{C{suffix}} (L{S_SEP}).
|
|
601
|
+
@kwarg clip: Optionally, limit value to range C{-/+B{clip}} (C{degrees}).
|
|
602
|
+
@kwarg sexagecimal: If C{True}, convert C{"D.MMSS"} or C{float(D.MMSS)} to
|
|
603
|
+
C{base-60} "MM" and "SS" digits. See C{form}s L{F_D60},
|
|
604
|
+
L{F_D60_} and L{F_D60__}.
|
|
605
|
+
|
|
606
|
+
@return: Degrees (C{float}).
|
|
607
|
+
|
|
608
|
+
@raise ParseError: Invalid B{C{strDDDMMSS}} or B{C{clip}} or the form of
|
|
609
|
+
B{C{strDDDMMSS}} is incompatible with the suffixed or
|
|
610
|
+
B{C{suffix}} compass point.
|
|
611
|
+
|
|
612
|
+
@raise RangeError: Value of B{C{strDDDMMSS}} outside the valid C{-/+B{clip}}
|
|
613
|
+
range and L{pygeodesy.rangerrors} set to C{True}.
|
|
614
|
+
|
|
615
|
+
@note: Type C{str} values "[D]DD", "[D]DDMM", "[D]DDMMSS" and "[D]DD.MMSS"
|
|
616
|
+
for B{C{strDDDMMSS}} are parsed properly only if I{either} unsigned
|
|
617
|
+
and suffixed with a valid, compatible, C{cardinal} L{compassPoint}
|
|
618
|
+
I{or} signed I{or} unsigned, unsuffixed and with keyword argument
|
|
619
|
+
B{C{suffix}="NS"}, B{C{suffix}="EW"} or a compatible L{compassPoint}.
|
|
620
|
+
|
|
621
|
+
@note: Unlike function L{parseDMS}, type C{float}, C{int} and other non-C{str}
|
|
622
|
+
B{C{strDDDMMSS}} values are interpreted as C{form} [D]DDMMSS or
|
|
623
|
+
[D]DD.MMSS. For example, C{int(1230)} is returned as 12.5 and I{not
|
|
624
|
+
1230.0} degrees. However, C{int(345)} is considered C{form} "DDD"
|
|
625
|
+
345 I{and not "DDMM" 0345}, unless B{C{suffix}} specifies the compass
|
|
626
|
+
point. Also, C{float(15.0523)} is returned as 15.0523 decimal
|
|
627
|
+
degrees and I{not 15°5′23″ sexagecimal}. To consider the latter, use
|
|
628
|
+
C{float(15.0523)} or C{"15.0523"} and specify the keyword argument
|
|
629
|
+
C{B{sexagecimal}=True}.
|
|
630
|
+
|
|
631
|
+
@see: Functions L{pygeodesy.parseDMS}, L{pygeodesy.parseDMS2} and
|
|
632
|
+
L{pygeodesy.parse3llh}.
|
|
633
|
+
'''
|
|
634
|
+
def _DDDMMSS(strDDDMMSS, suffix, sep, clip, sexagecimal):
|
|
635
|
+
S = suffix.upper()
|
|
636
|
+
if isstr(strDDDMMSS):
|
|
637
|
+
t = strDDDMMSS.replace(sep, NN) if sep else strDDDMMSS
|
|
638
|
+
s, t, P = _split3(t, S)
|
|
639
|
+
f = t.split(_DOT_)
|
|
640
|
+
n = len(f[0])
|
|
641
|
+
f = NN.join(f)
|
|
642
|
+
if 1 < n < 8 and f.isdigit() and ( # dddN/S/E/W or ddd or +/-ddd
|
|
643
|
+
(P in S and s.isdigit()) or
|
|
644
|
+
(P.isdigit() and s in _SDIGITS_ # PYCHOK indent
|
|
645
|
+
and S in _WINDS)):
|
|
646
|
+
# check [D]DDMMSS form and compass point
|
|
647
|
+
X = _EW_ if isodd(n) else _NS_
|
|
648
|
+
if not (P in X or (S in X and (P.isdigit() or P == _DOT_))):
|
|
649
|
+
t = _DDDMMSS_[int(X is _NS_):(n | 1)], _DASH_.join(X)
|
|
650
|
+
raise ParseError('form %s applies %s' % t)
|
|
651
|
+
elif not sexagecimal: # try other forms
|
|
652
|
+
return _DMS2deg(strDDDMMSS, S, sep, clip, {})
|
|
653
|
+
|
|
654
|
+
if sexagecimal: # move decimal dot from ...
|
|
655
|
+
n += 4 # ... [D]DD.MMSSs to [D]DDMMSS.s
|
|
656
|
+
if n < 6:
|
|
657
|
+
raise ParseError('%s digits (%s)' % (_sexagecimal_, n))
|
|
658
|
+
z = n - len(f) # zeros to append
|
|
659
|
+
t = (f + (_0_ * z)) if z > 0 else _DOT_(f[:n], f[n:])
|
|
660
|
+
f = _0_0 # fraction
|
|
661
|
+
|
|
662
|
+
else: # float or int to [D]DDMMSS[.fff]
|
|
663
|
+
f, m = float(strDDDMMSS), 0
|
|
664
|
+
if sexagecimal:
|
|
665
|
+
f *= _SEXAGECIMUL
|
|
666
|
+
m = 6
|
|
667
|
+
s = P = _0_ # anything except NN, _S_, _SW_, _W_
|
|
668
|
+
if f < 0:
|
|
669
|
+
f = -f
|
|
670
|
+
s = _MINUS_
|
|
671
|
+
f, i = modf(f) # returns ...
|
|
672
|
+
t = str(int(i)) # ... float(i)
|
|
673
|
+
n = len(t) # number of digits to ...
|
|
674
|
+
if n < m: # ... required min or ...
|
|
675
|
+
t = (_0_ * (m - n)) + t
|
|
676
|
+
# ... match the given compass point
|
|
677
|
+
elif S in (_NS_ if isodd(n) else _EW_):
|
|
678
|
+
t = _0_ + t
|
|
679
|
+
# P = S
|
|
680
|
+
# elif n > 1:
|
|
681
|
+
# P = (_EW_ if isodd(n) else _NS_)[0]
|
|
682
|
+
n = len(t)
|
|
683
|
+
|
|
684
|
+
if n < 4: # [D]DD[.ddd]
|
|
685
|
+
t = (float(t) + f),
|
|
686
|
+
else:
|
|
687
|
+
f += float(t[n-2:])
|
|
688
|
+
if n < 6: # [D]DDMM[.mmm]
|
|
689
|
+
t = float(t[:n-2]), f
|
|
690
|
+
else: # [D]DDMMSS[.sss]
|
|
691
|
+
t = float(t[:n-4]), float(t[n-4:n-2]), f
|
|
692
|
+
d = _dms2deg(s, P, *t)
|
|
693
|
+
return clipDegrees(d, float(clip)) if clip else d
|
|
694
|
+
|
|
695
|
+
return _parseX(_DDDMMSS, strDDDMMSS, suffix, sep, clip, sexagecimal,
|
|
696
|
+
strDDDMMSS=strDDDMMSS, suffix=suffix, sexagecimal=sexagecimal)
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _dms2deg(s, P, deg, min=_0_0, sec=_0_0):
|
|
700
|
+
'''(INTERNAL) Helper for C{parseDDDMMSS} and C{_DMS2deg}.
|
|
701
|
+
'''
|
|
702
|
+
deg += (min + (sec / _60_0)) / _60_0
|
|
703
|
+
if s == _MINUS_ or (P and P in _SW_):
|
|
704
|
+
deg = _neg(deg)
|
|
705
|
+
return deg
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def _DMS2deg(strDMS, suffix, sep, clip, s_D_M_S):
|
|
709
|
+
'''(INTERNAL) Helper for C{parseDDDMMSS} and C{parseDMS}.
|
|
710
|
+
'''
|
|
711
|
+
try:
|
|
712
|
+
d = float(strDMS)
|
|
713
|
+
|
|
714
|
+
except (TypeError, ValueError):
|
|
715
|
+
s, t, P = _split3(strDMS, suffix.upper())
|
|
716
|
+
if sep: # remove all DMS symbols
|
|
717
|
+
t = t.replace(sep, _SPACE_)
|
|
718
|
+
t = normDMS(t, norm=NN, **s_D_M_S)
|
|
719
|
+
else: # replace all DMS symbols
|
|
720
|
+
t = normDMS(t, norm=_SPACE_, **s_D_M_S)
|
|
721
|
+
t = map2(float, t.strip().split())
|
|
722
|
+
d = _dms2deg(s, P, *t[:3])
|
|
723
|
+
|
|
724
|
+
return clipDegrees(d, float(clip)) if clip else d
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def parseDMS(strDMS, suffix=_NSEW_, sep=S_SEP, clip=0, **s_D_M_S): # MCCABE 14
|
|
728
|
+
'''Parse a lat- or longitude representation in C{degrees}.
|
|
729
|
+
|
|
730
|
+
This is very flexible on formats, allowing signed decimal
|
|
731
|
+
degrees, degrees and minutes or degrees minutes and seconds
|
|
732
|
+
optionally suffixed by a cardinal compass point.
|
|
733
|
+
|
|
734
|
+
A variety of symbols, separators and suffixes are accepted,
|
|
735
|
+
for example "3°37′09″W". Minutes and seconds may be omitted.
|
|
736
|
+
|
|
737
|
+
@arg strDMS: Degrees in any of several forms (C{str}) and
|
|
738
|
+
types (C{float}, C{int}, other).
|
|
739
|
+
@kwarg suffix: Optional, valid compass points (C{str}, C{tuple}).
|
|
740
|
+
@kwarg sep: Optional separator between deg°, min′, sec″, B{C{suffix}} (C{''}).
|
|
741
|
+
@kwarg clip: Optionally, limit value to range C{-/+B{clip}} (C{degrees}).
|
|
742
|
+
@kwarg s_D_M_S: Optional, alternate symbol for degrees C{B{s_D}=str},
|
|
743
|
+
minutes C{B{s_M}=str} and/or seconds C{B{s_S}=str}.
|
|
744
|
+
|
|
745
|
+
@return: Degrees (C{float}).
|
|
746
|
+
|
|
747
|
+
@raise ParseError: Invalid B{C{strDMS}} or B{C{clip}}.
|
|
748
|
+
|
|
749
|
+
@raise RangeError: Value of B{C{strDMS}} outside the valid C{-/+B{clip}}
|
|
750
|
+
range and L{pygeodesy.rangerrors} set to C{True}.
|
|
751
|
+
|
|
752
|
+
@note: Unlike function L{parseDDDMMSS}, type C{float}, C{int} and other
|
|
753
|
+
non-C{str} B{C{strDMS}} values are considered decimal (and not
|
|
754
|
+
sexagecimal) degrees. For example, C{int(1230)} is returned
|
|
755
|
+
as 1230.0 I{and not as 12.5} degrees and C{float(345)} as 345.0
|
|
756
|
+
I{and not as 3.75} degrees!
|
|
757
|
+
|
|
758
|
+
@see: Functions L{pygeodesy.parseDDDMMSS}, L{pygeodesy.parseDMS2},
|
|
759
|
+
L{pygeodesy.parse3llh} and L{pygeodesy.toDMS}.
|
|
760
|
+
'''
|
|
761
|
+
return _parseX(_DMS2deg, strDMS, suffix, sep, clip, s_D_M_S, strDMS=strDMS, suffix=suffix)
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def parseDMS2(strLat, strLon, sep=S_SEP, clipLat=90, clipLon=180, wrap=False, **s_D_M_S):
|
|
765
|
+
'''Parse a lat- and a longitude representions C{"lat, lon"} in C{degrees}.
|
|
766
|
+
|
|
767
|
+
@arg strLat: Latitude in any of several forms (C{str} or C{degrees}).
|
|
768
|
+
@arg strLon: Longitude in any of several forms (C{str} or C{degrees}).
|
|
769
|
+
@kwarg sep: Optional separator between deg°, min′, sec″, suffix (C{''}).
|
|
770
|
+
@kwarg clipLat: Limit latitude to range C{-/+B{clipLat}} (C{degrees}).
|
|
771
|
+
@kwarg clipLon: Limit longitude to range C{-/+B{clipLon}} (C{degrees}).
|
|
772
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} the lat- and longitude,
|
|
773
|
+
overriding B{C{clipLat}} and B{C{clipLon}} (C{bool}).
|
|
774
|
+
@kwarg s_D_M_S: Optional, alternate symbol for degrees C{B{s_D}=str},
|
|
775
|
+
minutes C{B{s_M}=str} and/or seconds C{B{s_S}=str}.
|
|
776
|
+
|
|
777
|
+
@return: A L{LatLon2Tuple}C{(lat, lon)} in C{degrees}.
|
|
778
|
+
|
|
779
|
+
@raise ParseError: Invalid B{C{strLat}} or B{C{strLon}}.
|
|
780
|
+
|
|
781
|
+
@raise RangeError: Value of B{C{strLat}} or B{C{strLon}} outside the
|
|
782
|
+
valid C{-/+B{clipLat}} or C{-/+B{clipLon}} range
|
|
783
|
+
and L{pygeodesy.rangerrors} set to C{True}.
|
|
784
|
+
|
|
785
|
+
@note: See the B{Notes} at function L{parseDMS}.
|
|
786
|
+
|
|
787
|
+
@see: Functions L{pygeodesy.parseDDDMMSS}, L{pygeodesy.parseDMS},
|
|
788
|
+
L{pygeodesy.parse3llh} and L{pygeodesy.toDMS}.
|
|
789
|
+
'''
|
|
790
|
+
return _2Tuple(strLat, strLon, clipLat, clipLon, wrap, sep=sep, **s_D_M_S)
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def _2Tuple(strLat, strLon, clipLat, clipLon, wrap, **kwds):
|
|
794
|
+
'''(INTERNAL) Helper for C{parseDMS2} and C{parse3llh}.
|
|
795
|
+
'''
|
|
796
|
+
if wrap:
|
|
797
|
+
_W = _MODS.utily._Wrap
|
|
798
|
+
lat, lon = _W.latlon(parseDMS(strLat, suffix=_NS_, **kwds),
|
|
799
|
+
parseDMS(strLon, suffix=_EW_, **kwds))
|
|
800
|
+
else:
|
|
801
|
+
# if wrap is None:
|
|
802
|
+
# clipLat = clipLon = 0
|
|
803
|
+
lat = parseDMS(strLat, suffix=_NS_, clip=clipLat, **kwds)
|
|
804
|
+
lon = parseDMS(strLon, suffix=_EW_, clip=clipLon, **kwds)
|
|
805
|
+
return _MODS.namedTuples.LatLon2Tuple(lat, lon)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def parse3llh(strllh, height=0, sep=_COMMA_, clipLat=90, clipLon=180, wrap=False, **s_D_M_S):
|
|
809
|
+
'''Parse a string C{"lat, lon [, h]"} representing lat-, longitude in
|
|
810
|
+
C{degrees} and optional height in C{meter}.
|
|
811
|
+
|
|
812
|
+
The lat- and longitude value must be separated by a separator
|
|
813
|
+
character. If height is present it must follow, separated by
|
|
814
|
+
another separator.
|
|
815
|
+
|
|
816
|
+
The lat- and longitude values may be swapped, provided at least
|
|
817
|
+
one ends with the proper compass point.
|
|
818
|
+
|
|
819
|
+
@arg strllh: Latitude, longitude[, height] (C{str}, ...).
|
|
820
|
+
@kwarg height: Optional, default height (C{meter}) or C{None}.
|
|
821
|
+
@kwarg sep: Optional separator between C{"lat lon [h] suffix"} (C{str}).
|
|
822
|
+
@kwarg clipLat: Limit latitude to range C{-/+B{clipLat}} (C{degrees}).
|
|
823
|
+
@kwarg clipLon: Limit longitude to range C{-/+B{clipLon}} (C{degrees}).
|
|
824
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} the lat- and longitude,
|
|
825
|
+
overriding B{C{clipLat}} and B{C{clipLon}} (C{bool}).
|
|
826
|
+
@kwarg s_D_M_S: Optional, alternate symbol for degrees C{B{s_D}=str},
|
|
827
|
+
minutes C{B{s_M}=str} and/or seconds C{B{s_S}=str}.
|
|
828
|
+
|
|
829
|
+
@return: A L{LatLon3Tuple}C{(lat, lon, height)} in C{degrees},
|
|
830
|
+
C{degrees} and C{float}.
|
|
831
|
+
|
|
832
|
+
@raise RangeError: Lat- or longitude value of B{C{strllh}} outside
|
|
833
|
+
the valid C{-/+B{clipLat}} or C{-/+B{clipLon}}
|
|
834
|
+
range and L{pygeodesy.rangerrors} set to C{True}.
|
|
835
|
+
|
|
836
|
+
@raise ValueError: Invalid B{C{strllh}} or B{C{height}}.
|
|
837
|
+
|
|
838
|
+
@note: See the B{Notes} at function L{parseDMS}.
|
|
839
|
+
|
|
840
|
+
@see: Functions L{pygeodesy.parseDDDMMSS}, L{pygeodesy.parseDMS},
|
|
841
|
+
L{pygeodesy.parseDMS2} and L{pygeodesy.toDMS}.
|
|
842
|
+
'''
|
|
843
|
+
|
|
844
|
+
def _3llh(strllh, height, sep, wrap):
|
|
845
|
+
ll = strllh.strip().split(sep)
|
|
846
|
+
if len(ll) > 2: # XXX interpret height unit
|
|
847
|
+
h = float(ll.pop(2).rstrip(_LETTERS + _SPACE_))
|
|
848
|
+
else:
|
|
849
|
+
h = height # None from wgrs.Georef.__new__
|
|
850
|
+
if len(ll) != 2:
|
|
851
|
+
raise ValueError
|
|
852
|
+
|
|
853
|
+
a, b = [_.strip() for _ in ll] # PYCHOK false
|
|
854
|
+
if a[-1:] in _EW_ or b[-1:] in _NS_:
|
|
855
|
+
a, b = b, a
|
|
856
|
+
return _2Tuple(a, b, clipLat, clipLon, wrap, **s_D_M_S).to3Tuple(h)
|
|
857
|
+
|
|
858
|
+
return _parseX(_3llh, strllh, height, sep, wrap, strllh=strllh)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def parseRad(strRad, suffix=_NSEW_, clip=0):
|
|
862
|
+
'''Parse a string representing angle in C{radians}.
|
|
863
|
+
|
|
864
|
+
@arg strRad: Degrees in any of several forms (C{str} or C{radians}).
|
|
865
|
+
@kwarg suffix: Optional, valid compass points (C{str}, C{tuple}).
|
|
866
|
+
@kwarg clip: Optionally, limit value to range C{-/+B{clip}} (C{radians}).
|
|
867
|
+
|
|
868
|
+
@return: Radians (C{float}).
|
|
869
|
+
|
|
870
|
+
@raise ParseError: Invalid B{C{strRad}} or B{C{clip}}.
|
|
871
|
+
|
|
872
|
+
@raise RangeError: Value of B{C{strRad}} outside the valid C{-/+B{clip}}
|
|
873
|
+
range and L{pygeodesy.rangerrors} set to C{True}.
|
|
874
|
+
'''
|
|
875
|
+
def _Rad(strRad, suffix, clip):
|
|
876
|
+
try:
|
|
877
|
+
r = float(strRad)
|
|
878
|
+
|
|
879
|
+
except (TypeError, ValueError):
|
|
880
|
+
s, t, P = _split3(strRad, suffix.upper())
|
|
881
|
+
r = _dms2deg(s, P, float(t))
|
|
882
|
+
|
|
883
|
+
return clipRadians(r, float(clip)) if clip else r
|
|
884
|
+
|
|
885
|
+
return _parseX(_Rad, strRad, suffix, clip, strRad=strRad, suffix=suffix)
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
def precision(form, prec=None):
|
|
889
|
+
'''Set the default precison for a given F_ form.
|
|
890
|
+
|
|
891
|
+
@arg form: L{F_D}, L{F_DM}, L{F_DMS}, L{F_DEG}, L{F_MIN},
|
|
892
|
+
L{F_SEC}, L{F_D60}, L{F__E}, L{F__F}, L{F__G}
|
|
893
|
+
or L{F_RAD} (C{str}).
|
|
894
|
+
@kwarg prec: Number of decimal digits (0..9 or C{None} for
|
|
895
|
+
default). Trailing zero decimals are stripped
|
|
896
|
+
for B{C{prec}} values of 1 and above, but kept
|
|
897
|
+
for negative B{C{prec}}.
|
|
898
|
+
|
|
899
|
+
@return: Previous precision for the B{C{form}} (C{int}).
|
|
900
|
+
|
|
901
|
+
@raise ValueError: Invalid B{C{form}} or B{C{prec}} or B{C{prec}}
|
|
902
|
+
outside range C{-9..+9}.
|
|
903
|
+
'''
|
|
904
|
+
try:
|
|
905
|
+
p = _F_prec[form]
|
|
906
|
+
except KeyError:
|
|
907
|
+
raise _ValueError(form=form)
|
|
908
|
+
|
|
909
|
+
if prec is not None: # set as default
|
|
910
|
+
_F_prec[form] = _MODS.units.Precision_(prec, low=-9, high=9)
|
|
911
|
+
|
|
912
|
+
return p
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def toDMS(deg, form=_F_DMS, prec=2, sep=S_SEP, ddd=2, neg=_MINUS_, pos=_PLUS_, **s_D_M_S):
|
|
916
|
+
'''Convert I{signed} C{degrees} to string, without suffix.
|
|
917
|
+
|
|
918
|
+
@arg deg: Degrees to be formatted (C{scalar degrees}).
|
|
919
|
+
@kwarg form: Format specifier for B{C{deg}} (C{str} or L{F_D},
|
|
920
|
+
L{F_DM}, L{F_DMS}, L{F_DEG}, L{F_MIN}, L{F_SEC},
|
|
921
|
+
L{F_D60}, L{F__E}, L{F__F}, L{F__G}, L{F_RAD},
|
|
922
|
+
L{F_D_}, L{F_DM_}, L{F_DMS_}, L{F_DEG_}, L{F_MIN_},
|
|
923
|
+
L{F_SEC_}, L{F_D60_}, L{F__E_}, L{F__F_}, L{F__G_},
|
|
924
|
+
L{F_RAD_}, L{F_D__}, L{F_DM__}, L{F_DMS__}, L{F_DEG__},
|
|
925
|
+
L{F_MIN__}, L{F_SEC__}, L{F_D60__}, L{F__E__},
|
|
926
|
+
L{F__F__}, L{F__G__} or L{F_RAD__}).
|
|
927
|
+
@kwarg prec: Number of decimal digits (0..9 or C{None} for default).
|
|
928
|
+
Trailing zero decimals are stripped for B{C{prec}}
|
|
929
|
+
values of 1 and above, but kept for negative B{C{prec}}.
|
|
930
|
+
@kwarg sep: Separator between degrees, minutes, seconds, suffix (C{str}).
|
|
931
|
+
@kwarg ddd: Number of digits for B{C{deg}°} (2 or 3).
|
|
932
|
+
@kwarg neg: Prefix for negative B{C{deg}} (C{'-'}).
|
|
933
|
+
@kwarg pos: Prefix for positive B{C{deg}} and signed B{C{form}} (C{'+'}).
|
|
934
|
+
@kwarg s_D_M_S: Optional keyword arguments C{B{s_D}=str}, C{B{s_M}=str}
|
|
935
|
+
C{B{s_S}=str} and C{B{s_DMS}=True} to override any or
|
|
936
|
+
cancel all DMS symbols, defaults L{S_DEG}, L{S_MIN}
|
|
937
|
+
respectively L{S_SEC}. See B{Notes} below.
|
|
938
|
+
|
|
939
|
+
@return: Degrees in the specified form (C{str}).
|
|
940
|
+
|
|
941
|
+
@note: The degrees, minutes and seconds (DMS) symbol can be overridden in
|
|
942
|
+
this and other C{*DMS} functions by using optional keyword argments
|
|
943
|
+
C{B{s_D}="d"}, C{B{s_M}="'"} respectively C{B{s_S}='"'}. Using
|
|
944
|
+
keyword argument B{C{s_DMS}=None} cancels all C{DMS} symbols to
|
|
945
|
+
C{B{S_NUL}=NN}.
|
|
946
|
+
|
|
947
|
+
@note: Sexagecimal format B{C{F_D60}} supports overridable pseudo-DMS symbols
|
|
948
|
+
positioned at C{"[D]DD<B{s_D}>MM<B{s_M}>SS<B{s_S}>"} with defaults
|
|
949
|
+
C{B{s_D}="."}, C{B{s_M}=B{sep}} and C{B{s_S}=}L{pygeodesy.NN}.
|
|
950
|
+
|
|
951
|
+
@note: Formats B{C{F__E}}, B{C{F__F}} and B{C{F__G}} can be extended with
|
|
952
|
+
a C{D}-only symbol if defined with keyword argument C{B{s_D}=str}.
|
|
953
|
+
Likewise for B{C{F_RAD}} formats with keyword argument C{B{s_R}=str}.
|
|
954
|
+
|
|
955
|
+
@see: Function L{pygeodesy.degDMS}
|
|
956
|
+
'''
|
|
957
|
+
s = form[:1]
|
|
958
|
+
f = form[1:] if s in _PLUSMINUS_ else form
|
|
959
|
+
t = _toDMS(deg, f, prec, sep, ddd, NN, s_D_M_S) # unsigned and -suffixed
|
|
960
|
+
if deg < 0 and neg:
|
|
961
|
+
t = neg + t
|
|
962
|
+
elif deg > 0 and s == _PLUS_ and pos:
|
|
963
|
+
t = pos + t
|
|
964
|
+
return t
|
|
965
|
+
|
|
966
|
+
# **) MIT License
|
|
967
|
+
#
|
|
968
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
969
|
+
#
|
|
970
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
971
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
972
|
+
# to deal in the Software without restriction, including without limitation
|
|
973
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
974
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
975
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
976
|
+
#
|
|
977
|
+
# The above copyright notice and this permission notice shall be included
|
|
978
|
+
# in all copies or substantial portions of the Software.
|
|
979
|
+
#
|
|
980
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
981
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
982
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
983
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
984
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
985
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
986
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|