Coverage for pygeodesy/units.py: 96%

1

2# -*- coding: utf-8 -*-

3

4 u'''Various named units, all sub-classes of C{Float}, C{Int} or C{Str} from

5 basic C{float}, C{int} respectively C{str} to named units as L{Degrees},

6 L{Feet}, L{Meter}, L{Radians}, etc.

7'''

8

9 from pygeodesy.basics import isscalar, issubclassof, signOf, typename

10 from pygeodesy.constants import EPS, EPS1, PI, PI2, PI_2, _umod_360, _0_0, _0_001

11 from pygeodesy.dms import F__F, F__F_, S_NUL, S_SEP, parseDMS, parseRad, _toDMS

12 from pygeodesy.errors import _AssertionError, TRFError, UnitError, _xattr, _xcallable

13# from pygeodesy.internals import typename # from .basics

14 from pygeodesy.interns import NN, _azimuth_, _band_, _bearing_, _COMMASPACE_, \

15 _degrees_, _degrees2_, _distance_, _E_, _easting_, \

16 _epoch_, _EW_, _feet_, _height_, _lam_, _lat_, _lon_, \

17 _meter_, _meter2_, _N_, _negative_, _northing_, _radians_, \

18 _NS_, _NSEW_, _number_, _PERCENT_, _phi_, _precision_, \

19 _radians2_, _radius_, _S_, _scalar_, \

20 _W_, _zone_, _std_ # PYCHOK used!

21 from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS

22# from pygeodesy.named import _name__ # _MODS

23 from pygeodesy.props import Property_RO

24# from pygeodesy.streprs import Fmt, fstr # from .unitsBase

25 from pygeodesy.unitsBase import Float, Int, _NamedUnit, Radius, Str, Fmt, fstr

26

27 from math import degrees, isnan, radians

28

29 __all__ = _ALL_LAZY.units

30 __version__ = '25.05.12'

31

32

33 class Float_(Float):

34 '''Named C{float} with optional C{low} and C{high} limit.

35 '''

36 def __new__(cls, arg=None, name=NN, low=EPS, high=None, **Error_name_arg):

37 '''New, named C{Float_}, see L{Float}.

38

39 @arg cls: This class (C{Float_} or sub-class).

40 @kwarg arg: The value (any C{type} convertable to C{float}).

41 @kwarg name: Optional instance name (C{str}).

42 @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).

43 @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).

44

45 @returns: A named C{Float_}.

46

47 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.

48 '''

49 self = Float.__new__(cls, arg=arg, name=name, **Error_name_arg)

50 t = _xlimits(self, low, high, g=True)

51 if t:

52 raise _NamedUnit._Error(cls, arg, name, txt=t, **Error_name_arg)

53 return self

54

55

56 class Int_(Int):

57 '''Named C{int} with optional limits C{low} and C{high}.

58 '''

59 def __new__(cls, arg=None, name=NN, low=0, high=None, **Error_name_arg):

60 '''New, named C{int} instance with limits, see L{Int}.

61

62 @kwarg cls: This class (C{Int_} or sub-class).

63 @arg arg: The value (any C{type} convertable to C{int}).

64 @kwarg name: Optional instance name (C{str}).

65 @kwarg low: Optional lower B{C{arg}} limit (C{int} or C{None}).

66 @kwarg high: Optional upper B{C{arg}} limit (C{int} or C{None}).

67

68 @returns: A named L{Int_}.

69

70 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.

71 '''

72 self = Int.__new__(cls, arg=arg, name=name, **Error_name_arg)

73 t = _xlimits(self, low, high)

74 if t:

75 raise _NamedUnit._Error(cls, arg, name, txt=t, **Error_name_arg)

76 return self

77

78

79 class Bool(Int, _NamedUnit):

80 '''Named C{bool}, a sub-class of C{int} like Python's C{bool}.

81 '''

82 # _std_repr = True # set below

83 _bool_True_or_False = None

84

85 def __new__(cls, arg=None, name=NN, Error=UnitError, **name_arg):

86 '''New, named C{Bool}.

87

88 @kwarg cls: This class (C{Bool} or sub-class).

89 @kwarg arg: The value (any C{type} convertable to C{bool}).

90 @kwarg name: Optional instance name (C{str}).

91 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.

92 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of separate

93 B{C{arg}} and B{C{name}} ones.

94

95 @returns: A named L{Bool}, C{bool}-like.

96

97 @raise Error: Invalid B{C{arg}}.

98 '''

99 if name_arg:

100 name, arg = _NamedUnit._arg_name_arg2(arg, **name_arg)

101 try:

102 b = bool(arg)

103 except Exception as x:

104 raise _NamedUnit._Error(cls, arg, name, Error, cause=x)

105

106 self = Int.__new__(cls, b, name=name, Error=Error)

107 self._bool_True_or_False = b

108 return self

109

110 # <https://StackOverflow.com/questions/9787890/assign-class-boolean-value-in-python>

111 def __bool__(self): # PYCHOK Python 3+

112 return self._bool_True_or_False

113

114 __nonzero__ = __bool__ # PYCHOK Python 2-

115

116 def toRepr(self, std=False, **unused): # PYCHOK **unused

117 '''Return a representation of this C{Bool}.

118

119 @kwarg std: Use the standard C{repr} or the named representation (C{bool}).

120

121 @note: Use C{env} variable C{PYGEODESY_BOOL_STD_REPR=std} prior to C{import

122 pygeodesy} to get the standard C{repr} or set property C{std_repr=False}

123 to always get the named C{toRepr} representation.

124 '''

125 r = repr(self._bool_True_or_False)

126 return r if std else self._toRepr(r)

127

128 def toStr(self, **unused): # PYCHOK **unused

129 '''Return this C{Bool} as standard C{str}.

130 '''

131 return str(self._bool_True_or_False)

132

133

134 class Band(Str):

135 '''Named C{str} representing a UTM/UPS band letter, unchecked.

136 '''

137 def __new__(cls, arg=None, name=_band_, **Error_name_arg):

138 '''New, named L{Band}, see L{Str}.

139 '''

140 return Str.__new__(cls, arg=arg, name=name, **Error_name_arg)

141

142

143 class Degrees(Float):

144 '''Named C{float} representing a coordinate in C{degrees}, optionally clipped.

145 '''

146 _ddd_ = 1 # default for .dms._toDMS

147 _sep_ = S_SEP

148 _suf_ = (S_NUL,) * 3

149

150 def __new__(cls, arg=None, name=_degrees_, suffix=_NSEW_, clip=0, wrap=None, Error=UnitError, **name_arg):

151 '''New C{Degrees} instance, see L{Float}.

152

153 @arg cls: This class (C{Degrees} or sub-class).

154 @kwarg arg: The value (any C{type} convertable to C{float} or parsable by

155 function L{parseDMS<pygeodesy.dms.parseDMS>}).

156 @kwarg name: Optional instance name (C{str}).

157 @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).

158 @kwarg clip: Optional B{C{arg}} range B{C{-clip..+clip}} (C{degrees} or C{0}

159 or C{None} for unclipped).

160 @kwarg wrap: Optionally adjust the B{C{arg}} value (L{wrap90<pygeodesy.wrap90>},

161 L{wrap180<pygeodesy.wrap180>} or L{wrap360<pygeodesy.wrap360>}).

162 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.

163 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of separate

164 B{C{arg}} and B{C{name}} ones.

165

166 @returns: A C{Degrees} instance.

167

168 @raise Error: Invalid B{C{arg}} or B{C{abs(arg)}} outside the B{C{clip}}

169 range and L{rangerrors<pygeodesy.rangerrors>} is C{True}.

170 '''

171 if name_arg:

172 name, arg = _NamedUnit._arg_name_arg2(arg, name, **name_arg)

173 try:

174 arg = parseDMS(arg, suffix=suffix, clip=clip)

175 if wrap:

176 _xcallable(wrap=wrap)

177 arg = wrap(arg)

178 self = Float.__new__(cls, arg=arg, name=name, Error=Error)

179 except Exception as x:

180 raise _NamedUnit._Error(cls, arg, name, Error, cause=x)

181 return self

182

183 def toDegrees(self):

184 '''Convert C{Degrees} to C{Degrees}.

185 '''

186 return self

187

188 def toRadians(self):

189 '''Convert C{Degrees} to C{Radians}.

190 '''

191 return Radians(radians(self), name=self.name)

192

193 def toRepr(self, std=False, **prec_fmt_ints): # PYCHOK prec=8, ...

194 '''Return a representation of this C{Degrees}.

195

196 @kwarg std: If C{True}, return the standard C{repr}, otherwise

197 the named representation (C{bool}).

198

199 @see: Methods L{Degrees.toStr}, L{Float.toRepr} and function

200 L{pygeodesy.toDMS} for futher C{prec_fmt_ints} details.

201 '''

202 return Float.toRepr(self, std=std, **prec_fmt_ints)

203

204 def toStr(self, prec=None, fmt=F__F_, ints=False, **s_D_M_S): # PYCHOK prec=8, ...

205 '''Return this C{Degrees} as standard C{str}.

206

207 @see: Function L{pygeodesy.toDMS} for futher details.

208 '''

209 if fmt.startswith(_PERCENT_): # use regular formatting

210 p = 8 if prec is None else prec

211 return fstr(self, prec=p, fmt=fmt, ints=ints, sep=self._sep_)

212 else:

213 s = self._suf_[signOf(self) + 1]

214 return _toDMS(self, fmt, prec, self._sep_, self._ddd_, s, s_D_M_S)

215

216

217 class Degrees_(Degrees):

218 '''Named C{Degrees} representing a coordinate in C{degrees} with optional limits C{low} and C{high}.

219 '''

220 def __new__(cls, arg=None, name=_degrees_, low=None, high=None, **suffix_Error_name_arg):

221 '''New, named C{Degrees_}, see L{Degrees} and L{Float}.

222

223 @arg cls: This class (C{Degrees_} or sub-class).

224 @kwarg arg: The value (any C{type} convertable to C{float} or parsable by

225 function L{parseDMS<pygeodesy.dms.parseDMS>}).

226 @kwarg name: Optional instance name (C{str}).

227 @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).

228 @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).

229

230 @returns: A named C{Degrees}.

231

232 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.

233 '''

234 self = Degrees.__new__(cls, arg=arg, name=name, clip=0, **suffix_Error_name_arg)

235 t = _xlimits(self, low, high)

236 if t:

237 raise _NamedUnit._Error(cls, arg, name, txt=t, **suffix_Error_name_arg)

238 return self

239

240

241 class Degrees2(Float):

242 '''Named C{float} representing a distance in C{degrees squared}.

243 '''

244 def __new__(cls, arg=None, name=_degrees2_, **Error_name_arg):

245 '''See L{Float}.

246 '''

247 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)

248

249

250 class Radians(Float):

251 '''Named C{float} representing a coordinate in C{radians}, optionally clipped.

252 '''

253 def __new__(cls, arg=None, name=_radians_, suffix=_NSEW_, clip=0, Error=UnitError, **name_arg):

254 '''New, named C{Radians}, see L{Float}.

255

256 @arg cls: This class (C{Radians} or sub-class).

257 @kwarg arg: The value (any C{type} convertable to C{float} or parsable by

258 L{pygeodesy.parseRad}).

259 @kwarg name: Optional instance name (C{str}).

260 @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).

261 @kwarg clip: Optional B{C{arg}} range B{C{-clip..+clip}} (C{radians} or C{0}

262 or C{None} for unclipped).

263 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.

264 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of separate

265 B{C{arg}} and B{C{name}} ones.

266

267 @returns: A named C{Radians}.

268

269 @raise Error: Invalid B{C{arg}} or B{C{abs(arg)}} outside the B{C{clip}}

270 range and L{rangerrors<pygeodesy.rangerrors>} is C{True}.

271 '''

272 if name_arg:

273 name, arg = _NamedUnit._arg_name_arg2(arg, name, **name_arg)

274 try:

275 arg = parseRad(arg, suffix=suffix, clip=clip)

276 return Float.__new__(cls, arg, name=name, Error=Error)

277 except Exception as x:

278 raise _NamedUnit._Error(cls, arg, name, Error, cause=x)

279

280 def toDegrees(self):

281 '''Convert C{Radians} to C{Degrees}.

282 '''

283 return Degrees(degrees(self), name=self.name)

284

285 def toRadians(self):

286 '''Convert C{Radians} to C{Radians}.

287 '''

288 return self

289

290 def toRepr(self, std=False, **prec_fmt_ints): # PYCHOK prec=8, ...

291 '''Return a representation of this C{Radians}.

292

293 @kwarg std: If C{True}, return the standard C{repr}, otherwise

294 the named representation (C{bool}).

295

296 @see: Methods L{Radians.toStr}, L{Float.toRepr} and function

297 L{pygeodesy.toDMS} for more documentation.

298 '''

299 return Float.toRepr(self, std=std, **prec_fmt_ints)

300

301 def toStr(self, prec=8, fmt=F__F, ints=False): # PYCHOK prec=8, ...

302 '''Return this C{Radians} as standard C{str}.

303

304 @see: Function L{pygeodesy.fstr} for keyword argument details.

305 '''

306 return fstr(self, prec=prec, fmt=fmt, ints=ints)

307

308

309 class Radians_(Radians):

310 '''Named C{float} representing a coordinate in C{radians} with optional limits C{low} and C{high}.

311 '''

312 def __new__(cls, arg=None, name=_radians_, low=_0_0, high=PI2, **suffix_Error_name_arg):

313 '''New, named C{Radians_}, see L{Radians}.

314

315 @arg cls: This class (C{Radians_} or sub-class).

316 @kwarg arg: The value (any C{type} convertable to C{float} or parsable by

317 function L{parseRad<pygeodesy.dms.parseRad>}).

318 @kwarg name: Optional name (C{str}).

319 @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).

320 @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).

321

322 @returns: A named C{Radians_}.

323

324 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.

325 '''

326 self = Radians.__new__(cls, arg=arg, name=name, **suffix_Error_name_arg)

327 t = _xlimits(self, low, high)

328 if t:

329 raise _NamedUnit._Error(cls, arg, name, txt=t, **suffix_Error_name_arg)

330 return self

331

332

333 class Radians2(Float_):

334 '''Named C{float} representing a distance in C{radians squared}.

335 '''

336 def __new__(cls, arg=None, name=_radians2_, **Error_name_arg):

337 '''New, named L{Radians2}, see L{Float_}.

338 '''

339 return Float_.__new__(cls, arg=arg, name=name, low=_0_0, **Error_name_arg)

340

341

342 def _Degrees_new(cls, **arg_name_suffix_clip_Error_name_arg):

343 d = Degrees.__new__(cls, **arg_name_suffix_clip_Error_name_arg)

344 b = _umod_360(d) # 0 <= b < 360

345 return d if b == d else Degrees.__new__(cls, arg=b, name=d.name)

346

347

348 class Azimuth(Degrees):

349 '''Named C{float} representing an azimuth in compass C{degrees} from (true) North.

350 '''

351 _ddd_ = 1

352 _suf_ = _W_, S_NUL, _E_ # no zero suffix

353

354 def __new__(cls, arg=None, name=_azimuth_, **clip_Error_name_arg):

355 '''New, named L{Azimuth} with optional suffix 'E' for clockwise or 'W' for

356 anti-clockwise, see L{Degrees}.

357 '''

358 return _Degrees_new(cls, arg=arg, name=name, suffix=_EW_, **clip_Error_name_arg)

359

360

361 class Bearing(Degrees):

362 '''Named C{float} representing a bearing in compass C{degrees} from (true) North.

363 '''

364 _ddd_ = 1

365 _suf_ = _N_ * 3 # always suffix N

366

367 def __new__(cls, arg=None, name=_bearing_, **clip_Error_name_arg):

368 '''New, named L{Bearing}, see L{Degrees}.

369 '''

370 return _Degrees_new(cls, arg=arg, name=name, suffix=_N_, **clip_Error_name_arg)

371

372

373 class Bearing_(Radians):

374 '''Named C{float} representing a bearing in C{radians} from compass C{degrees} from (true) North.

375 '''

376 def __new__(cls, arg=None, **name_clip_Error_name_arg):

377 '''New, named L{Bearing_}, see L{Bearing} and L{Radians}.

378 '''

379 d = Bearing.__new__(cls, arg=arg, **name_clip_Error_name_arg)

380 return Radians.__new__(cls, radians(d), name=d.name)

381

382

383 class Distance(Float):

384 '''Named C{float} representing a distance, conventionally in C{meter}.

385 '''

386 def __new__(cls, arg=None, name=_distance_, **Error_name_arg):

387 '''New, named L{Distance}, see L{Float}.

388 '''

389 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)

390

391

392 class Distance_(Float_):

393 '''Named C{float} with optional C{low} and C{high} limits representing a distance, conventionally in C{meter}.

394 '''

395 def __new__(cls, arg=None, name=_distance_, **low_high_Error_name_arg):

396 '''New L{Distance_} instance, see L{Float}.

397 '''

398 return Float_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)

399

400

401 class _EasNorBase(Float):

402 '''(INTERNAL) L{Easting} and L{Northing} base class.

403 '''

404 def __new__(cls, arg, name, falsed, high, **Error_name_arg):

405 self = Float.__new__(cls, arg=arg, name=name, **Error_name_arg)

406 low = self < 0

407 if (high is not None) and (low or self > high): # like Veness

408 t = _negative_ if low else Fmt.limit(above=high)

409 elif low and falsed:

410 t = _COMMASPACE_(_negative_, 'falsed')

411 else:

412 return self

413 raise _NamedUnit._Error(cls, arg, name, txt=t, **Error_name_arg)

414

415

416 class Easting(_EasNorBase):

417 '''Named C{float} representing an easting, conventionally in C{meter}.

418 '''

419 def __new__(cls, arg=None, name=_easting_, falsed=False, high=None, **Error_name_arg):

420 '''New, named C{Easting} or C{Easting of Point}, see L{Float}.

421

422 @arg cls: This class (C{Easting} or sub-class).

423 @kwarg arg: The value (any C{type} convertable to C{float}).

424 @kwarg name: Optional name (C{str}).

425 @kwarg falsed: If C{True}, the B{C{arg}} value is falsed (C{bool}).

426 @kwarg high: Optional upper B{C{arg}} limit (C{scalar} or C{None}).

427

428 @returns: A named C{Easting}.

429

430 @raise Error: Invalid B{C{arg}}, above B{C{high}} or negative, falsed B{C{arg}}.

431 '''

432 return _EasNorBase.__new__(cls, arg, name, falsed, high, **Error_name_arg)

433

434

435 class Epoch(Float_): # in .ellipsoidalBase, .trf

436 '''Named C{epoch} with optional C{low} and C{high} limits representing a fractional

437 calendar year.

438 '''

439 _std_repr = False

440

441 def __new__(cls, arg=None, name=_epoch_, low=1900, high=9999, Error=TRFError, **name_arg):

442 '''New, named L{Epoch}, see L{Float_}.

443 '''

444 if name_arg:

445 name, arg = _NamedUnit._arg_name_arg2(arg, name, **name_arg)

446 return arg if isinstance(arg, Epoch) else Float_.__new__(cls,

447 arg=arg, name=name, Error=Error, low=low, high=high)

448

449 def toRepr(self, prec=3, std=False, **unused): # PYCHOK fmt=Fmt.F, ints=True

450 '''Return a representation of this C{Epoch}.

451

452 @kwarg std: Use the standard C{repr} or the named

453 representation (C{bool}).

454

455 @see: Method L{Float.toRepr} for more documentation.

456 '''

457 return Float_.toRepr(self, prec=prec, std=std) # fmt=Fmt.F, ints=True

458

459 def toStr(self, prec=3, **unused): # PYCHOK fmt=Fmt.F, nts=True

460 '''Format this C{Epoch} as C{str}.

461

462 @see: Function L{pygeodesy.fstr} for more documentation.

463 '''

464 return fstr(self, prec=prec, fmt=Fmt.F, ints=True)

465

466 __str__ = toStr # PYCHOK default '%.3F', with trailing zeros and decimal point

467

468

469 class Feet(Float):

470 '''Named C{float} representing a distance or length in C{feet}.

471 '''

472 def __new__(cls, arg=None, name=_feet_, **Error_name_arg):

473 '''New, named L{Feet}, see L{Float}.

474 '''

475 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)

476

477

478 class FIx(Float_):

479 '''A named I{Fractional Index}, an C{int} or C{float} index into a C{list}

480 or C{tuple} of C{points}, typically. A C{float} I{Fractional Index}

481 C{fi} represents a location on the edge between C{points[int(fi)]} and

482 C{points[(int(fi) + 1) % len(points)]}.

483 '''

484 _fin = 0

485

486 def __new__(cls, fi, fin=None, Error=UnitError, **name):

487 '''New, named I{Fractional Index} in a C{list} or C{tuple} of points.

488

489 @arg fi: The fractional index (C{float} or C{int}).

490 @kwarg fin: Optional C{len}, the number of C{points}, the index

491 C{[n]} wrapped to C{[0]} (C{int} or C{None}).

492 @kwarg Error: Optional error to raise.

493 @kwarg name: Optional C{B{name}=NN} (C{str}).

494

495 @return: A named B{C{fi}} (L{FIx}).

496

497 @note: The returned B{C{fi}} may exceed the B{C{len}}, number of

498 original C{points} in certain open/closed cases.

499

500 @see: Method L{fractional} or function L{pygeodesy.fractional}.

501 '''

502 _ = _MODS.named._name__(name) if name else NN # check error

503 n = Int_(fin=fin, low=0) if fin else None

504 f = Float_.__new__(cls, fi, low=_0_0, high=n, Error=Error, **name)

505 i = int(f)

506 r = f - float(i)

507 if r < EPS: # see .points._fractional

508 f = Float_.__new__(cls, i, low=_0_0, Error=Error, **name)

509 elif r > EPS1:

510 f = Float_.__new__(cls, i + 1, high=n, Error=Error, **name)

511 if n: # non-zero and non-None

512 f._fin = n

513 return f

514

515 @Property_RO

516 def fin(self):

517 '''Get the given C{len}, the index C{[n]} wrapped to C{[0]} (C{int}).

518 '''

519 return self._fin

520

521 def fractional(self, points, wrap=None, **LatLon_or_Vector_and_kwds):

522 '''Return the point at this I{Fractional Index}.

523

524 @arg points: The points (C{LatLon}[], L{Numpy2LatLon}[], L{Tuple2LatLon}[] or

525 C{other}[]).

526 @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the B{C{points}}

527 (C{bool}) or C{None} for backward compatible L{LatLon2Tuple} or

528 B{C{LatLon}} with I{averaged} lat- and longitudes.

529 @kwarg LatLon_or_Vector_and_kwds: Optional C{B{LatLon}=None} I{or} C{B{Vector}=None}

530 to return the I{intermediate}, I{fractional} point and optionally,

531 additional B{C{LatLon}} I{or} B{C{Vector}} keyword arguments, see

532 function L{fractional<pygeodesy.points.fractional>}.

533

534 @return: See function L{fractional<pygeodesy.points.fractional>}.

535

536 @raise IndexError: In fractional index invalid or B{C{points}} not

537 subscriptable or not closed.

538

539 @raise TypeError: Invalid B{C{LatLon}}, B{C{Vector}} or B{C{kwds}} argument.

540

541 @see: Function L{pygeodesy.fractional}.

542 '''

543 # fi = 0 if self == self.fin else self

544 return _MODS.points.fractional(points, self, wrap=wrap, **LatLon_or_Vector_and_kwds)

545

546

547 def _fi_j2(f, n): # PYCHOK in .ellipsoidalBaseDI, .vector3d

548 # Get 2-tuple (C{fi}, C{j})

549 i = int(f) # like L{FIx}

550 if not 0 <= i < n:

551 raise _AssertionError(i=i, n=n, f=f, r=f - float(i))

552 return FIx(fi=f, fin=n), (i + 1) % n

553

554

555 class Height(Float): # here to avoid circular import

556 '''Named C{float} representing a height, conventionally in C{meter}.

557 '''

558 def __new__(cls, arg=None, name=_height_, **Error_name_arg):

559 '''New, named L{Height}, see L{Float}.

560 '''

561 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)

562

563

564 class Height_(Float_): # here to avoid circular import

565 '''Named C{float} with optional C{low} and C{high} limits representing a height, conventionally in C{meter}.

566 '''

567 def __new__(cls, arg=None, name=_height_, **low_high_Error_name_arg):

568 '''New, named L{Height}, see L{Float}.

569 '''

570 return Float_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)

571

572

573 class HeightX(Height):

574 '''Like L{Height}, used to distinguish the interpolated height

575 from an original L{Height} at a clip intersection.

576 '''

577 pass

578

579

580 def _heigHt(inst, height):

581 '''(INTERNAL) Override the C{inst}ance' height.

582 '''

583 return inst.height if height is None else Height(height)

584

585

586 class Lam(Radians):

587 '''Named C{float} representing a longitude in C{radians}.

588 '''

589 def __new__(cls, arg=None, name=_lam_, clip=PI, **Error_name_arg):

590 '''New, named L{Lam}, see L{Radians}.

591 '''

592 return Radians.__new__(cls, arg=arg, name=name, suffix=_EW_, clip=clip, **Error_name_arg)

593

594

595 class Lamd(Lam):

596 '''Named C{float} representing a longitude in C{radians} converted from C{degrees}.

597 '''

598 def __new__(cls, arg=None, name=_lon_, clip=180, **Error_name_arg):

599 '''New, named L{Lamd}, see L{Lam} and L{Radians}.

600 '''

601 d = Degrees(arg=arg, name=name, clip=clip, **Error_name_arg)

602 return Lam.__new__(cls, radians(d), clip=radians(clip), name=d.name)

603

604

605 class Lat(Degrees):

606 '''Named C{float} representing a latitude in C{degrees}.

607 '''

608 _ddd_ = 2

609 _suf_ = _S_, S_NUL, _N_ # no zero suffix

610

611 def __new__(cls, arg=None, name=_lat_, clip=90, **Error_name_arg):

612 '''New, named L{Lat}, see L{Degrees}.

613 '''

614 return Degrees.__new__(cls, arg=arg, name=name, suffix=_NS_, clip=clip, **Error_name_arg)

615

616

617 class Lat_(Degrees_):

618 '''Named C{float} representing a latitude in C{degrees} within limits C{low} and C{high}.

619 '''

620 _ddd_ = 2

621 _suf_ = _S_, S_NUL, _N_ # no zero suffix

622

623 def __new__(cls, arg=None, name=_lat_, low=-90, high=90, **Error_name_arg):

624 '''See L{Degrees_}.

625 '''

626 return Degrees_.__new__(cls, arg=arg, name=name, suffix=_NS_, low=low, high=high, **Error_name_arg)

627

628

629 class Lon(Degrees):

630 '''Named C{float} representing a longitude in C{degrees}.

631 '''

632 _ddd_ = 3

633 _suf_ = _W_, S_NUL, _E_ # no zero suffix

634

635 def __new__(cls, arg=None, name=_lon_, clip=180, **Error_name_arg):

636 '''New, named L{Lon}, see L{Degrees}.

637 '''

638 return Degrees.__new__(cls, arg=arg, name=name, suffix=_EW_, clip=clip, **Error_name_arg)

639

640

641 class Lon_(Degrees_):

642 '''Named C{float} representing a longitude in C{degrees} within limits C{low} and C{high}.

643 '''

644 _ddd_ = 3

645 _suf_ = _W_, S_NUL, _E_ # no zero suffix

646

647 def __new__(cls, arg=None, name=_lon_, low=-180, high=180, **Error_name_arg):

648 '''New, named L{Lon_}, see L{Lon} and L{Degrees_}.

649 '''

650 return Degrees_.__new__(cls, arg=arg, name=name, suffix=_EW_, low=low, high=high, **Error_name_arg)

651

652

653 class Meter(Float):

654 '''Named C{float} representing a distance or length in C{meter}.

655 '''

656 def __new__(cls, arg=None, name=_meter_, **Error_name_arg):

657 '''New, named L{Meter}, see L{Float}.

658 '''

659 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)

660

661 def __repr__(self):

662 '''Return a representation of this C{Meter}.

663

664 @see: Method C{Str.toRepr} and property C{Str.std_repr}.

665

666 @note: Use C{env} variable C{PYGEODESY_METER_STD_REPR=std} prior to C{import

667 pygeodesy} to get the standard C{repr} or set property C{std_repr=False}

668 to always get the named C{toRepr} representation.

669 '''

670 return self.toRepr(std=self._std_repr)

671

672

673# _1Å = Meter( _Å= 1e-10) # PyCHOK 1 Ångstrōm

674 _1um = Meter( _1um= 1.e-6) # PYCHOK 1 micrometer in .mgrs

675 _10um = Meter( _10um= 1.e-5) # PYCHOK 10 micrometer in .osgr

676 _1mm = Meter( _1mm=_0_001) # PYCHOK 1 millimeter in .ellipsoidal...

677 _100km = Meter( _100km= 1.e+5) # PYCHOK 100 kilometer in .formy, .mgrs, .osgr, .sphericalBase

678 _2000km = Meter(_2000km= 2.e+6) # PYCHOK 2,000 kilometer in .mgrs

679

680

681 class Meter_(Float_):

682 '''Named C{float} representing a distance or length in C{meter}.

683 '''

684 def __new__(cls, arg=None, name=_meter_, low=_0_0, **high_Error_name_arg):

685 '''New, named L{Meter_}, see L{Meter} and L{Float_}.

686 '''

687 return Float_.__new__(cls, arg=arg, name=name, low=low, **high_Error_name_arg)

688

689

690 class Meter2(Float_):

691 '''Named C{float} representing an area in C{meter squared}.

692 '''

693 def __new__(cls, arg=None, name=_meter2_, **Error_name_arg):

694 '''New, named L{Meter2}, see L{Float_}.

695 '''

696 return Float_.__new__(cls, arg=arg, name=name, low=_0_0, **Error_name_arg)

697

698

699 class Meter3(Float_):

700 '''Named C{float} representing a volume in C{meter cubed}.

701 '''

702 def __new__(cls, arg=None, name='meter3', **Error_name_arg):

703 '''New, named L{Meter3}, see L{Float_}.

704 '''

705 return Float_.__new__(cls, arg=arg, name=name, low=_0_0, **Error_name_arg)

706

707

708 class Northing(_EasNorBase):

709 '''Named C{float} representing a northing, conventionally in C{meter}.

710 '''

711 def __new__(cls, arg=None, name=_northing_, falsed=False, high=None, **Error_name_arg):

712 '''New, named C{Northing} or C{Northing of point}, see L{Float}.

713

714 @arg cls: This class (C{Northing} or sub-class).

715 @kwarg arg: The value (any C{type} convertable to C{float}).

716 @kwarg name: Optional name (C{str}).

717 @kwarg falsed: If C{True}, the B{C{arg}} value is falsed (C{bool}).

718 @kwarg high: Optional upper B{C{arg}} limit (C{scalar} or C{None}).

719

720 @returns: A named C{Northing}.

721

722 @raise Error: Invalid B{C{arg}}, above B{C{high}} or negative, falsed B{C{arg}}.

723 '''

724 return _EasNorBase.__new__(cls, arg, name, falsed, high, **Error_name_arg)

725

726

727 class Number_(Int_):

728 '''Named C{int} representing a non-negative number.

729 '''

730 def __new__(cls, arg=None, name=_number_, **low_high_Error_name_arg):

731 '''New, named L{Number_}, see L{Int_}.

732 '''

733 return Int_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)

734

735

736 class Phi(Radians):

737 '''Named C{float} representing a latitude in C{radians}.

738 '''

739 def __new__(cls, arg=None, name=_phi_, clip=PI_2, **Error_name_arg):

740 '''New, named L{Phi}, see L{Radians}.

741 '''

742 return Radians.__new__(cls, arg=arg, name=name, suffix=_NS_, clip=clip, **Error_name_arg)

743

744

745 class Phid(Phi):

746 '''Named C{float} representing a latitude in C{radians} converted from C{degrees}.

747 '''

748 def __new__(cls, arg=None, name=_lat_, clip=90, **Error_name_arg):

749 '''New, named L{Phid}, see L{Phi} and L{Radians}.

750 '''

751 d = Degrees(arg=arg, name=name, clip=clip, **Error_name_arg)

752 return Phi.__new__(cls, arg=radians(d), clip=radians(clip), name=d.name)

753

754

755 class Precision_(Int_):

756 '''Named C{int} with optional C{low} and C{high} limits representing a precision.

757 '''

758 def __new__(cls, arg=None, name=_precision_, **low_high_Error_name_arg):

759 '''New, named L{Precision_}, see L{Int_}.

760 '''

761 return Int_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)

762

763

764 class Radius_(Float_):

765 '''Named C{float} with optional C{low} and C{high} limits representing a radius, conventionally in C{meter}.

766 '''

767 def __new__(cls, arg=None, name=_radius_, **low_high_Error_name_arg):

768 '''New, named L{Radius_}, see L{Radius} and L{Float}.

769 '''

770 return Float_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)

771

772

773 class Scalar(Float):

774 '''Named C{float} representing a factor, fraction, scale, etc.

775 '''

776 def __new__(cls, arg=None, name=_scalar_, **Error_name_arg):

777 '''New, named L{Scalar}, see L{Float}.

778 '''

779 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)

780

781

782 class Scalar_(Float_):

783 '''Named C{float} with optional C{low} and C{high} limits representing a factor, fraction, scale, etc.

784 '''

785 def __new__(cls, arg=None, name=_scalar_, low=_0_0, **high_Error_name_arg):

786 '''New, named L{Scalar_}, see L{Scalar} and L{Float_}.

787 '''

788 return Float_.__new__(cls, arg=arg, name=name, low=low, **high_Error_name_arg)

789

790

791 class Zone(Int):

792 '''Named C{int} representing a UTM/UPS zone number.

793 '''

794 def __new__(cls, arg=None, name=_zone_, **Error_name_arg):

795 '''New, named L{Zone}, see L{Int}

796 '''

797 # usually low=_UTMUPS_ZONE_MIN, high=_UTMUPS_ZONE_MAX

798 return Int_.__new__(cls, arg=arg, name=name, **Error_name_arg)

799

800

801 _Degrees = (Azimuth, Bearing, Bearing_, Degrees, Degrees_)

802 _Meters = (Distance, Distance_, Meter, Meter_)

803 _Radians = (Radians, Radians_) # PYCHOK unused

804 _Radii = _Meters + (Radius, Radius_)

805 _ScalarU = Float, Float_, Scalar, Scalar_

806

807

808 def _isDegrees(obj, iscalar=True):

809 # Check for valid degrees types.

810 return isinstance(obj, _Degrees) or (iscalar and _isScalar(obj))

811

812

813 def _isHeight(obj, iscalar=True):

814 # Check for valid height types.

815 return isinstance(obj, _Meters) or (iscalar and _isScalar(obj))

816

817

818 def _isMeter(obj, iscalar=True):

819 # Check for valid meter types.

820 return isinstance(obj, _Meters) or (iscalar and _isScalar(obj))

821

822

823 def _isRadians(obj, iscalar=True):

824 # Check for valid radian types.

825 return isinstance(obj, _Radians) or (iscalar and _isScalar(obj))

826

827

828 def _isRadius(obj, iscalar=True):

829 # Check for valid earth radius types.

830 return isinstance(obj, _Radii) or (iscalar and _isScalar(obj))

831

832

833 def _isScalar(obj, iscalar=True):

834 # Check for pure scalar types.

835 return isinstance(obj, _ScalarU) or (iscalar and isscalar(obj) and not isinstance(obj, _NamedUnit))

836

837

838 def _toUnit(Unit, arg, name=NN, **Error):

839 '''(INTERNAL) Wrap C{arg} in a C{name}d C{Unit}.

840 '''

841 if not (issubclassof(Unit, _NamedUnit) and isinstance(arg, Unit) and

842 _xattr(arg, name=NN) == name):

843 arg = Unit(arg, name=name, **Error)

844 return arg

845

846

847 def _xlimits(arg, low, high, g=False):

848 '''(INTERNAL) Check C{low <= arg <= high}.

849 '''

850 if (low is not None) and (arg < low or isnan(arg)):

851 if g:

852 low = Fmt.g(low, prec=6, ints=isinstance(arg, Epoch))

853 t = Fmt.limit(below=low)

854 elif (high is not None) and (arg > high or isnan(arg)):

855 if g:

856 high = Fmt.g(high, prec=6, ints=isinstance(arg, Epoch))

857 t = Fmt.limit(above=high)

858 else:

859 t = NN

860 return t

861

862

863 def _std_repr(*Classes):

864 '''(INTERNAL) Use standard C{repr} or named C{toRepr}.

865 '''

866 from pygeodesy.internals import _getenv

867 for C in Classes:

868 if hasattr(C, typename(_std_repr)): # noqa: F821 del

869 env = 'PYGEODESY_%s_STD_REPR' % (typename(C).upper(),)

870 if _getenv(env, _std_).lower() != _std_:

871 C._std_repr = False

872

873 _std_repr(Azimuth, Bearing, Bool, Degrees, Epoch, Float, Int, Meter, Radians, Str) # PYCHOK expected

874 del _std_repr

875

876 __all__ += _ALL_DOCS(_NamedUnit)

877

878# **) MIT License

879#

880# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved.

881#

882# Permission is hereby granted, free of charge, to any person obtaining a

883# copy of this software and associated documentation files (the "Software"),

884# to deal in the Software without restriction, including without limitation

885# the rights to use, copy, modify, merge, publish, distribute, sublicense,

886# and/or sell copies of the Software, and to permit persons to whom the

887# Software is furnished to do so, subject to the following conditions:

888#

889# The above copyright notice and this permission notice shall be included

890# in all copies or substantial portions of the Software.

891#

892# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS

893# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

894# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL

895# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR

896# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,

897# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR

898# OTHER DEALINGS IN THE SOFTWARE.