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, isstr, issubclassof, signOf, _xsubclassof

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

11 _0_001, _0_5, INT0 # PYCHOK for .mgrs, .namedTuples

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

13 from pygeodesy.errors import _AssertionError, _IsnotError, TRFError, UnitError, \

14 _xattr

15 from pygeodesy.interns import NN, _band_, _bearing_, _degrees_, _degrees2_, \

16 _distance_, _E_, _easting_, _epoch_, _EW_, _feet_, \

17 _height_, _lam_, _lat_, _LatLon_, _lon_, _meter_, \

18 _meter2_, _N_, _northing_, _NS_, _NSEW_, _number_, \

19 _PERCENT_, _phi_, _precision_, _radians_, _radians2_, \

20 _radius_, _S_, _scalar_, _units_, _W_, _zone_, \

21 _std_ # PYCHOK used!

22 from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, _getenv

23 from pygeodesy.props import Property_RO

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

25 from pygeodesy.unitsBase import _Error, Float, Fmt, fstr, Int, _arg_name_arg2, \

26 _NamedUnit, Radius, Str # PYCHOK shared .namedTuples

27

28 from math import degrees, radians

29

30__all__ = _ALL_LAZY.units

31__version__ = '24.06.10'

32

33_negative_falsed_ = 'negative, falsed'

34

35

36 class Float_(Float):

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

38 '''

39 def __new__(cls, arg=None, name=NN, Error=UnitError, low=EPS, high=None, **name_arg):

40 '''New C{Float_} instance.

41

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

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

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

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

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

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

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

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

50

51 @returns: A C{Float_} instance.

52

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

54 '''

55 if name_arg:

56 name, arg = _arg_name_arg2(arg, **name_arg)

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

58 if (low is not None) and self < low:

59 txt = Fmt.limit(below=Fmt.g(low, prec=6, ints=isinstance(self, Epoch)))

60 elif (high is not None) and self > high:

61 txt = Fmt.limit(above=Fmt.g(high, prec=6, ints=isinstance(self, Epoch)))

62 else:

63 return self

64 raise _Error(cls, arg, name, Error, txt=txt)

65

66

67 class Int_(Int):

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

69 '''

70 def __new__(cls, arg=None, name=NN, Error=UnitError, low=0, high=None, **name_arg):

71 '''New named C{int} instance with limits.

72

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

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

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

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

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

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

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

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

81

82 @returns: An L{Int_} instance.

83

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

85 '''

86 if name_arg:

87 name, arg = _arg_name_arg2(arg, **name_arg)

88 self = Int.__new__(cls, arg=arg, name=name, Error=Error)

89 if (low is not None) and self < low:

90 txt = Fmt.limit(below=low)

91 elif (high is not None) and self > high:

92 txt = Fmt.limit(above=high)

93 else:

94 return self

95 raise _Error(cls, arg, name, Error, txt=txt)

96

97

98 class Bool(Int, _NamedUnit):

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

100 '''

101 # _std_repr = True # set below

102 _bool_True_or_False = None

103

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

105 '''New C{Bool} instance.

106

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

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

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

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

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

112 B{C{name}} and B{C{arg}}.

113

114 @returns: A L{Bool}, a C{bool}-like instance.

115

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

117 '''

118 if name_arg:

119 name, arg = _arg_name_arg2(arg, **name_arg)

120 try:

121 b = bool(arg)

122 except Exception as x: # XXX not ... as x:

123 raise _Error(cls, arg, name, Error, x=x)

124

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

126 self._bool_True_or_False = b

127 return self

128

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

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

131 return self._bool_True_or_False

132

133 __nonzero__ = __bool__ # PYCHOK Python 2-

134

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

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

137

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

139

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

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

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

143 '''

144 r = repr(self._bool_True_or_False)

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

146

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

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

149 '''

150 return str(self._bool_True_or_False)

151

152

153 class Band(Str):

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

155 '''

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

157 '''New L{Band} instance, see L{Str}.

158 '''

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

160

161

162 class Degrees(Float):

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

164 '''

165 _ddd_ = 1 # default for .dms._toDMS

166 _sep_ = S_SEP

167 _suf_ = (S_NUL,) * 3

168

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

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

171

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

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

174 by L{pygeodesy.parseDMS}).

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

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

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

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

179 or C{None} for unclipped).

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

181 L{pygeodesy.wrap180} or L{pygeodesy.wrap360}).

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

183 B{C{name}} and B{C{arg}}.

184

185 @returns: A C{Degrees} instance.

186

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

188 range and L{pygeodesy.rangerrors} set to C{True}.

189 '''

190 if name_arg:

191 name, arg = _arg_name_arg2(arg, **name_arg)

192 try:

193 d = Float.__new__(cls, parseDMS(arg, suffix=suffix, clip=clip),

194 Error=Error, name=name)

195 if wrap:

196 w = wrap(d)

197 if w != d:

198 d = Float.__new__(cls, arg=w, name=name, Error=Error)

199 except Exception as x:

200 raise _Error(cls, arg, name, Error, x=x)

201 return d

202

203 def toDegrees(self):

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

205 '''

206 return self

207

208 def toRadians(self):

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

210 '''

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

212

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

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

215

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

217 the named representation (C{bool}).

218

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

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

221 '''

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

223

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

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

226

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

228 '''

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

230 p = 8 if prec is None else prec

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

232 else:

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

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

235

236

237 class Degrees_(Degrees):

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

239 '''

240 def __new__(cls, arg=None, name=_degrees_, Error=UnitError, suffix=_NSEW_, low=None, high=None, **name_arg):

241 '''New C{Degrees_} instance, see L{Degrees} and L{Float}..

242

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

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

245 L{pygeodesy.parseDMS}).

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

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

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

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

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

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

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

253

254 @returns: A C{Degrees} instance.

255

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

257 '''

258 if name_arg:

259 name, arg = _arg_name_arg2(arg, **name_arg)

260 self = Degrees.__new__(cls, arg=arg, name=name, Error=Error, suffix=suffix, clip=0)

261 if (low is not None) and self < low:

262 txt = Fmt.limit(below=low)

263 elif (high is not None) and self > high:

264 txt = Fmt.limit(above=high)

265 else:

266 return self

267 raise _Error(cls, arg, name, Error, txt=txt)

268

269

270 class Degrees2(Float):

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

272 '''

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

274 '''See L{Float}.

275 '''

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

277

278

279 class Radians(Float):

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

281 '''

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

283 '''New C{Radians} instance, see L{Float}.

284

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

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

287 L{pygeodesy.parseRad}).

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

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

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

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

292 or C{None} for unclipped).

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

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

295

296 @returns: A C{Radians} instance.

297

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

299 range and L{pygeodesy.rangerrors} set to C{True}.

300 '''

301 if name_arg:

302 name, arg = _arg_name_arg2(arg, **name_arg)

303 try:

304 return Float.__new__(cls, parseRad(arg, suffix=suffix, clip=clip),

305 Error=Error, name=name)

306 except Exception as x:

307 raise _Error(cls, arg, name, Error, x=x)

308

309 def toDegrees(self):

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

311 '''

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

313

314 def toRadians(self):

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

316 '''

317 return self

318

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

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

321

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

323 the named representation (C{bool}).

324

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

326 L{pygeodesy.toDMS} for more documentation.

327 '''

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

329

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

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

332

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

334 '''

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

336

337

338 class Radians_(Radians):

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

340 '''

341 def __new__(cls, arg=None, name=_radians_, Error=UnitError, suffix=_NSEW_, low=_0_0, high=PI2, **name_arg):

342 '''New C{Radians_} instance.

343

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

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

346 L{pygeodesy.parseRad}).

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

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

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

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

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

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

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

354

355 @returns: A C{Radians_} instance.

356

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

358 '''

359 if name_arg:

360 name, arg = _arg_name_arg2(arg, **name_arg)

361 self = Radians.__new__(cls, arg=arg, name=name, Error=Error, suffix=suffix, clip=0)

362 if (low is not None) and self < low:

363 txt = Fmt.limit(below=low)

364 elif (high is not None) and self > high:

365 txt = Fmt.limit(above=high)

366 else:

367 return self

368 raise _Error(cls, arg, name, Error, txt=txt)

369

370

371 class Radians2(Float_):

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

373 '''

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

375 '''New L{Radians2} instance, see L{Float_}.

376 '''

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

378

379

380 class Bearing(Degrees):

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

382 '''

383 _ddd_ = 1

384 _suf_ = _N_ * 3 # always suffix N

385

386 def __new__(cls, arg=None, name=_bearing_, Error=UnitError, clip=0, **name_arg):

387 '''New L{Bearing} instance, see L{Degrees}.

388 '''

389 if name_arg:

390 name, arg = _arg_name_arg2(arg, **name_arg)

391 d = Degrees.__new__(cls, arg=arg, name=name, Error=Error, suffix=_N_, clip=clip)

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

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

394

395

396 class Bearing_(Radians):

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

398 '''

399 def __new__(cls, arg=None, name=_bearing_, clip=0, **Error_name_arg):

400 '''New L{Bearing_} instance, see L{Bearing} and L{Radians}.

401 '''

402 d = Bearing.__new__(cls, arg=arg, name=name, clip=clip, **Error_name_arg)

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

404

405

406 class Distance(Float):

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

408 '''

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

410 '''New L{Distance} instance, see L{Float}.

411 '''

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

413

414

415 class Distance_(Float_):

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

417 '''

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

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

420 '''

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

422

423

424 class Easting(Float):

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

426 '''

427 def __new__(cls, arg=None, name=_easting_, Error=UnitError, falsed=False, high=None, **name_arg):

428 '''New named C{Easting} or C{Easting of Point} instance.

429

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

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

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

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

434 @kwarg falsed: The B{C{arg}} value includes false origin (C{bool}).

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

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

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

438

439 @returns: An C{Easting} instance.

440

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

442 '''

443 if name_arg:

444 name, arg = _arg_name_arg2(arg, **name_arg)

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

446 if high and (self < 0 or self > high): # like Veness

447 raise _Error(cls, arg, name, Error)

448 elif falsed and self < 0:

449 raise _Error(cls, arg, name, Error, txt=_negative_falsed_)

450 return self

451

452

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

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

455 calendar year.

456 '''

457 _std_repr = False

458

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

460 '''New L{Epoch} instance, see L{Float_}.

461 '''

462 if name_arg:

463 name, arg = _arg_name_arg2(arg, **name_arg)

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

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

466

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

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

469

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

471 representation (C{bool}).

472

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

474 '''

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

476

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

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

479

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

481 '''

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

483

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

485

486

487 class Feet(Float):

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

489 '''

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

491 '''New L{Feet} instance, see L{Float}.

492 '''

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

494

495

496 class FIx(Float_):

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

498 a C{list} or C{tuple} of C{points}, typically. A C{float}

499 I{Fractional Index} C{fi} represents a location on the edge

500 between C{points[int(fi)]} and C{points[(int(fi) + 1) %

501 len(points)]}.

502 '''

503 _fin = 0

504

505 def __new__(cls, fi, fin=None, **name_Error):

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

507

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

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

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

511 @kwarg name_Error: Optional C{B{name}=NN} (C{str}) and keyword

512 argument C{B{Error}=UnitError}.

513

514 @return: The B{C{fi}} (named L{FIx}).

515

516 @note: The returned B{C{fi}} may exceed the B{C{flen}} of

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

518

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

520 '''

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

522 f = Float_.__new__(cls, fi, low=_0_0, high=n, **name_Error)

523 i = int(f)

524 r = f - float(i)

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

526 f = Float_.__new__(cls, i, low=_0_0)

527 elif r > EPS1:

528 f = Float_.__new__(cls, i + 1, high=n, **name_Error)

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

530 f._fin = n

531 return f

532

533 @Property_RO

534 def fin(self):

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

536 '''

537 return self._fin

538

539 def fractional(self, points, wrap=None, LatLon=None, Vector=None, **kwds):

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

541

542 @arg points: The points (C{LatLon}[], L{Numpy2LatLon}[],

543 L{Tuple2LatLon}[] or C{other}[]).

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

545 B{C{points}} (C{bool}) or C{None} for backward

546 compatible L{LatLon2Tuple} or B{C{LatLon}} with

547 I{averaged} lat- and longitudes.

548 @kwarg LatLon: Optional class to return the I{intermediate},

549 I{fractional} point (C{LatLon}) or C{None}.

550 @kwarg Vector: Optional class to return the I{intermediate},

551 I{fractional} point (C{Cartesian}, C{Vector3d})

552 or C{None}.

553 @kwarg kwds: Optional, additional B{C{LatLon}} I{or} B{C{Vector}}

554 keyword arguments, ignored if both C{B{LatLon}} and

555 C{B{Vector}} are C{None}.

556

557 @return: See function L{pygeodesy.fractional}.

558

559 @raise IndexError: This fractional index invalid or B{C{points}}

560 not subscriptable or not closed.

561

562 @raise TypeError: Invalid B{C{LatLon}}, B{C{Vector}} or B{C{kwds}}

563 argument.

564

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

566 '''

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

568 return _MODS.points.fractional(points, self, wrap=wrap,

569 LatLon=LatLon, Vector=Vector, **kwds)

570

571

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

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

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

575 if not 0 <= i < n:

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

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

578

579

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

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

582 '''

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

584 '''New L{Height} instance, see L{Float}.

585 '''

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

587

588

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

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

591 '''

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

593 '''New L{Height} instance, see L{Float}.

594 '''

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

596

597

598 class HeightX(Height):

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

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

601 '''

602 pass

603

604

605 def _heigHt(inst, height):

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

607 '''

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

609

610

611 class Lam(Radians):

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

613 '''

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

615 '''New L{Lam} instance, see L{Radians}.

616 '''

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

618

619

620 class Lam_(Lam):

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

622 '''

623 def __new__(cls, arg=None, name=_lon_, Error=UnitError, clip=180, **name_arg):

624 '''New L{Lam_} instance, see L{Lam} and L{Radians}.

625 '''

626 if name_arg:

627 name, arg = _arg_name_arg2(arg, **name_arg)

628 d = Lam.__new__(cls, arg=arg, name=name, Error=Error, clip=clip)

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

630

631

632 class Lat(Degrees):

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

634 '''

635 _ddd_ = 2

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

637

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

639 '''New L{Lat} instnace, see L{Degrees}.

640 '''

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

642

643

644 class Lat_(Degrees_):

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

646 '''

647 _ddd_ = 2

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

649

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

651 '''See L{Degrees_}.

652 '''

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

654

655

656 class Lon(Degrees):

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

658 '''

659 _ddd_ = 3

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

661

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

663 '''New L{Lon} instance, see L{Degrees}.

664 '''

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

666

667

668 class Lon_(Degrees_):

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

670 '''

671 _ddd_ = 3

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

673

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

675 '''New L{Lon_} instance, see L{Lon} and L{Degrees_}.

676 '''

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

678

679

680 class Meter(Float):

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

682 '''

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

684 '''New L{Meter} instance, see L{Float}.

685 '''

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

687

688 def __repr__(self):

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

690

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

692

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

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

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

696 '''

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

698

699

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

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

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

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

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

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

706

707

708 class Meter_(Float_):

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

710 '''

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

712 '''New L{Meter_} instance, see L{Meter} and L{Float_}.

713 '''

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

715

716

717 class Meter2(Float_):

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

719 '''

720 def __new__(cls, arg=None, name=_meter2_, Error=UnitError, **name_arg):

721 '''New L{Meter2} instance, see L{Float_}.

722 '''

723 return Float_.__new__(cls, arg=arg, name=name, Error=Error, low=_0_0, **name_arg)

724

725

726 class Meter3(Float_):

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

728 '''

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

730 '''New L{Meter3} instance, see L{Float_}.

731 '''

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

733

734

735 class Northing(Float):

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

737 '''

738 def __new__(cls, arg=None, name=_northing_, Error=UnitError, falsed=False, high=None, **name_arg):

739 '''New C{Northing} or C{Northing of point} instance.

740

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

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

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

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

745 @kwarg falsed: The B{C{arg}} value includes false origin (C{bool}).

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

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

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

749

750 @returns: A C{Northing} instance.

751

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

753 '''

754 if name_arg:

755 name, arg = _arg_name_arg2(arg, **name_arg)

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

757 if high and (self < 0 or self > high):

758 raise _Error(cls, arg, name, Error)

759 elif falsed and self < 0:

760 raise _Error(cls, arg, name, Error, txt=_negative_falsed_)

761 return self

762

763

764 class Number_(Int_):

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

766 '''

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

768 '''New L{Number_} instance, see L{Int_}.

769 '''

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

771

772

773 class Phi(Radians):

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

775 '''

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

777 '''New L{Phi} instance, see L{Radians}.

778 '''

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

780

781

782 class Phi_(Phi):

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

784 '''

785 def __new__(cls, arg=None, name=_lat_, Error=UnitError, clip=90, **name_arg):

786 '''New L{Phi_} instance, see L{Phi} and L{Radians}.

787 '''

788 if name_arg:

789 name, arg = _arg_name_arg2(arg, **name_arg)

790 d = Phi.__new__(cls, arg=arg, name=name, Error=Error, clip=clip)

791 return Radians.__new__(cls, arg=radians(d), name=name, Error=Error)

792

793

794 class Precision_(Int_):

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

796 '''

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

798 '''New L{Precision_} instance, see L{Int_}.

799 '''

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

801

802

803 class Radius_(Float_):

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

805 '''

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

807 '''New L{Radius_} instance, see L{Radius} and L{Float}.

808 '''

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

810

811

812 class Scalar(Float):

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

814 '''

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

816 '''New L{Scalar} instance, see L{Float}.

817 '''

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

819

820

821 class Scalar_(Float_):

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

823 '''

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

825 '''New L{Scalar_} instance, see L{Scalar} and L{Float_}.

826 '''

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

828

829

830 class Zone(Int):

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

832 '''

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

834 '''New L{Zone} instance, see L{Int}

835 '''

836 # usually low=_UTMUPS_ZONE_MIN, high=_UTMUPS_ZONE_MAX

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

838

839

840_Scalars = Float, Float_, Scalar, Scalar_

841_Degrees = (Bearing, Bearing_, Degrees, Degrees_) + _Scalars

842_Meters = (Distance, Distance_, Meter, Meter_) + _Scalars

843_Radians = (Radians, Radians_) + _Scalars # PYCHOK unused

844_Radii = _Meters + (Radius, Radius_)

845

846

847 def _isDegrees(obj):

848 # Check for valid degrees types.

849 return isinstance(obj, _Degrees) or _isScalar(obj)

850

851

852 def _isHeight(obj):

853 # Check for valid heigth types.

854 return isinstance(obj, _Meters) or _isScalar(obj)

855

856

857 def _isMeter(obj):

858 # Check for valid meter types.

859 return isinstance(obj, _Meters) or _isScalar(obj)

860

861

862 def _isRadius(obj):

863 # Check for valid earth radius types.

864 return isinstance(obj, _Radii) or _isScalar(obj)

865

866

867 def _isScalar(obj):

868 # Check for pure scalar types.

869 return isscalar(obj) and not isinstance(obj, _NamedUnit)

870

871

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

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

874 '''

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

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

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

878 return arg

879

880

881 def _xStrError(*Refs, **name_value_Error):

882 '''(INTERNAL) Create a C{TypeError} for C{Garef}, C{Geohash}, C{Wgrs}.

883 '''

884 r = tuple(r.__name__ for r in Refs) + (Str.__name__, _LatLon_, 'LatLon*Tuple')

885 return _IsnotError(*r, **name_value_Error)

886

887

888 def _xUnit(units, Base): # in .frechet, .hausdorff

889 '''(INTERNAL) Get C{Unit} from C{Unit} or C{name}, ortherwise C{Base}.

890 '''

891 _xsubclassof(_NamedUnit, Base=Base)

892 U = globals().get(units.capitalize(), Base) if isstr(units) else (

893 units if issubclassof(units, Base) else Base)

894 return U if issubclassof(U, Base) else Base

895

896

897 def _xUnits(units, Base=_NamedUnit): # in .frechet, .hausdorff

898 '''(INTERNAL) Set property C{units} as C{Unit} or C{Str}.

899 '''

900 _xsubclassof(_NamedUnit, Base=Base)

901 if issubclassof(units, Base):

902 return units

903 elif isstr(units):

904 return Str(units, name=_units_) # XXX Str to _Pass and for backward compatibility

905 raise _IsnotError(*(_.__name__ for _ in (Base, Str, str)), units=units)

906

907

908 def _std_repr(*Classes):

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

910 '''

911 for C in Classes:

912 if hasattr(C, _std_repr.__name__): # PYCHOK del _std_repr

913 env = 'PYGEODESY_%s_STD_REPR' % (C.__name__.upper(),)

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

915 C._std_repr = False

916

917_std_repr(Bearing, Bool, Degrees, Float, Int, Meter, Radians, Str) # PYCHOK expected

918 del _std_repr

919

920__all__ += _ALL_DOCS(_NamedUnit)

921

922# **) MIT License

923#

924# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.

925#

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

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

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

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

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

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

932#

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

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

935#

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

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

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

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

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

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

942# OTHER DEALINGS IN THE SOFTWARE.