Coverage for pygeodesy/basics.py: 88%

1

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

3

4 u'''Some, basic definitions, functions and dependencies.

5

6 Use env variable C{PYGEODESY_XPACKAGES} to avoid import of dependencies

7 C{geographiclib}, C{numpy} and/or C{scipy}. Set C{PYGEODESY_XPACKAGES}

8 to a comma-separated list of package names to be excluded from import.

9'''

10# make sure int/int division yields float quotient

11 from __future__ import division

12 division = 1 / 2 # .albers, .azimuthal, .constants, etc., .utily

13 if not division:

14 raise ImportError('%s 1/2 == %s' % ('division', division))

15 del division

16

17# from pygeodesy.cartesianBase import CartesianBase # _MODS

18# from pygeodesy.constants import isneg0, NEG0 # _MODS

19 from pygeodesy.errors import _AttributeError, _ImportError, _NotImplementedError, \

20 _TypeError, _TypesError, _ValueError, _xAssertionError, \

21 _xkwds_get1

22# from pygeodesy.fsums import _isFsum_2Tuple # _MODS

23 from pygeodesy.internals import _0_0, _enquote, _envPYGEODESY, _getenv, _passarg, \

24 _PYGEODESY_ENV, typename, _version_info

25 from pygeodesy.interns import MISSING, NN, _1_, _by_, _COMMA_, _DOT_, _DEPRECATED_, \

26 _ELLIPSIS4_, _EQUAL_, _in_, _invalid_, _N_A_, _not_, \

27 _not_scalar_, _odd_, _SPACE_, _UNDER_, _version_

28# from pygeodesy.latlonBase import LatLonBase # _MODS

29 from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, LazyImportError

30# from pygeodesy.named import classname, modulename, _name__ # _MODS

31# from pygeodesy.nvectorBase import NvectorBase # _MODS

32# from pygeodesy.props import _update_all # _MODS

33# from pygeodesy.streprs import Fmt # _MODS

34

35 from copy import copy as _copy, deepcopy as _deepcopy

36 from math import copysign as _copysign

37# import inspect as _inspect # _MODS

38

39 __all__ = _ALL_LAZY.basics

40 __version__ = '25.12.02'

41

42 _below_ = 'below'

43 _list_tuple_types = (list, tuple)

44 _required_ = 'required'

45

46 try: # Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 395, 2022 p. 577+

47 from numbers import Integral as _Ints, Real as _Scalars # .units

48 except ImportError:

49 try:

50 _Ints = int, long # int objects (C{tuple})

51 except NameError: # Python 3+

52 _Ints = int, # int objects (C{tuple})

53 _Scalars = (float,) + _Ints

54

55 try:

56 try: # use C{from collections.abc import ...} in Python 3.9+

57 from collections.abc import Sequence as _Sequence # in .points

58 except ImportError: # no .abc in Python 3.8- and 2.7-

59 from collections import Sequence as _Sequence # in .points

60 if isinstance([], _Sequence) and isinstance((), _Sequence):

61 # and isinstance(range(1), _Sequence):

62 _Seqs = _Sequence

63 else:

64 raise ImportError() # _AssertionError

65 except ImportError:

66 _Sequence = tuple # immutable for .points._Basequence

67 _Seqs = list, _Sequence # range for function len2 below

68

69 try:

70 _Bytes = unicode, bytearray # PYCHOK in .internals

71 _Strs = basestring, str # XXX str == bytes

72 str2ub = ub2str = _passarg # avoids UnicodeDecodeError

73

74 def _Xstr(exc): # PYCHOK no cover

75 '''I{Invoke only with caught ImportError} B{C{exc}}.

76

77 C{... "can't import name _distributor_init" ...}

78

79 only for C{numpy}, C{scipy} import errors occurring

80 on arm64 Apple Silicon running macOS' Python 2.7.16?

81 '''

82 t = str(exc)

83 if '_distributor_init' in t:

84 from sys import exc_info

85 from traceback import extract_tb

86 tb = exc_info()[2] # 3-tuple (type, value, traceback)

87 t4 = extract_tb(tb, 1)[0] # 4-tuple (file, line, name, 'import ...')

88 t = _SPACE_("can't", t4[3] or _N_A_)

89 del tb, t4

90 return t

91

92 except NameError: # Python 3+

93 from pygeodesy.interns import _utf_8_

94

95 _Bytes = bytes, bytearray # in .internals

96 _Strs = str, # tuple

97 _Xstr = str

98

99 def str2ub(sb):

100 '''Convert C{str} to C{unicode bytes}.

101 '''

102 if isinstance(sb, _Strs):

103 sb = sb.encode(_utf_8_)

104 return sb

105

106 def ub2str(ub):

107 '''Convert C{unicode bytes} to C{str}.

108 '''

109 if isinstance(ub, _Bytes):

110 ub = str(ub.decode(_utf_8_))

111 return ub

112

113

114# def _args_kwds_count2(func, exelf=True): # in .formy

115# '''(INTERNAL) Get a C{func}'s args and kwds count as 2-tuple

116# C{(nargs, nkwds)}, including arg C{self} for methods.

117#

118# @kwarg exelf: If C{True}, exclude C{self} in the C{args}

119# of a method (C{bool}).

120# '''

121# i = _MODS.inspect

122# try:

123# a = k = 0

124# for _, p in i.signature(func).parameters.items():

125# if p.kind is p.POSITIONAL_OR_KEYWORD:

126# if p.default is p.empty:

127# a += 1

128# else:

129# k += 1

130# except AttributeError: # Python 2-

131# s = i.getargspec(func)

132# k = len(s.defaults or ())

133# a = len(s.args) - k

134# if exelf and a > 0 and i.ismethod(func):

135# a -= 1

136# return a, k

137

138

139 def _args_kwds_names(func, splast=False):

140 '''(INTERNAL) Get a C{func}'s args and kwds names, including

141 C{self} for methods.

142

143 @kwarg splast: If C{True}, split the last keyword argument

144 at UNDERscores (C{bool}).

145

146 @note: Python 2 may I{not} include the C{*args} nor the

147 C{**kwds} names.

148 '''

149 i = _MODS.inspect

150 try:

151 args_kwds = i.signature(func).parameters.keys()

152 except AttributeError: # Python 2-

153 args_kwds = i.getargspec(func).args

154 if splast and args_kwds: # PYCHOK no cover

155 args_kwds = list(args_kwds)

156 t = args_kwds[-1:]

157 if t:

158 s = t[0].strip(_UNDER_).split(_UNDER_)

159 if len(s) > 1 or s != t:

160 args_kwds += s

161 return tuple(args_kwds)

162

163

164 def clips(sb, limit=50, white=NN, length=False):

165 '''Clip a string to the given length limit.

166

167 @arg sb: String (C{str} or C{bytes}).

168 @kwarg limit: Length limit (C{int}).

169 @kwarg white: Optionally, replace all whitespace (C{str}).

170 @kwarg length: If C{True}, append the original I{[length]} (C{bool}).

171

172 @return: The clipped or unclipped B{C{sb}}.

173 '''

174 T, n = type(sb), len(sb)

175 if n > limit > 8:

176 h = limit // 2

177 sb = T(_ELLIPSIS4_).join((sb[:h], sb[-h:]))

178 if length:

179 n = _MODS.streprs.Fmt.SQUARE(n)

180 sb = T(NN).join((sb, n))

181 if white: # replace whitespace

182 sb = T(white).join(sb.split())

183 return sb

184

185

186 def copysign0(x, y):

187 '''Like C{math.copysign(x, y)} except C{zero}, I{unsigned}.

188

189 @return: C{math.copysign(B{x}, B{y})} if B{C{x}} else

190 C{type(B{x})(0)}.

191 '''

192 return _copysign(x, (y if y else 0)) if x else copytype(0, x)

193

194

195 def copytype(x, y):

196 '''Return the value of B{x} as C{type} of C{y}.

197

198 @return: C{type(B{y})(B{x})}.

199 '''

200 return type(y)(x if x else _0_0)

201

202

203 def _enumereverse(iterable):

204 '''(INTERNAL) Reversed C{enumerate}.

205 '''

206 for j in _reverange(len(iterable)):

207 yield j, iterable[j]

208

209

210 try:

211 from math import gcd as _gcd

212 except ImportError: # 3.4-

213

214 def _gcd(a, b): # PYCHOK redef

215 # <https://WikiPedia.org/wiki/Greatest_common_divisor>

216 a, b = int(a), int(b)

217 if b > a:

218 a, b = b, a

219# if b <= 0:

220# return 1

221 while b:

222 a, b = b, (a % b)

223 return a

224

225

226 def halfs2(str2):

227 '''Split a string in 2 halfs.

228

229 @arg str2: String to split (C{str}).

230

231 @return: 2-Tuple C{(_1st, _2nd)} half (C{str}).

232

233 @raise ValueError: Zero or odd C{len(B{str2})}.

234 '''

235 h, r = divmod(len(str2), 2)

236 if r or not h:

237 raise _ValueError(str2=str2, txt=_odd_)

238 return str2[:h], str2[h:]

239

240

241 def _integer_ratio2(x): # PYCHOK no cover

242 '''(INTERNAL) Return C{B{x}.as_interger_ratio()}.

243 '''

244 try: # int.as_integer_ratio in 3.8+

245 return x.as_integer_ratio()

246 except (AttributeError, OverflowError, TypeError, ValueError):

247 return (x if isint(x) else float(x)), 1

248

249

250 def int1s(x): # PYCHOK no cover

251 '''Count the number of 1-bits in an C{int}, I{unsigned}.

252

253 @note: C{int1s(-B{x}) == int1s(abs(B{x}))}.

254 '''

255 try:

256 return x.bit_count() # Python 3.10+

257 except AttributeError:

258 # bin(-x) = '-' + bin(abs(x))

259 return bin(x).count(_1_)

260

261

262 def isbool(obj):

263 '''Is B{C{obj}}ect a C{bool}ean?

264

265 @arg obj: The object (any C{type}).

266

267 @return: C{True} if C{bool}ean, C{False} otherwise.

268 '''

269 return isinstance(obj, bool) # and (obj is False

270# or obj is True)

271

272 assert not (isbool(1) or isbool(0) or isbool(None)) # PYCHOK 2

273

274

275 def isCartesian(obj, ellipsoidal=None):

276 '''Is B{C{obj}}ect some C{Cartesian}?

277

278 @arg obj: The object (any C{type}).

279 @kwarg ellipsoidal: If C{None}, return the type of any C{Cartesian},

280 if C{True}, only an ellipsoidal C{Cartesian type}

281 or if C{False}, only a spherical C{Cartesian type}.

282

283 @return: C{type(B{obj}} if a C{Cartesian} of the required type, C{False}

284 if a C{Cartesian} of an other type or {None} otherwise.

285 '''

286 if ellipsoidal is not None:

287 try:

288 return obj.ellipsoidalCartesian if ellipsoidal else obj.sphericalCartesian

289 except AttributeError:

290 return None

291 return isinstanceof(obj, _MODS.cartesianBase.CartesianBase)

292

293

294 def isclass(obj): # XXX avoid epydoc Python 2.7 error

295 '''Is B{C{obj}}ect a C{Class} or C{type}?

296 '''

297 return _MODS.inspect.isclass(obj)

298

299

300 def iscomplex(obj, both=False):

301 '''Is B{C{obj}}ect a C{complex} or complex literal C{str}?

302

303 @arg obj: The object (any C{type}).

304 @kwarg both: If C{True}, check complex C{str} (C{bool}).

305

306 @return: C{True} if C{complex}, C{False} otherwise.

307 '''

308 try: # hasattr('conjugate', 'real' and 'imag')

309 return isinstance(obj, complex) or bool(both and isstr(obj) and

310 isinstance(complex(obj), complex)) # numbers.Complex?

311 except (TypeError, ValueError):

312 return False

313

314

315 def isDEPRECATED(obj, outer=1):

316 '''Is B{C{obj}}ect or its outer C{type} a C{DEPRECATED}

317 class, constant, method or function?

318

319 @return: C{True} if C{DEPRECATED}, {False} if not or

320 C{None} if undetermined.

321 '''

322 r = None

323 for _ in range(max(0, outer) + 1):

324 try: # inspect.getdoc(obj)

325 if _DEPRECATED_ in obj.__doc__:

326 return True

327 r = False

328 except AttributeError:

329 pass

330 obj = type(obj)

331 return r

332

333

334 def isfloat(obj, both=False):

335 '''Is B{C{obj}}ect a C{float} or float literal C{str}?

336

337 @arg obj: The object (any C{type}).

338 @kwarg both: If C{True}, check float C{str} (C{bool}).

339

340 @return: C{True} if C{float}, C{False} otherwise.

341 '''

342 try:

343 return isinstance(obj, float) or bool(both and

344 isstr(obj) and isinstance(float(obj), float))

345 except (TypeError, ValueError):

346 return False

347

348

349 try:

350 isidentifier = str.isidentifier # must be str

351 except AttributeError: # 2.0-

352

353 def isidentifier(obj):

354 '''Is B{C{obj}}ect a Python identifier?

355 '''

356 return bool(obj and isstr(obj)

357 and obj.replace(_UNDER_, NN).isalnum()

358 and not obj[:1].isdigit())

359

360

361 def _isin(obj, *objs):

362 '''(INTERNAL) Return C{bool(obj in objs)} with C{True} and C{False} matching.

363 '''

364 return any(o is obj for o in objs) or \

365 any(o == obj for o in objs if not isbool(o))

366

367

368 def isinstanceof(obj, *Classes):

369 '''Is B{C{obj}}ect an instance of one of the C{Classes}?

370

371 @arg obj: The object (any C{type}).

372 @arg Classes: One or more classes (C{Class}).

373

374 @return: C{type(B{obj}} if one of the B{C{Classes}},

375 C{None} otherwise.

376 '''

377 return type(obj) if isinstance(obj, Classes) else None

378

379

380 def isint(obj, both=False):

381 '''Is B{C{obj}}ect an C{int} or integer C{float} value?

382

383 @arg obj: The object (any C{type}).

384 @kwarg both: If C{True}, check C{float} and L{Fsum}

385 type and value (C{bool}).

386

387 @return: C{True} if C{int} or I{integer} C{float}

388 or L{Fsum}, C{False} otherwise.

389

390 @note: Both C{isint(True)} and C{isint(False)} return

391 C{False} (and no longer C{True}).

392 '''

393 if isinstance(obj, _Ints):

394 return not isbool(obj)

395 elif both: # and isinstance(obj, (float, Fsum))

396 try: # NOT , _Scalars) to include Fsum!

397 return obj.is_integer()

398 except AttributeError:

399 pass # XXX float(int(obj)) == obj?

400 return False

401

402

403 def isiterable(obj, strict=False):

404 '''Is B{C{obj}}ect C{iterable}?

405

406 @arg obj: The object (any C{type}).

407 @kwarg strict: If C{True}, check class attributes (C{bool}).

408

409 @return: C{True} if C{iterable}, C{False} otherwise.

410 '''

411 # <https://PyPI.org/project/isiterable/>

412 return bool(isiterabletype(obj)) if strict else hasattr(obj, '__iter__') # map, range, set

413

414

415 def isiterablen(obj, strict=False):

416 '''Is B{C{obj}}ect C{iterable} and has C{len}gth?

417

418 @arg obj: The object (any C{type}).

419 @kwarg strict: If C{True}, check class attributes (C{bool}).

420

421 @return: C{True} if C{iterable} with C{len}gth, C{False} otherwise.

422 '''

423 _has = isiterabletype if strict else hasattr

424 return bool(_has(obj, '__len__') and _has(obj, '__getitem__'))

425

426

427 def isiterabletype(obj, method='__iter__'):

428 '''Is B{C{obj}}ect an instance of an C{iterable} class or type?

429

430 @arg obj: The object (any C{type}).

431 @kwarg method: The name of the required method (C{str}).

432

433 @return: The C{base-class} if C{iterable}, C{None} otherwise.

434 '''

435 try: # <https://StackOverflow.com/questions/73568964>

436 for b in type(obj).__mro__[:-1]: # ignore C{object}

437 try:

438 if callable(b.__dict__[method]):

439 return b

440 except (AttributeError, KeyError):

441 pass

442 except (AttributeError, TypeError):

443 pass

444 return None

445

446

447 try:

448 from keyword import iskeyword # 2.7+

449 except ImportError:

450

451 def iskeyword(unused):

452 '''Not Implemented, C{False} always.

453 '''

454 return False

455

456

457 def isLatLon(obj, ellipsoidal=None):

458 '''Is B{C{obj}}ect some C{LatLon}?

459

460 @arg obj: The object (any C{type}).

461 @kwarg ellipsoidal: If C{None}, return the type of any C{LatLon},

462 if C{True}, only an ellipsoidal C{LatLon type}

463 or if C{False}, only a spherical C{LatLon type}.

464

465 @return: C{type(B{obj}} if a C{LatLon} of the required type, C{False}

466 if a C{LatLon} of an other type or {None} otherwise.

467 '''

468 if ellipsoidal is not None:

469 try:

470 return obj.ellipsoidalLatLon if ellipsoidal else obj.sphericalLatLon

471 except AttributeError:

472 return None

473 return isinstanceof(obj, _MODS.latlonBase.LatLonBase)

474

475

476 def islistuple(obj, minum=0):

477 '''Is B{C{obj}}ect a C{list} or C{tuple} with non-zero length?

478

479 @arg obj: The object (any C{type}).

480 @kwarg minum: Minimal C{len} required C({int}).

481

482 @return: C{True} if a C{list} or C{tuple} with C{len} at

483 least B{C{minum}}, C{False} otherwise.

484 '''

485 return isinstance(obj, _list_tuple_types) and len(obj) >= minum

486

487

488 def isNvector(obj, ellipsoidal=None):

489 '''Is B{C{obj}}ect some C{Nvector}?

490

491 @arg obj: The object (any C{type}).

492 @kwarg ellipsoidal: If C{None}, return the type of any C{Nvector},

493 if C{True}, only an ellipsoidal C{Nvector type}

494 or if C{False}, only a spherical C{Nvector type}.

495

496 @return: C{type(B{obj}} if an C{Nvector} of the required type, C{False}

497 if an C{Nvector} of an other type or {None} otherwise.

498 '''

499 if ellipsoidal is not None:

500 try:

501 return obj.ellipsoidalNvector if ellipsoidal else obj.sphericalNvector

502 except AttributeError:

503 return None

504 return isinstanceof(obj, _MODS.nvectorBase.NvectorBase)

505

506

507 def isodd(x):

508 '''Is B{C{x}} odd?

509

510 @arg x: Value (C{scalar}).

511

512 @return: C{True} if odd, C{False} otherwise.

513 '''

514 return bool(int(x) & 1) # == bool(int(x) % 2)

515

516

517 def isscalar(obj, both=False):

518 '''Is B{C{obj}}ect an C{int} or integer C{float} value?

519

520 @arg obj: The object (any C{type}).

521 @kwarg both: If C{True}, check L{Fsum} and L{Fsum2Tuple}

522 residuals.

523

524 @return: C{True} if C{int}, C{float} or C{Fsum/-2Tuple}

525 with zero residual, C{False} otherwise.

526 '''

527 if isinstance(obj, _Scalars):

528 return not isbool(obj) # exclude bool

529 elif both and _MODS.fsums._isFsum_2Tuple(obj):

530 return bool(obj.residual == 0)

531 return False

532

533

534 def issequence(obj, *excls):

535 '''Is B{C{obj}}ect some sequence type?

536

537 @arg obj: The object (any C{type}).

538 @arg excls: Classes to exclude (C{type}), all positional.

539

540 @note: Excluding C{tuple} implies excluding C{namedtuple}.

541

542 @return: C{True} if a sequence, C{False} otherwise.

543 '''

544 return isinstance(obj, _Seqs) and not (excls and isinstance(obj, excls))

545

546

547 def isstr(obj):

548 '''Is B{C{obj}}ect some string type?

549

550 @arg obj: The object (any C{type}).

551

552 @return: C{True} if a C{str}, C{bytes}, ...,

553 C{False} otherwise.

554 '''

555 return isinstance(obj, _Strs)

556

557

558 def issubclassof(Sub, *Supers):

559 '''Is B{C{Sub}} a class and sub-class of some other class(es)?

560

561 @arg Sub: The sub-class (C{Class}).

562 @arg Supers: One or more C(super) classes (C{Class}).

563

564 @return: C{True} if a sub-class of any B{C{Supers}}, C{False}

565 if not (C{bool}) or C{None} if not a class or if no

566 B{C{Supers}} are given or none of those are a class.

567 '''

568 if isclass(Sub):

569 t = tuple(S for S in Supers if isclass(S))

570 if t:

571 return bool(issubclass(Sub, t)) # built-in

572 return None

573

574

575 def itemsorted(adict, *items_args, **asorted_reverse):

576 '''Return the items of C{B{adict}} sorted I{alphabetically,

577 case-insensitively} and in I{ascending} order.

578

579 @arg items_args: Optional positional argument(s) for method

580 C{B{adict}.items(B*{items_args})}.

581 @kwarg asorted_reverse: Use C{B{asorted}=False} for I{alphabetical,

582 case-sensitive} sorting and C{B{reverse}=True} for

583 sorting in C{descending} order.

584 '''

585 def _ins(item): # functools.cmp_to_key

586 k, v = item

587 return k.lower()

588

589 def _reverse_key(asorted=True, reverse=False):

590 return dict(reverse=reverse, key=_ins if asorted else None)

591

592 items = adict.items(*items_args) if items_args else adict.items()

593 return sorted(items, **_reverse_key(**asorted_reverse))

594

595

596 def len2(items):

597 '''Make built-in function L{len} work for generators, iterators,

598 etc. since those can only be started exactly once.

599

600 @arg items: Generator, iterator, list, range, tuple, etc.

601

602 @return: 2-Tuple C{(n, items)} of the number of items (C{int})

603 and the items (C{list} or C{tuple}).

604 '''

605 if not isinstance(items, _Seqs): # NOT hasattr(items, '__len__'):

606 items = list(items)

607 return len(items), items

608

609

610 def map1(fun1, *xs): # XXX map_

611 '''Call a single-argument function to each B{C{xs}}

612 and return a C{tuple} of results.

613

614 @arg fun1: 1-Arg function (C{callable}).

615 @arg xs: Arguments (C{any positional}).

616

617 @return: Function results (C{tuple}).

618 '''

619 return tuple(map(fun1, xs)) # if len(xs) != 1 else fun1(xs[0])

620

621

622 def map2(fun, *xs, **strict):

623 '''Like Python's B{C{map}} but returning a C{tuple} of results.

624

625 Unlike Python 2's built-in L{map}, Python 3+ L{map} returns a

626 L{map} object, an iterator-like object which generates the

627 results only once. Converting the L{map} object to a tuple

628 maintains the Python 2 behavior.

629

630 @arg fun: Function (C{callable}).

631 @arg xs: Arguments (C{all positional}).

632 @kwarg strict: See U{Python 3.14+ map<https://docs.Python.org/

633 3.14/library/functions.html#map>} (C{bool}).

634

635 @return: Function results (C{tuple}).

636 '''

637 return tuple(map(fun, *xs, **strict) if strict else map(fun, *xs))

638

639

640 def max2(*xs):

641 '''Return 2-tuple C{(max(xs), xs.index(max(xs)))}.

642 '''

643 return _max2min2(xs, max, max2)

644

645

646 def _max2min2(xs, _m, _m2):

647 '''(INTERNAL) Helper for C{max2} and C{min2}.

648 '''

649 if len(xs) == 1:

650 x = xs[0]

651 if isiterable(x) or isiterablen(x):

652 x, i = _m2(*x)

653 else:

654 i = 0

655 else:

656 x = _m(xs) # max or min

657 i = xs.index(x)

658 return x, i

659

660

661 def min2(*xs):

662 '''Return 2-tuple C{(min(xs), xs.index(min(xs)))}.

663 '''

664 return _max2min2(xs, min, min2)

665

666

667 def neg(x, neg0=None):

668 '''Negate C{x} and optionally, negate C{0.0} and C{-0.0}.

669

670 @kwarg neg0: Defines the return value for zero C{B{x}}: if C{None}

671 return C{0.0}, if C{True} return C{NEG0 if B{x}=0.0}

672 and C{0.0 if B{x}=NEG0} or if C{False} return C{B{x}}

673 I{as-is} (C{bool} or C{None}).

674

675 @return: C{-B{x} if B{x} else 0.0, NEG0 or B{x}}.

676 '''

677 return (-x) if x else (

678 _0_0 if neg0 is None else (

679 x if not neg0 else (

680 _0_0 if signBit(x) else _MODS.constants.

681 NEG0))) # PYCHOK indent

682

683

684 def neg_(*xs):

685 '''Negate all C{xs} with L{neg}.

686

687 @return: A C{map(neg, B{xs})}.

688 '''

689 return map(neg, xs)

690

691

692 def _neg0(x):

693 '''(INTERNAL) Return C{NEG0 if x < 0 else _0_0},

694 unlike C{_copysign_0_0} which returns C{_N_0_0}.

695 '''

696 return _MODS.constants.NEG0 if x < 0 else _0_0

697

698

699 def _req_d_by(where, **name):

700 '''(INTERNAL) Get the fully qualified name.

701 '''

702 m = _MODS.named

703 n = m._name__(**name)

704 m = m.modulename(where, prefixed=True)

705 if n:

706 m = _DOT_(m, n)

707 return _SPACE_(_required_, _by_, m)

708

709

710 def _reverange(n, stop=-1, step=-1):

711 '''(INTERNAL) Reversed range yielding C{n-1, n-1-step, ..., stop+1}.

712 '''

713 return range(n - 1, stop, step)

714

715

716 try:

717 from math import signbit as signBit # 3.15+

718 except ImportError:

719

720 def signBit(x):

721 '''Return C{signbit(B{x})}, like C++, see also L{isneg}.

722

723 @return: C{True} if C{B{x} < 0} or C{NEG0} (C{bool}).

724 '''

725 return (x or _copysign(1, x)) < 0

726

727

728 def _signOf(x, ref): # in .fsums

729 '''(INTERNAL) Return the sign of B{C{x}} versus B{C{ref}}.

730 '''

731 return (-1) if x < ref else (+1 if x > ref else 0)

732

733

734 def signOf(x):

735 '''Return sign of C{x} as C{int}.

736

737 @return: -1, 0 or +1 (C{int}).

738 '''

739 try:

740 s = x.signOf() # Fsum instance?

741 except AttributeError:

742 s = _signOf(x, 0)

743 return s

744

745

746 def splice(iterable, n=2, **fill):

747 '''Split an iterable into C{n} slices.

748

749 @arg iterable: Items to be spliced (C{list}, C{tuple}, ...).

750 @kwarg n: Number of slices to generate (C{int}).

751 @kwarg fill: Optional fill value for missing items.

752

753 @return: A generator for each of B{C{n}} slices,

754 M{iterable[i::n] for i=0..n}.

755

756 @raise TypeError: Invalid B{C{n}}.

757

758 @note: Each generated slice is a C{tuple} or a C{list},

759 the latter only if the B{C{iterable}} is a C{list}.

760

761 @example:

762

763 >>> from pygeodesy import splice

764

765 >>> a, b = splice(range(10))

766 >>> a, b

767 ((0, 2, 4, 6, 8), (1, 3, 5, 7, 9))

768

769 >>> a, b, c = splice(range(10), n=3)

770 >>> a, b, c

771 ((0, 3, 6, 9), (1, 4, 7), (2, 5, 8))

772

773 >>> a, b, c = splice(range(10), n=3, fill=-1)

774 >>> a, b, c

775 ((0, 3, 6, 9), (1, 4, 7, -1), (2, 5, 8, -1))

776

777 >>> tuple(splice(list(range(9)), n=5))

778 ([0, 5], [1, 6], [2, 7], [3, 8], [4])

779

780 >>> splice(range(9), n=1)

781 <generator object splice at 0x0...>

782 '''

783 if not isint(n):

784 raise _TypeError(n=n)

785

786 t = _xiterablen(iterable)

787 if not isinstance(t, _list_tuple_types):

788 t = tuple(t)

789

790 if n > 1:

791 if fill:

792 fill = _xkwds_get1(fill, fill=MISSING)

793 if fill is not MISSING:

794 m = len(t) % n

795 if m > 0: # same type fill

796 t = t + type(t)((fill,) * (n - m))

797 for i in range(n):

798 # XXX t[i::n] chokes PyChecker

799 yield t[slice(i, None, n)]

800 else:

801 yield t # 1 slice, all

802

803

804 def _splituple(strs, *sep_splits): # in .mgrs, ...

805 '''(INTERNAL) Split a C{comma}- or C{whitespace}-separated

806 string into a C{tuple} of stripped C{str}ings.

807 '''

808 if sep_splits:

809 t = (t.strip() for t in strs.split(*sep_splits))

810 else:

811 t = strs.strip()

812 if t:

813 t = t.replace(_COMMA_, _SPACE_).split()

814 return tuple(t) if t else ()

815

816

817 def unsigned0(x):

818 '''Unsign if C{0.0}.

819

820 @return: C{B{x}} if B{C{x}} else C{0.0}.

821 '''

822 return x if x else _0_0

823

824

825 def _xcopy(obj, deep=False):

826 '''(INTERNAL) Copy an object, shallow or deep.

827

828 @arg obj: The object to copy (any C{type}).

829 @kwarg deep: If C{True}, make a deep, otherwise

830 a shallow copy (C{bool}).

831

832 @return: The copy of B{C{obj}}.

833 '''

834 return _deepcopy(obj) if deep else _copy(obj)

835

836

837 def _xcoverage(where, *required): # in .__main__ # PYCHOK no cover

838 '''(INTERNAL) Import C{coverage} and check required version.

839 '''

840 try:

841 _xpackages(_xcoverage)

842 import coverage

843 except ImportError as x:

844 raise _xImportError(x, where)

845 return _xversion(coverage, where, *required)

846

847

848 def _xdup(obj, deep=False, **items):

849 '''(INTERNAL) Duplicate an object, replacing some attributes.

850

851 @arg obj: The object to copy (any C{type}).

852 @kwarg deep: If C{True}, copy deep, otherwise shallow (C{bool}).

853 @kwarg items: Attributes to be changed (C{any}).

854

855 @return: A duplicate of B{C{obj}} with modified

856 attributes, if any B{C{items}}.

857

858 @raise AttributeError: Some B{C{items}} invalid.

859 '''

860 d = _xcopy(obj, deep=deep)

861 for n, v in items.items():

862 if getattr(d, n, v) != v:

863 setattr(d, n, v)

864 elif not hasattr(d, n):

865 t = _MODS.named.classname(obj)

866 t = _SPACE_(_DOT_(t, n), _invalid_)

867 raise _AttributeError(txt=t, obj=obj, **items)

868# if items:

869# _MODS.props._update_all(d)

870 return d

871

872

873 def _xgeographiclib(where, *required):

874 '''(INTERNAL) Import C{geographiclib} and check required version.

875 '''

876 try:

877 _xpackages(_xgeographiclib)

878 import geographiclib

879 except ImportError as x:

880 raise _xImportError(x, where, Error=LazyImportError)

881 return _xversion(geographiclib, where, *required)

882

883

884 def _xImportError(exc, where, Error=_ImportError, **name):

885 '''(INTERNAL) Embellish an C{Lazy/ImportError}.

886 '''

887 t = _req_d_by(where, **name)

888 return Error(_Xstr(exc), txt=t, cause=exc)

889

890

891 def _xinstanceof(*Types, **names_values):

892 '''(INTERNAL) Check C{Types} of all C{name=value} pairs.

893

894 @arg Types: One or more classes or types (C{class}), all

895 positional.

896 @kwarg names_values: One or more C{B{name}=value} pairs

897 with the C{value} to be checked.

898

899 @raise TypeError: One B{C{names_values}} pair is not an

900 instance of any of the B{C{Types}}.

901 '''

902 if not (Types and names_values):

903 raise _xAssertionError(_xinstanceof, *Types, **names_values)

904

905 for n, v in names_values.items():

906 if not isinstance(v, Types):

907 raise _TypesError(n, v, *Types)

908

909

910 def _xiterable(obj):

911 '''(INTERNAL) Return C{obj} if iterable, otherwise raise C{TypeError}.

912 '''

913 return obj if isiterable(obj) else _xiterror(obj, _xiterable) # PYCHOK None

914

915

916 def _xiterablen(obj):

917 '''(INTERNAL) Return C{obj} if iterable with C{__len__}, otherwise raise C{TypeError}.

918 '''

919 return obj if isiterablen(obj) else _xiterror(obj, _xiterablen) # PYCHOK None

920

921

922 def _xiterror(obj, _xwhich):

923 '''(INTERNAL) Helper for C{_xinterable} and C{_xiterablen}.

924 '''

925 t = typename(_xwhich)[2:] # less '_x'

926 raise _TypeError(repr(obj), txt=_not_(t))

927

928

929 def _xnumpy(where, *required):

930 '''(INTERNAL) Import C{numpy} and check required version.

931 '''

932 try:

933 _xpackages(_xnumpy)

934 import numpy

935 except ImportError as x:

936 raise _xImportError(x, where)

937 return _xversion(numpy, where, *required)

938

939

940 def _xor(x, *xs):

941 '''(INTERNAL) Exclusive-or C{x} and C{xs}.

942 '''

943 for x_ in xs:

944 x ^= x_

945 return x

946

947

948 def _xpackages(_xhich):

949 '''(INTERNAL) Check dependency to be excluded.

950 '''

951 if _XPACKAGES: # PYCHOK no cover

952 n = typename(_xhich)[2:] # less '_x'

953 if n.lower() in _XPACKAGES:

954 E = _PYGEODESY_ENV(_xpackages_)

955 x = _SPACE_(n, _in_, E)

956 e = _enquote(_getenv(E, NN))

957 raise ImportError(_EQUAL_(x, e))

958

959

960 def _xscalar(**names_values):

961 '''(INTERNAL) Check all C{name=value} pairs to be C{scalar}.

962 '''

963 for n, v in names_values.items():

964 if not isscalar(v):

965 raise _TypeError(n, v, txt=_not_scalar_)

966

967

968 def _xscipy(where, *required):

969 '''(INTERNAL) Import C{scipy} and check required version.

970 '''

971 try:

972 _xpackages(_xscipy)

973 import scipy

974 except ImportError as x:

975 raise _xImportError(x, where)

976 return _xversion(scipy, where, *required)

977

978

979 def _xsubclassof(*Classes, **names_values):

980 '''(INTERNAL) Check (super) class of all C{name=value} pairs.

981

982 @arg Classes: One or more classes or types (C{class}), all

983 positional.

984 @kwarg names_values: One or more C{B{name}=value} pairs

985 with the C{value} to be checked.

986

987 @raise TypeError: One B{C{names_values}} pair is not a

988 (sub-)class of any of the B{C{Classes}}.

989 '''

990 if not (Classes and names_values):

991 raise _xAssertionError(_xsubclassof, *Classes, **names_values)

992

993 for n, v in names_values.items():

994 if not issubclassof(v, *Classes):

995 raise _TypesError(n, v, *Classes)

996

997

998 def _xversion(package, where, *required, **name):

999 '''(INTERNAL) Check the C{package} version vs B{C{required}}.

1000 '''

1001 if required:

1002 t = _version_info(package)

1003 if t[:len(required)] < required:

1004 t = _SPACE_(typename(package),

1005 _version_, _DOT_(*t),

1006 _below_, _DOT_(*required),

1007 _req_d_by(where, **name))

1008 raise ImportError(t)

1009 return package

1010

1011

1012 def _xzip(*args, **strict): # PYCHOK no cover

1013 '''(INTERNAL) Standard C{zip(..., strict=True)}.

1014 '''

1015 s = _xkwds_get1(strict, strict=True)

1016 if s:

1017 if _zip is zip: # < (3, 10)

1018 t = _MODS.streprs.unstr(_xzip, *args, strict=s)

1019 raise _NotImplementedError(t, txt=None)

1020 return _zip(*args)

1021 return zip(*args)

1022

1023

1024 if _MODS.sys_version_info2 < (3, 10): # see .errors

1025 _zip = zip # PYCHOK exported

1026 else: # Python 3.10+

1027

1028 def _zip(*args):

1029 return zip(*args, strict=True)

1030

1031 _xpackages_ = typename(_xpackages).lstrip(_UNDER_)

1032 _XPACKAGES = _splituple(_envPYGEODESY(_xpackages_).lower()) # test/bases._X_OK

1033

1034# **) MIT License

1035#

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

1037#

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

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

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

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

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

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

1044#

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

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

1047#

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

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

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

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

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

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

1054# OTHER DEALINGS IN THE SOFTWARE.