1

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

3

4 u'''Various units, all sub-classes of C{Float}, C{Int} and

5 C{Str} from basic C{float}, C{int} respectively C{str} to

6 named units as L{Degrees}, L{Feet}, L{Meter}, L{Radians}, etc.

7'''

8

9 from pygeodesy.basics import isstr, issubclassof, signOf

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

11 _umod_360, _0_0, _0_001, \

12 _0_5, INT0 # PYCHOK for .mgrs, .namedTuples

13 from pygeodesy.dms import F__F, F__F_, parseDMS, parseRad, \

14 S_NUL, S_SEP, _toDMS

15 from pygeodesy.errors import _AssertionError, _IsnotError, TRFError, \

16 UnitError, _xkwds_popitem

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

18 _distance_, _E_, _easting_, _epoch_, _EW_, \

19 _feet_, _height_, _lam_, _lat_, \

20 _LatLon_, _lon_, _meter_, _meter2_, _N_, \

21 _northing_, _NS_, _NSEW_, _number_, _PERCENT_, \

22 _phi_, _precision_, _radians_, _radians2_, \

23 _radius_, _S_, _scalar_, _units_, \

24 _W_, _zone_, _std_ # PYCHOK used!

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

26 from pygeodesy.props import Property_RO

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

28 from pygeodesy.unitsBase import _Error, Float, Fmt, fstr, Int, _NamedUnit, \

29 Radius, Str # PYCHOK shared .namedTuples

30 from math import degrees, radians

31

32__all__ = _ALL_LAZY.units

33__version__ = '23.05.12'

34

35_negative_falsed_ = 'negative, falsed'

36

37

38 class Float_(Float):

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

40 '''

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

42 '''New C{Float_} instance.

43

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

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

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

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

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

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

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

51 and B{C{arg}}.

52

53 @returns: A C{Float_} instance.

54

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

56 '''

57 if name_arg:

58 name, arg = _xkwds_popitem(name_arg)

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

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

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

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

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

64 else:

65 return self

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

67

68

69 class Int_(Int):

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

71 '''

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

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

74

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

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

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

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

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

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

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

82 and B{C{arg}}.

83

84 @returns: An L{Int_} instance.

85

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

87 '''

88 if name_arg:

89 name, arg = _xkwds_popitem(name_arg)

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

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

92 txt = Fmt.limit(below=low)

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

94 txt = Fmt.limit(above=high)

95 else:

96 return self

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

98

99

100 class Bool(Int, _NamedUnit):

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

102 '''

103 # _std_repr = True # set below

104 _bool_True_or_False = None

105

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

107 '''New C{Bool} instance.

108

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

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

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

112 @kwarg Error: Optional error to raise, overriding the default

113 L{UnitError}.

114 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu

115 of B{C{name}} and B{C{arg}}.

116

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

118

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

120 '''

121 if name_arg:

122 name, arg = _xkwds_popitem(name_arg)

123 try:

124 b = bool(arg)

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

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

127

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

129 self._bool_True_or_False = b

130 return self

131

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

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

134 return self._bool_True_or_False

135

136 __nonzero__ = __bool__ # PYCHOK Python 2-

137

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

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

140

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

142 representation (C{bool}).

143

144 @note: Use C{env} variable C{PYGEODESY_BOOL_STD_REPR=std}

145 prior to C{import pygeodesy} to get the standard

146 C{repr} or set property C{std_repr=False} to always

147 get the named C{toRepr} representation.

148 '''

149 r = repr(self._bool_True_or_False)

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

151

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

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

154 '''

155 return str(self._bool_True_or_False)

156

157

158 class Band(Str):

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

160 '''

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

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

163 '''

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

165

166

167 class Degrees(Float):

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

169 '''

170 _ddd_ = 1 # default for .dms._toDMS

171 _sep_ = S_SEP

172 _suf_ = (S_NUL,) * 3

173

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

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

176

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

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

179 parsable by L{pygeodesy.parseDMS}).

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

181 @kwarg Error: Optional error to raise, overriding the default

182 L{UnitError}.

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

184 @kwarg clip: Optional B{C{arg}} range B{C{-clip..+clip}}

185 (C{degrees} or C{0} or C{None} for unclipped).

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

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

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

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

190

191 @returns: A C{Degrees} instance.

192

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

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

195 '''

196 if name_arg:

197 name, arg = _xkwds_popitem(name_arg)

198 try:

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

200 Error=Error, name=name)

201 if wrap:

202 w = wrap(d)

203 if w != d:

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

205 except Exception as x:

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

207 return d

208

209 def toDegrees(self):

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

211 '''

212 return self

213

214 def toRadians(self):

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

216 '''

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

218

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

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

221

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

223 otherwise the named representation (C{bool}).

224

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

226 L{pygeodesy.toDMS} for more documentation.

227 '''

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

229

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

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

232

233 @see: Function L{pygeodesy.toDMS} for keyword argument details.

234 '''

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

236 p = 8 if prec is None else prec

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

238 else:

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

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

241

242

243 class Degrees_(Degrees):

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

245 '''

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

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

248

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

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

251 L{pygeodesy.parseDMS}).

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

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

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

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

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

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

258 and B{C{arg}}.

259

260 @returns: A C{Degrees} instance.

261

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

263 '''

264 if name_arg:

265 name, arg = _xkwds_popitem(name_arg)

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

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

268 txt = Fmt.limit(below=low)

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

270 txt = Fmt.limit(above=high)

271 else:

272 return self

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

274

275

276 class Degrees2(Float):

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

278 '''

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

280 '''See L{Float}.

281 '''

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

283

284

285 class Radians(Float):

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

287 '''

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

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

290

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

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

293 by L{pygeodesy.parseRad}).

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

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

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

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

298 or C{None} for unclipped).

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

300 and B{C{arg}}.

301

302 @returns: A C{Radians} instance.

303

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

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

306 '''

307 if name_arg:

308 name, arg = _xkwds_popitem(name_arg)

309 try:

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

311 Error=Error, name=name)

312 except Exception as x:

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

314

315 def toDegrees(self):

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

317 '''

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

319

320 def toRadians(self):

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

322 '''

323 return self

324

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

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

327

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

329 otherwise the named representation (C{bool}).

330

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

332 L{pygeodesy.toDMS} for more documentation.

333 '''

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

335

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

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

338

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

340 '''

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

342

343

344 class Radians_(Radians):

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

346 '''

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

348 '''New C{Radians_} instance.

349

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

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

352 L{pygeodesy.parseRad}).

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

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

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

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

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

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

359 and B{C{arg}}.

360

361 @returns: A C{Radians_} instance.

362

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

364 '''

365 if name_arg:

366 name, arg = _xkwds_popitem(name_arg)

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

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

369 txt = Fmt.limit(below=low)

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

371 txt = Fmt.limit(above=high)

372 else:

373 return self

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

375

376

377 class Radians2(Float_):

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

379 '''

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

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

382 '''

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

384

385

386 class Bearing(Degrees):

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

388 '''

389 _ddd_ = 1

390 _suf_ = _N_ * 3 # always suffix N

391

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

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

394 '''

395 if name_arg:

396 name, arg = _xkwds_popitem(name_arg)

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

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

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

400

401

402 class Bearing_(Radians):

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

404 '''

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

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

407 '''

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

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

410

411

412 class Distance(Float):

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

414 '''

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

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

417 '''

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

419

420

421 class Distance_(Float_):

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

423 '''

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

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

426 '''

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

428

429

430 class Easting(Float):

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

432 '''

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

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

435

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

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

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

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

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

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

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

443 and B{C{arg}}.

444

445 @returns: An C{Easting} instance.

446

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

448 '''

449 if name_arg:

450 name, arg = _xkwds_popitem(name_arg)

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

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

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

454 elif falsed and self < 0:

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

456 return self

457

458

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

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

461 calendar year.

462 '''

463 _std_repr = False

464

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

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

467 '''

468 if name_arg:

469 name, arg = _xkwds_popitem(name_arg)

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

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

472

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

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

475

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

477 representation (C{bool}).

478

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

480 '''

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

482

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

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

485

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

487 '''

488 return fstr(self, prec=-3, fmt=Fmt.F, ints=True)

489

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

491

492

493 class Feet(Float):

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

495 '''

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

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

498 '''

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

500

501

502 class FIx(Float_):

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

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

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

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

507 len(points)]}.

508 '''

509 _fin = 0

510

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

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

513

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

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

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

517 @kwarg name_Error: Optional keyword argument C{B{name}=NN}

518 and C{B{Error}=UnitError}.

519

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

521

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

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

524

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

526 '''

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

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

529 i = int(f)

530 r = f - float(i)

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

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

533 elif r > EPS1:

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

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

536 f._fin = n

537 return f

538

539 @Property_RO

540 def fin(self):

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

542 '''

543 return self._fin

544

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

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

547

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

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

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

551 B{C{points}} (C{bool}) C{None} for backward

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

553 I{averaged} lat- and longitudes.

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

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

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

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

558 or C{None}.

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

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

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

562

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

564

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

566 not subscriptable or not closed.

567

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

569 argument.

570

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

572 '''

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

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

575 LatLon=LatLon, Vector=Vector, **kwds)

576

577

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

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

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

581 if not 0 <= i < n:

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

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

584

585

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

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

588 '''

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

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

591 '''

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

593

594

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

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

597 '''

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

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

600 '''

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

602

603

604 class HeightX(Height):

605 '''Like L{Height} but to distinguish the interpolated height

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

607 '''

608 pass

609

610

611 def _heigHt(inst, height):

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

613 '''

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

615

616

617 class Lam(Radians):

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

619 '''

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

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

622 '''

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

624

625

626 class Lam_(Lam):

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

628 '''

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

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

631 '''

632 if name_arg:

633 name, arg = _xkwds_popitem(name_arg)

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

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

636

637

638 class Lat(Degrees):

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

640 '''

641 _ddd_ = 2

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

643

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

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

646 '''

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

648

649

650 class Lat_(Degrees_):

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

652 '''

653 _ddd_ = 2

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

655

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

657 '''See L{Degrees_}.

658 '''

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

660

661

662 class Lon(Degrees):

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

664 '''

665 _ddd_ = 3

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

667

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

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

670 '''

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

672

673

674 class Lon_(Degrees_):

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

676 '''

677 _ddd_ = 3

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

679

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

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

682 '''

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

684

685

686 class Meter(Float):

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

688 '''

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

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

691 '''

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

693

694 def __repr__(self):

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

696

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

698

699 @note: Use C{env} variable C{PYGEODESY_METER_STD_REPR=std}

700 prior to C{import pygeodesy} to get the standard

701 C{repr} or set property C{std_repr=False} to always

702 get the named C{toRepr} representation.

703 '''

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

705

706

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

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

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

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

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

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

713

714

715 class Meter_(Float_):

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

717 '''

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

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

720 '''

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

722

723

724 class Meter2(Float_):

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

726 '''

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

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

729 '''

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

731

732

733 class Meter3(Float_):

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

735 '''

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

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

738 '''

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

740

741

742 class Northing(Float):

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

744 '''

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

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

747

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

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

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

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

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

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

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

755 and B{C{arg}}.

756

757 @returns: A C{Northing} instance.

758

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

760 '''

761 if name_arg:

762 name, arg = _xkwds_popitem(name_arg)

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

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

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

766 elif falsed and self < 0:

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

768 return self

769

770

771 class Number_(Int_):

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

773 '''

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

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

776 '''

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

778

779

780 class Phi(Radians):

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

782 '''

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

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

785 '''

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

787

788

789 class Phi_(Phi):

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

791 '''

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

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

794 '''

795 if name_arg:

796 name, arg = _xkwds_popitem(name_arg)

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

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

799

800

801 class Precision_(Int_):

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

803 '''

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

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

806 '''

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

808

809

810 class Radius_(Float_):

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

812 '''

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

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

815 '''

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

817

818

819 class Scalar(Float):

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

821 '''

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

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

824 '''

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

826

827

828 class Scalar_(Float_):

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

830 '''

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

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

833 '''

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

835

836

837 class Zone(Int):

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

839 '''

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

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

842 '''

843 # usually low=_UTMUPS_ZONE_MIN, high=_UTMUPS_ZONE_MAX

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

845

846

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

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

849 '''

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

851 return _IsnotError(*r, **name_value_Error)

852

853

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

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

856 '''

857 if not issubclassof(Base, _NamedUnit):

858 raise _IsnotError(_NamedUnit.__name__, Base=Base)

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

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

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

862

863

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

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

866 '''

867 if not issubclassof(Base, _NamedUnit):

868 raise _IsnotError(_NamedUnit.__name__, Base=Base)

869 elif issubclassof(units, Base):

870 return units

871 elif isstr(units):

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

873 else:

874 raise _IsnotError(Base.__name__, Str.__name__, str.__name__, units=units)

875

876

877 def _std_repr(*Classes):

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

879 '''

880 for C in Classes:

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

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

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

884 C._std_repr = False

885

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

887 del _std_repr

888

889__all__ += _ALL_DOCS(_NamedUnit)

890

891# **) MIT License

892#

893# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.

894#

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

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

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

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

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

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

901#

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

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

904#

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

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

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

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

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

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

911# OTHER DEALINGS IN THE SOFTWARE.