Coverage for pygeodesy/vector3dBase.py: 91%

287 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-05-04 12:01 -0400

1 

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

3 

4u'''(INTERNAL) Private, 3-D vector base class C{Vector3dBase}. 

5 

6A pure Python implementation of vector-based functions by I{(C) Chris Veness 

72011-2024} published under the same MIT Licence**, see U{Vector-based geodesy 

8<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}. 

9''' 

10 

11from pygeodesy.basics import _copysign, islistuple, isscalar, map1, map2, \ 

12 _signOf, _zip 

13from pygeodesy.constants import EPS, EPS0, INT0, PI, PI2, _copysignINF, \ 

14 _float0, isnear0, isnear1, isneg0, \ 

15 _pos_self, _1_0 

16from pygeodesy.errors import CrossError, VectorError, _xcallable, _xError 

17from pygeodesy.fmath import euclid_, fdot, fdot_, hypot_, hypot2_ # _MODS.fmath.fma 

18from pygeodesy.interns import _coincident_, _colinear_, _COMMASPACE_, _xyz_ 

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

20from pygeodesy.named import _NamedBase, _NotImplemented, _xother3 

21# from pygeodesy.namedTuples import Vector3Tuple # _MODS 

22from pygeodesy.props import deprecated_method, Property, Property_RO, \ 

23 property_doc_, property_RO, _update_all 

24from pygeodesy.streprs import Fmt, strs, unstr 

25from pygeodesy.units import Float, Scalar 

26from pygeodesy.utily import atan2, sincos2, fabs 

27 

28from math import ceil as _ceil, floor as _floor, trunc as _trunc 

29 

30__all__ = _ALL_LAZY.vector3dBase 

31__version__ = '24.11.24' 

32 

33 

34class Vector3dBase(_NamedBase): # sync __methods__ with .fsums.Fsum 

35 '''(INTERNAL) Generic 3-D vector base class. 

36 ''' 

37 _crosserrors = True # un/set by .errors.crosserrors 

38 

39 _ll = None # original latlon, '_fromll' 

40# _x = INT0 # X component 

41# _y = INT0 # Y component 

42# _z = INT0 # Z component 

43 

44 def __init__(self, x_xyz, y=INT0, z=INT0, ll=None, **name): 

45 '''New L{Vector3d} or C{Vector3dBase} instance. 

46 

47 The vector may be normalised or use x, y, z for position and 

48 distance from earth centre or height relative to the surface 

49 of the earth' sphere or ellipsoid. 

50 

51 @arg x_xyz: X component of vector (C{scalar}) or a (3-D) vector 

52 (C{Cartesian}, L{Ecef9Tuple}, C{Nvector}, L{Vector3d}, 

53 L{Vector3Tuple}, L{Vector4Tuple} or a C{tuple} or 

54 C{list} of 3+ C{scalar} items). 

55 @kwarg y: Y component of vector (C{scalar}), ignored if B{C{x_xyz}} 

56 is not C{scalar}, otherwise same units as B{C{x_xyz}}. 

57 @kwarg z: Z component of vector (C{scalar}), ignored if B{C{x_xyz}} 

58 is not C{scalar}, otherwise same units as B{C{x_xyz}}. 

59 @kwarg ll: Optional latlon reference (C{LatLon}). 

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

61 

62 @raise VectorError: Invalid B{C{x_xyz}}, B{C{y}} or B{C{z}}. 

63 ''' 

64 self._x, \ 

65 self._y, \ 

66 self._z = _xyz3(type(self), x_xyz, y, z) if isscalar(x_xyz) else \ 

67 _xyz3(type(self), x_xyz) 

68 if ll: 

69 self._ll = ll 

70 if name: 

71 self.name = name 

72 

73 def __abs__(self): 

74 '''Return the norm of this vector. 

75 

76 @return: Norm, unit length (C{float}); 

77 ''' 

78 return self.length 

79 

80 def __add__(self, other): 

81 '''Add this to an other vector (L{Vector3d}). 

82 

83 @return: Vectorial sum (L{Vector3d}). 

84 

85 @raise TypeError: Incompatible B{C{other}} C{type}. 

86 ''' 

87 return self.plus(other) 

88 

89 def __bool__(self): # PYCHOK PyChecker 

90 '''Is this vector non-zero? 

91 

92 @see: Method C{bools}. 

93 ''' 

94 return bool(self.x or self.y or self.z) 

95 

96 def __ceil__(self): # PYCHOK no cover 

97 '''Return a vector with the C{ceil} of these components. 

98 

99 @return: Ceil-ed (L{Vector3d}). 

100 ''' 

101 return self._mapped(_ceil) 

102 

103 def __cmp__(self, other): # Python 2- 

104 '''Compare this and an other vector (L{Vector3d}). 

105 

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

107 

108 @raise TypeError: Incompatible B{C{other}} C{type}. 

109 ''' 

110 return _signOf(self.length, self._other_cmp(other)) 

111 

112 cmp = __cmp__ 

113 

114 def __divmod__(self, other): # PYCHOK no cover 

115 '''Not implemented.''' 

116 return _NotImplemented(self, other) 

117 

118 def __eq__(self, other): 

119 '''Is this vector equal to an other vector? 

120 

121 @arg other: The other vector (L{Vector3d}). 

122 

123 @return: C{True} if equal, C{False} otherwise. 

124 

125 @raise TypeError: Incompatible B{C{other}} C{type}. 

126 ''' 

127 return self.isequalTo(other, eps=EPS0) 

128 

129 def __float__(self): # PYCHOK no cover 

130 '''Not implemented, see method C{float}.''' 

131 return _NotImplemented(self) # must return C{float} 

132 

133 def __floor__(self): # PYCHOK no cover 

134 '''Return a vector with the C{floor} of these components. 

135 

136 @return: Floor-ed (L{Vector3d}). 

137 ''' 

138 return self._mapped(_floor) 

139 

140 def __floordiv__(self, other): # PYCHOK no cover 

141 '''Not implemented.''' 

142 return _NotImplemented(self, other) 

143 

144 def __ge__(self, other): 

145 '''Is this vector longer than or equal to an other vector? 

146 

147 @arg other: The other vector (L{Vector3d}). 

148 

149 @return: C{True} if so, C{False} otherwise. 

150 

151 @raise TypeError: Incompatible B{C{other}} C{type}. 

152 ''' 

153 return self.length >= self._other_cmp(other) 

154 

155# def __getitem__(self, key): 

156# '''Return C{item} at index or slice C{[B{key}]}. 

157# ''' 

158# return self.xyz[key] 

159 

160 def __gt__(self, other): 

161 '''Is this vector longer than an other vector? 

162 

163 @arg other: The other vector (L{Vector3d}). 

164 

165 @return: C{True} if so, C{False} otherwise. 

166 

167 @raise TypeError: Incompatible B{C{other}} C{type}. 

168 ''' 

169 return self.length > self._other_cmp(other) 

170 

171 def __hash__(self): # PYCHOK no cover 

172 '''Return this instance' C{hash}. 

173 ''' 

174 return hash(self.xyz) # XXX id(self)? 

175 

176 def __iadd__(self, other): 

177 '''Add this and an other vector I{in-place}, C{this += B{other}}. 

178 

179 @arg other: The other vector (L{Vector3d}). 

180 

181 @raise TypeError: Incompatible B{C{other}} C{type}. 

182 ''' 

183 return self._xyz(self.plus(other)) 

184 

185 def __ifloordiv__(self, other): # PYCHOK no cover 

186 '''Not implemented.''' 

187 return _NotImplemented(self, other) 

188 

189 def __imatmul__(self, other): # PYCHOK Python 3.5+ 

190 '''Cross multiply this and an other vector I{in-place}, C{this @= B{other}}. 

191 

192 @arg other: The other vector (L{Vector3d}). 

193 

194 @raise TypeError: Incompatible B{C{other}} C{type}. 

195 

196 @see: Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 397+, 2022 p. 578+. 

197 ''' 

198 return self._xyz(self.cross(other)) 

199 

200 def __imod__(self, other): # PYCHOK no cover 

201 '''Not implemented.''' 

202 return _NotImplemented(self, other) 

203 

204 def __imul__(self, scalar): 

205 '''Multiply this vector by a scalar I{in-place}, C{this *= B{scalar}}. 

206 

207 @arg scalar: Factor (C{scalar}). 

208 

209 @raise TypeError: Non-scalar B{C{scalar}}. 

210 ''' 

211 return self._xyz(self.times(scalar)) 

212 

213 def __int__(self): # PYCHOK no cover 

214 '''Not implemented, see method C{ints}.''' 

215 return _NotImplemented(self) # must return C{int} 

216 

217 def __ipow__(self, other, *mod): # PYCHOK no cover 

218 '''Not implemented.''' 

219 return _NotImplemented(self, other, *mod) 

220 

221 def __isub__(self, other): 

222 '''Subtract an other vector from this one I{in-place}, C{this -= B{other}}. 

223 

224 @arg other: The other vector (L{Vector3d}). 

225 

226 @raise TypeError: Incompatible B{C{other}} C{type}. 

227 ''' 

228 return self._xyz(self.minus(other)) 

229 

230# def __iter__(self): 

231# '''Return an C{iter}ator over this vector's components. 

232# ''' 

233# return iter(self.xyz3) 

234 

235 def __itruediv__(self, scalar): 

236 '''Divide this vector by a scalar I{in-place}, C{this /= B{scalar}}. 

237 

238 @arg scalar: The divisor (C{scalar}). 

239 

240 @raise TypeError: Non-scalar B{C{scalar}}. 

241 ''' 

242 return self._xyz(self.dividedBy(scalar)) 

243 

244 def __le__(self, other): # Python 3+ 

245 '''Is this vector shorter than or equal to an other vector? 

246 

247 @arg other: The other vector (L{Vector3d}). 

248 

249 @return: C{True} if so, C{False} otherwise. 

250 

251 @raise TypeError: Incompatible B{C{other}} C{type}. 

252 ''' 

253 return self.length <= self._other_cmp(other) 

254 

255# def __len__(self): 

256# '''Return C{3}, always. 

257# ''' 

258# return len(self.xyz) 

259 

260 def __lt__(self, other): # Python 3+ 

261 '''Is this vector shorter than an other vector? 

262 

263 @arg other: The other vector (L{Vector3d}). 

264 

265 @return: C{True} if so, C{False} otherwise. 

266 

267 @raise TypeError: Incompatible B{C{other}} C{type}. 

268 ''' 

269 return self.length < self._other_cmp(other) 

270 

271 def __matmul__(self, other): # PYCHOK Python 3.5+ 

272 '''Compute the cross product of this and an other vector, C{this @ B{other}}. 

273 

274 @arg other: The other vector (L{Vector3d}). 

275 

276 @return: Cross product (L{Vector3d}). 

277 

278 @raise TypeError: Incompatible B{C{other}} C{type}. 

279 ''' 

280 return self.cross(other) 

281 

282 def __mod__(self, other): # PYCHOK no cover 

283 '''Not implemented.''' 

284 return _NotImplemented(self, other) 

285 

286 def __mul__(self, scalar): 

287 '''Multiply this vector by a scalar, C{this * B{scalar}}. 

288 

289 @arg scalar: Factor (C{scalar}). 

290 

291 @return: Product (L{Vector3d}). 

292 ''' 

293 return self.times(scalar) 

294 

295 def __ne__(self, other): 

296 '''Is this vector not equal to an other vector? 

297 

298 @arg other: The other vector (L{Vector3d}). 

299 

300 @return: C{True} if so, C{False} otherwise. 

301 

302 @raise TypeError: Incompatible B{C{other}} C{type}. 

303 ''' 

304 return not self.isequalTo(other, eps=EPS0) 

305 

306 def __neg__(self): 

307 '''Return the opposite of this vector. 

308 

309 @return: A negated copy (L{Vector3d}) 

310 ''' 

311 return self.negate() 

312 

313 def __pos__(self): # PYCHOK no cover 

314 '''Return this vector I{as-is} or a copy. 

315 

316 @return: This instance (L{Vector3d}) 

317 ''' 

318 return self if _pos_self else self.copy() 

319 

320 def __pow__(self, other, *mod): # PYCHOK no cover 

321 '''Not implemented.''' 

322 return _NotImplemented(self, other, *mod) 

323 

324 __radd__ = __add__ # PYCHOK no cover 

325 

326 def __rdivmod__ (self, other): # PYCHOK no cover 

327 '''Not implemented.''' 

328 return _NotImplemented(self, other) 

329 

330# def __repr__(self): 

331# '''Return the default C{repr(this)}. 

332# ''' 

333# return self.toRepr() 

334 

335 def __rfloordiv__(self, other): # PYCHOK no cover 

336 '''Not implemented.''' 

337 return _NotImplemented(self, other) 

338 

339 def __rmatmul__(self, other): # PYCHOK Python 3.5+ 

340 '''Compute the cross product of an other and this vector, C{B{other} @ this}. 

341 

342 @arg other: The other vector (L{Vector3d}). 

343 

344 @return: Cross product (L{Vector3d}). 

345 

346 @raise TypeError: Incompatible B{C{other}} C{type}. 

347 ''' 

348 return self.others(other).cross(self) 

349 

350 def __rmod__(self, other): # PYCHOK no cover 

351 '''Not implemented.''' 

352 return _NotImplemented(self, other) 

353 

354 __rmul__ = __mul__ 

355 

356 def __round__(self, *ndigits): # PYCHOK no cover 

357 '''Return a vector with these components C{rounded}. 

358 

359 @arg ndigits: Optional number of digits (C{int}). 

360 

361 @return: Rounded (L{Vector3d}). 

362 ''' 

363 # <https://docs.Python.org/3.12/reference/datamodel.html?#object.__round__> 

364 return self.classof(*(round(_, *ndigits) for _ in self.xyz3)) 

365 

366 def __rpow__(self, other, *mod): # PYCHOK no cover 

367 '''Not implemented.''' 

368 return _NotImplemented(self, other, *mod) 

369 

370 def __rsub__(self, other): # PYCHOK no cover 

371 '''Subtract this vector from an other vector, C{B{other} - this}. 

372 

373 @arg other: The other vector (L{Vector3d}). 

374 

375 @return: Difference (L{Vector3d}). 

376 

377 @raise TypeError: Incompatible B{C{other}} C{type}. 

378 ''' 

379 return self.others(other).minus(self) 

380 

381 def __rtruediv__(self, scalar): # PYCHOK no cover 

382 '''Not implemented.''' 

383 return _NotImplemented(self, scalar) 

384 

385# def __str__(self): 

386# '''Return the default C{str(self)}. 

387# ''' 

388# return self.toStr() 

389 

390 def __sub__(self, other): 

391 '''Subtract an other vector from this vector, C{this - B{other}}. 

392 

393 @arg other: The other vector (L{Vector3d}). 

394 

395 @return: Difference (L{Vector3d}). 

396 

397 @raise TypeError: Incompatible B{C{other}} C{type}. 

398 ''' 

399 return self.minus(other) 

400 

401 def __truediv__(self, scalar): 

402 '''Divide this vector by a scalar, C{this / B{scalar}}. 

403 

404 @arg scalar: The divisor (C{scalar}). 

405 

406 @return: Quotient (L{Vector3d}). 

407 

408 @raise TypeError: Non-scalar B{C{scalar}}. 

409 ''' 

410 return self.dividedBy(scalar) 

411 

412 def __trunc__(self): # PYCHOK no cover 

413 '''Return a vector with the C{trunc} of these components. 

414 

415 @return: Trunc-ed (L{Vector3d}). 

416 ''' 

417 return self._mapped(_trunc) 

418 

419 if _MODS.sys_version_info2 < (3, 0): # PYCHOK no cover 

420 # <https://docs.Python.org/2/library/operator.html#mapping-operators-to-functions> 

421 __div__ = __truediv__ 

422 __idiv__ = __itruediv__ 

423 __long__ = __int__ 

424 __nonzero__ = __bool__ 

425 __rdiv__ = __rtruediv__ 

426 

427 def angleTo(self, other, vSign=None, wrap=False): 

428 '''Compute the angle between this and an other vector. 

429 

430 @arg other: The other vector (L{Vector3d}). 

431 @kwarg vSign: Optional vector, if supplied (and out of the 

432 plane of this and the other), angle is signed 

433 positive if this->other is clockwise looking 

434 along vSign or negative in opposite direction, 

435 otherwise angle is unsigned. 

436 @kwarg wrap: If C{True}, wrap/unroll the angle to +/-PI (C{bool}). 

437 

438 @return: Angle (C{radians}). 

439 

440 @raise TypeError: If B{C{other}} or B{C{vSign}} not a L{Vector3d}. 

441 ''' 

442 x = self.cross(other) 

443 s = x.length 

444 # use vSign as reference to set sign of s 

445 if s and vSign and x.dot(vSign) < 0: 

446 s = -s 

447 

448 a = atan2(s, self.dot(other)) 

449 if wrap and fabs(a) > PI: 

450 a -= _copysign(PI2, a) 

451 return a 

452 

453 def apply(self, fun2, other_x, *y_z, **fun2_kwds): 

454 '''Apply a 2-argument function pairwise to the components 

455 of this and an other vector. 

456 

457 @arg fun2: 2-Argument callable (C{any(scalar, scalar}), 

458 return a C{scalar} or L{INT0} result. 

459 @arg other_x: Other X component (C{scalar}) or a vector 

460 with X, Y and Z components (C{Cartesian}, 

461 L{Ecef9Tuple}, C{Nvector}, L{Vector3d}, 

462 L{Vector3Tuple} or L{Vector4Tuple}). 

463 @arg y_z: Other Y and Z components, positional (C{scalar}, C{scalar}). 

464 @kwarg fun2_kwds: Optional keyword arguments for B{C{fun2}}. 

465 

466 @return: New, applied vector (L{Vector3d}). 

467 

468 @raise ValueError: Invalid B{C{other_x}} or B{C{y_z}}. 

469 ''' 

470 _xcallable(fun2=fun2) 

471 if fun2_kwds: 

472 def _f2(a, b): 

473 return fun2(a, b, **fun2_kwds) 

474 else: 

475 _f2 = fun2 

476 

477 xyz = _xyz3(self.apply, other_x, *y_z) 

478 xyz = (_f2(a, b) for a, b in _zip(self.xyz3, xyz)) # strict=True 

479 return self.classof(*xyz) 

480 

481 def bools(self): 

482 '''Return the vector with C{bool} components. 

483 ''' 

484 return self._mapped(bool) 

485 

486 def cross(self, other, raiser=None, eps0=EPS): # raiser=NN 

487 '''Compute the cross product of this and an other vector. 

488 

489 @arg other: The other vector (L{Vector3d}). 

490 @kwarg raiser: Optional, L{CrossError} label if raised (C{str}, non-L{NN}). 

491 @kwarg eps0: Near-zero tolerance (C{scalar}), same units as C{x}, C{y} and 

492 C{z}. 

493 

494 @return: Cross product (L{Vector3d}). 

495 

496 @raise CrossError: Zero or near-zero cross product and if B{C{raiser}} and 

497 L{crosserrors<pygeodesy.crosserrors>} are both C{True}. 

498 

499 @raise TypeError: Incompatible B{C{other}} C{type}. 

500 ''' 

501 X, Y, Z = self.others(other).xyz3 

502 x, y, z = self.xyz3 

503 xyz = (fdot_(y, Z, -z, Y), 

504 fdot_(z, X, -x, Z), 

505 fdot_(x, Y, -y, X)) 

506 

507 if raiser and self.crosserrors and eps0 > 0 \ 

508 and max(map(fabs, xyz)) < eps0: 

509 r = other._fromll or other 

510 s = self._fromll or self 

511 t = self.isequalTo(other, eps=eps0) 

512 t = _coincident_ if t else _colinear_ 

513 raise CrossError(raiser, s, other=r, txt=t) 

514 

515 return self.classof(*xyz) # name__=self.cross 

516 

517 @property_doc_('''raise or ignore L{CrossError} exceptions (C{bool}).''') 

518 def crosserrors(self): 

519 '''Get L{CrossError} exceptions (C{bool}). 

520 ''' 

521 return self._crosserrors 

522 

523 @crosserrors.setter # PYCHOK setter! 

524 def crosserrors(self, raiser): 

525 '''Raise or ignore L{CrossError} exceptions (C{bool}). 

526 ''' 

527 self._crosserrors = bool(raiser) 

528 

529 def dividedBy(self, divisor): 

530 '''Divide this vector by a scalar. 

531 

532 @arg divisor: The divisor (C{scalar}). 

533 

534 @return: New, scaled vector (L{Vector3d}). 

535 

536 @raise TypeError: Non-scalar B{C{divisor}}. 

537 

538 @raise VectorError: Invalid or zero B{C{divisor}}. 

539 ''' 

540 d = Scalar(divisor=divisor) 

541 try: 

542 return self._times(_1_0 / d) 

543 except (ValueError, ZeroDivisionError) as x: 

544 raise VectorError(divisor=divisor, cause=x) 

545 

546 def dot(self, other): 

547 '''Compute the dot (scalar) product of this and an other vector. 

548 

549 @arg other: The other vector (L{Vector3d}). 

550 

551 @return: Dot product (C{float}). 

552 

553 @raise TypeError: Incompatible B{C{other}} C{type}. 

554 ''' 

555 return self.length2 if other is self else fdot( 

556 self.xyz3, *self.others(other).xyz3) 

557 

558 @deprecated_method 

559 def equals(self, other, units=False): # PYCHOK no cover 

560 '''DEPRECATED, use method C{isequalTo}. 

561 ''' 

562 return self.isequalTo(other, units=units) 

563 

564 @Property_RO 

565 def euclid(self): 

566 '''I{Approximate} the length (norm, magnitude) of this vector (C{Float}). 

567 

568 @see: Properties C{length} and C{length2} and function 

569 L{pygeodesy.euclid_}. 

570 ''' 

571 return Float(euclid=euclid_(*self.xyz3)) 

572 

573 def equirectangular(self, other): 

574 '''I{Approximate} the difference between this and an other vector. 

575 

576 @arg other: Vector to subtract (C{Vector3dBase}). 

577 

578 @return: The length I{squared} of the difference (C{Float}). 

579 

580 @raise TypeError: Incompatible B{C{other}} C{type}. 

581 

582 @see: Property C{length2}. 

583 ''' 

584 d = self.minus(other) 

585 return Float(equirectangular=hypot2_(*d.xyz3)) 

586 

587 def fabs(self): 

588 '''Return the vector with C{fabs} components. 

589 ''' 

590 return self._mapped(fabs) 

591 

592 def floats(self): 

593 '''Return the vector with C{float} components. 

594 ''' 

595 return self._mapped(_float0) 

596 

597 @Property 

598 def _fromll(self): 

599 '''(INTERNAL) Get the latlon reference (C{LatLon}) or C{None}. 

600 ''' 

601 return self._ll 

602 

603 @_fromll.setter # PYCHOK setter! 

604 def _fromll(self, ll): 

605 '''(INTERNAL) Set the latlon reference (C{LatLon}) or C{None}. 

606 ''' 

607 self._ll = ll or None 

608 

609 @property_RO 

610 def homogeneous(self): 

611 '''Get this vector's homogeneous representation (L{Vector3d}). 

612 ''' 

613 x, y, z = self.xyz3 

614 if z: 

615 x = x / z # /= chokes PyChecker 

616 y = y / z 

617# z = _1_0 

618 else: 

619 if isneg0(z): 

620 x = -x 

621 y = -y 

622 x = _copysignINF(x) 

623 y = _copysignINF(y) 

624# z = NAN 

625 return self.classof(x, y, _1_0) 

626 

627 def intermediateTo(self, other, fraction, **unused): # height=None, wrap=False 

628 '''Locate the vector at a given fraction between (or along) this 

629 and an other vector. 

630 

631 @arg other: The other vector (L{Vector3d}). 

632 @arg fraction: Fraction between both vectors (C{scalar}, 

633 0.0 for this and 1.0 for the other vector). 

634 

635 @return: Intermediate vector (L{Vector3d}). 

636 

637 @raise TypeError: Incompatible B{C{other}} C{type}. 

638 ''' 

639 f = Scalar(fraction=fraction) 

640 if isnear0(f): # PYCHOK no cover 

641 r = self 

642 else: 

643 r = self.others(other) 

644 if not isnear1(f): # self * (1 - f) + r * f 

645 r = self.plus(r.minus(self)._times(f)) 

646 return r 

647 

648 def ints(self): 

649 '''Return the vector with C{int} components. 

650 ''' 

651 return self._mapped(int) 

652 

653 def isconjugateTo(self, other, minum=1, eps=EPS): 

654 '''Determine whether this and an other vector are conjugates. 

655 

656 @arg other: The other vector (C{Cartesian}, L{Ecef9Tuple}, 

657 L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). 

658 @kwarg minum: Minimal number of conjugates required (C{int}, 0..3). 

659 @kwarg eps: Tolerance for equality and conjugation (C{scalar}), 

660 same units as C{x}, C{y}, and C{z}. 

661 

662 @return: C{True} if both vector's components either match 

663 or at least C{B{minum}} have opposite signs. 

664 

665 @raise TypeError: Incompatible B{C{other}} C{type}. 

666 

667 @see: Method C{isequalTo}. 

668 ''' 

669 self.others(other) 

670 n = 0 

671 for a, b in zip(self.xyz3, other.xyz3): 

672 if fabs(a + b) < eps and ((a < 0 and b > 0) or 

673 (a > 0 and b < 0)): 

674 n += 1 # conjugate 

675 elif fabs(a - b) > eps: 

676 return False # unequal 

677 return bool(n >= minum) 

678 

679 def isequalTo(self, other, units=False, eps=EPS): 

680 '''Check if this and an other vector are equal or equivalent. 

681 

682 @arg other: The other vector (L{Vector3d}). 

683 @kwarg units: Optionally, compare the normalized, unit 

684 version of both vectors. 

685 @kwarg eps: Tolerance for equality (C{scalar}), same units as 

686 C{x}, C{y}, and C{z}. 

687 

688 @return: C{True} if vectors are identical, C{False} otherwise. 

689 

690 @raise TypeError: Incompatible B{C{other}} C{type}. 

691 

692 @see: Method C{isconjugateTo}. 

693 ''' 

694 if units: 

695 self.others(other) 

696 d = self.unit().minus(other.unit()) 

697 else: 

698 d = self.minus(other) 

699 return max(map(fabs, d.xyz3)) < eps 

700 

701 @Property_RO 

702 def length(self): # __dict__ value overwritten by Property_RO C{_united} 

703 '''Get the length (norm, magnitude) of this vector (C{Float}). 

704 

705 @see: Properties L{length2} and L{euclid}. 

706 ''' 

707 return Float(length=hypot_(self.x, self.y, self.z)) 

708 

709 @Property_RO 

710 def length2(self): # __dict__ value overwritten by Property_RO C{_united} 

711 '''Get the length I{squared} of this vector (C{Float}). 

712 

713 @see: Property L{length} and method C{equirectangular}. 

714 ''' 

715 return Float(length2=hypot2_(self.x, self.y, self.z)) 

716 

717 def _mapped(self, func): 

718 '''(INTERNAL) Apply C{func} to all components. 

719 ''' 

720 return self.classof(*map2(func, self.xyz3)) 

721 

722 def minus(self, other): 

723 '''Subtract an other vector from this vector. 

724 

725 @arg other: The other vector (L{Vector3d}). 

726 

727 @return: New vector difference (L{Vector3d}). 

728 

729 @raise TypeError: Incompatible B{C{other}} C{type}. 

730 ''' 

731 return self._minus(*self.others(other).xyz3) 

732 

733 def _minus(self, x, y, z): 

734 '''(INTERNAL) Helper for methods C{.minus} and C{.minus_}. 

735 ''' 

736 return self.classof(self.x - x, self.y - y, self.z - z) 

737 

738 def minus_(self, other_x, *y_z): 

739 '''Subtract separate X, Y and Z components from this vector. 

740 

741 @arg other_x: X component (C{scalar}) or a vector's 

742 X, Y, and Z components (C{Cartesian}, 

743 L{Ecef9Tuple}, C{Nvector}, L{Vector3d}, 

744 L{Vector3Tuple}, L{Vector4Tuple}). 

745 @arg y_z: Y and Z components (C{scalar}, C{scalar}), 

746 ignored if B{C{other_x}} is not C{scalar}. 

747 

748 @return: New, vectiorial vector (L{Vector3d}). 

749 

750 @raise ValueError: Invalid B{C{other_x}} or B{C{y_z}}. 

751 ''' 

752 return self._minus(*_xyz3(self.minus_, other_x, *y_z)) 

753 

754 def negate(self): 

755 '''Return the opposite of this vector. 

756 

757 @return: A negated copy (L{Vector3d}) 

758 ''' 

759 return self.classof(-self.x, -self.y, -self.z) 

760 

761 @Property_RO 

762 def _N_vector(self): 

763 '''(INTERNAL) Get the (C{nvectorBase._N_vector_}) 

764 ''' 

765 return _MODS.nvectorBase._N_vector_(*self.xyz3, name=self.name) 

766 

767 def _other_cmp(self, other): 

768 '''(INTERNAL) Return the value for comparison. 

769 ''' 

770 return other if isscalar(other) else self.others(other).length 

771 

772 def others(self, *other, **name_other_up): 

773 '''Refined class comparison. 

774 

775 @arg other: The other vector (L{Vector3d}). 

776 @kwarg name_other_up: Overriding C{name=other} and C{up=1} 

777 keyword arguments. 

778 

779 @return: The B{C{other}} if compatible. 

780 

781 @raise TypeError: Incompatible B{C{other}} C{type}. 

782 ''' 

783 other, name, up = _xother3(self, other, **name_other_up) 

784 if not isinstance(other, Vector3dBase): 

785 _NamedBase.others(self, other, name=name, up=up + 1) 

786 return other 

787 

788 def plus(self, other): 

789 '''Add this vector and an other vector. 

790 

791 @arg other: The other vector (L{Vector3d}). 

792 

793 @return: Vectorial sum (L{Vector3d}). 

794 

795 @raise TypeError: Incompatible B{C{other}} C{type}. 

796 ''' 

797 return self._plus(*self.others(other).xyz3) 

798 

799 sum = plus # alternate name 

800 

801 def _plus(self, x, y, z): 

802 '''(INTERNAL) Helper for methods C{.plus} and C{.plus_}. 

803 ''' 

804 return self.classof(self.x + x, self.y + y, self.z + z) 

805 

806 def plus_(self, other_x, *y_z): 

807 '''Sum of this vector and separate X, Y and Z components. 

808 

809 @arg other_x: X component (C{scalar}) or a vector's 

810 X, Y, and Z components (C{Cartesian}, 

811 L{Ecef9Tuple}, C{Nvector}, L{Vector3d}, 

812 L{Vector3Tuple}, L{Vector4Tuple}). 

813 @arg y_z: Y and Z components (C{scalar}, C{scalar}), 

814 ignored if B{C{other_x}} is not C{scalar}. 

815 

816 @return: New, vectiorial vector (L{Vector3d}). 

817 

818 @raise ValueError: Invalid B{C{other_x}} or B{C{y_z}}. 

819 ''' 

820 return self._plus(*_xyz3(self.plus_, other_x, *y_z)) 

821 

822 def rotate(self, axis, theta, fma=False): 

823 '''Rotate this vector around an axis by a specified angle. 

824 

825 @arg axis: The axis being rotated around (L{Vector3d}). 

826 @arg theta: The angle of rotation (C{radians}). 

827 @kwarg fma: If C{True}, use fused-multiply-add (C{bool}). 

828 

829 @return: New, rotated vector (L{Vector3d}). 

830 

831 @see: U{Rotation matrix from axis and angle<https://WikiPedia.org/wiki/ 

832 Rotation_matrix#Rotation_matrix_from_axis_and_angle>} and 

833 U{Quaternion-derived rotation matrix<https://WikiPedia.org/wiki/ 

834 Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix>}. 

835 ''' 

836 s, c = sincos2(theta) # rotation angle 

837 d = _1_0 - c 

838 if d or s: 

839 p = self.unit().xyz # point being rotated 

840 r = self.others(axis=axis).unit() # axis being rotated around 

841 

842 ax, ay, az = r.xyz3 # quaternion-derived rotation matrix 

843 bx, by, bz = r.times(d).xyz3 

844 sx, sy, sz = r.times(s).xyz3 

845 

846 if fma: 

847 _fma = _MODS.fmath.fma 

848 else: 

849 def _fma(a, b, c): 

850 return a * b + c 

851 

852 x = fdot(p, _fma(ax, bx, c), _fma(ax, by, -sz), _fma(ax, bz, sy)) 

853 y = fdot(p, _fma(ay, bx, sz), _fma(ay, by, c), _fma(ay, bz, -sx)) 

854 z = fdot(p, _fma(az, bx, -sy), _fma(az, by, sx), _fma(az, bz, c)) 

855 else: # unrotated 

856 x, y, z = self.xyz3 

857 return self.classof(x, y, z) 

858 

859 @deprecated_method 

860 def rotateAround(self, axis, theta): 

861 '''DEPRECATED, use method C{rotate}.''' 

862 return self.rotate(axis, theta) # PYCHOK no cover 

863 

864 def times(self, factor): 

865 '''Multiply this vector by a scalar. 

866 

867 @arg factor: Scale factor (C{scalar}). 

868 

869 @return: New, scaled vector (L{Vector3d}). 

870 

871 @raise TypeError: Non-scalar B{C{factor}}. 

872 ''' 

873 return self._times(Scalar(factor=factor)) 

874 

875 def _times(self, s): 

876 '''(INTERNAL) Helper for C{.dividedBy} and C{.times}. 

877 ''' 

878 return self.classof(self.x * s, self.y * s, self.z * s) 

879 

880 def times_(self, other_x, *y_z): 

881 '''Multiply this vector's components by separate X, Y and Z factors. 

882 

883 @arg other_x: X scale factor (C{scalar}) or a vector's 

884 X, Y, and Z components as scale factors 

885 (C{Cartesian}, L{Ecef9Tuple}, C{Nvector}, 

886 L{Vector3d}, L{Vector3Tuple}, L{Vector4Tuple}). 

887 @arg y_z: Y and Z scale factors (C{scalar}, C{scalar}), 

888 ignored if B{C{other_x}} is not C{scalar}. 

889 

890 @return: New, scaled vector (L{Vector3d}). 

891 

892 @raise ValueError: Invalid B{C{other_x}} or B{C{y_z}}. 

893 ''' 

894 x, y, z = _xyz3(self.times_, other_x, *y_z) 

895 return self.classof(self.x * x, self.y * y, self.z * z) 

896 

897# @deprecated_method 

898# def to2ab(self): # PYCHOK no cover 

899# '''DEPRECATED, use property C{Nvector.philam}. 

900# 

901# @return: A L{PhiLam2Tuple}C{(phi, lam)}. 

902# ''' 

903# return _MODS.nvectorBase.n_xyz2philam(self.x, self.y, self.z) 

904 

905# @deprecated_method 

906# def to2ll(self): # PYCHOK no cover 

907# '''DEPRECATED, use property C{Nvector.latlon}. 

908# 

909# @return: A L{LatLon2Tuple}C{(lat, lon)}. 

910# ''' 

911# return _MODS.nvectorBase.n_xyz2latlon(self.x, self.y, self.z) 

912 

913 @deprecated_method 

914 def to3xyz(self): # PYCHOK no cover 

915 '''DEPRECATED, use property L{xyz}. 

916 ''' 

917 return self.xyz 

918 

919 def toStr(self, prec=5, fmt=Fmt.PAREN, sep=_COMMASPACE_): # PYCHOK expected 

920 '''Return a string representation of this vector. 

921 

922 @kwarg prec: Number of decimal places (C{int}). 

923 @kwarg fmt: Enclosing format to use (C{str}). 

924 @kwarg sep: Separator between components (C{str}). 

925 

926 @return: Vector as "(x, y, z)" (C{str}). 

927 ''' 

928 t = sep.join(strs(self.xyz3, prec=prec)) 

929 return (fmt % (t,)) if fmt else t 

930 

931 def unit(self, ll=None): 

932 '''Normalize this vector to unit length. 

933 

934 @kwarg ll: Optional, original location (C{LatLon}). 

935 

936 @return: Normalized vector (L{Vector3d}). 

937 ''' 

938 u = self._united 

939 if ll: 

940 u._fromll = ll 

941 return u 

942 

943 @Property_RO 

944 def _united(self): # __dict__ value overwritten below 

945 '''(INTERNAL) Get normalized vector (L{Vector3d}). 

946 ''' 

947 n = self.length 

948 if n > EPS0 and fabs(n - _1_0) > EPS0: 

949 u = self._xnamed(self.dividedBy(n)) 

950 u._update(False, length=_1_0, length2=_1_0, _united=u) 

951 else: 

952 u = self.copy() 

953 u._update(False, _united=u) 

954 if self._fromll: 

955 u._fromll = self._fromll 

956 return u 

957 

958 @Property 

959 def x(self): 

960 '''Get the X component (C{float}). 

961 ''' 

962 return self._x 

963 

964 @x.setter # PYCHOK setter! 

965 def x(self, x): 

966 '''Set the X component, if different (C{float}). 

967 ''' 

968 x = Float(x=x) 

969 if self._x != x: 

970 _update_all(self, needed=3) 

971 self._x = x 

972 

973 @Property 

974 def xyz(self): 

975 '''Get the X, Y and Z components (L{Vector3Tuple}C{(x, y, z)}). 

976 ''' 

977 return _MODS.namedTuples.Vector3Tuple(*self.xyz3, name=self.name) 

978 

979 @xyz.setter # PYCHOK setter! 

980 def xyz(self, xyz): 

981 '''Set the X, Y and Z components (C{Cartesian}, L{Ecef9Tuple}, 

982 C{Nvector}, L{Vector3d}, L{Vector3Tuple}, L{Vector4Tuple} 

983 or a C{tuple} or C{list} of 3+ C{scalar} items). 

984 ''' 

985 self._xyz(xyz) 

986 

987 def _xyz(self, x_xyz, *y_z): 

988 '''(INTERNAL) Set the C{_x}, C{_y} and C{_z} attributes. 

989 ''' 

990 _update_all(self, needed=3) 

991 self._x, self._y, self._z = _xyz3(_xyz_, x_xyz, *y_z) 

992 return self 

993 

994 @property_RO 

995 def xyz3(self): 

996 '''Get the X, Y and Z components as C{3-tuple}. 

997 ''' 

998 return self.x, self.y, self.z 

999 

1000 @property_RO 

1001 def x2y2z2(self): 

1002 '''Get the X, Y and Z components I{squared} (3-tuple C{(x**2, y**2, z**2)}). 

1003 ''' 

1004 return self.x**2, self.y**2, self.z**2 

1005 

1006 @Property 

1007 def y(self): 

1008 '''Get the Y component (C{float}). 

1009 ''' 

1010 return self._y 

1011 

1012 @y.setter # PYCHOK setter! 

1013 def y(self, y): 

1014 '''Set the Y component, if different (C{float}). 

1015 ''' 

1016 y = Float(y=y) 

1017 if self._y != y: 

1018 _update_all(self, needed=3) 

1019 self._y = y 

1020 

1021 @Property 

1022 def z(self): 

1023 '''Get the Z component (C{float}). 

1024 ''' 

1025 return self._z 

1026 

1027 @z.setter # PYCHOK setter! 

1028 def z(self, z): 

1029 '''Set the Z component, if different (C{float}). 

1030 ''' 

1031 z = Float(z=z) 

1032 if self._z != z: 

1033 _update_all(self, needed=3) 

1034 self._z = z 

1035 

1036 

1037def _xyz3(where, x_xyz, *y_z): # in .cartesianBase._rtp3 

1038 '''(INTERNAL) Get , Y and Z as 3-tuple C{(x, y, z)}. 

1039 ''' 

1040 try: 

1041 xyz3 = map1(_float0, x_xyz, *y_z) if y_z else ( # islistuple for Vector*Tuple 

1042 map2(_float0, x_xyz[:3]) if islistuple(x_xyz, minum=3) else 

1043 x_xyz.xyz) # .xyz3 

1044 except (AttributeError, TypeError, ValueError) as x: 

1045 raise _xError(x, unstr(where, x_xyz, *y_z)) 

1046 return xyz3 

1047 

1048 

1049__all__ += _ALL_DOCS(Vector3dBase) 

1050 

1051# **) MIT License 

1052# 

1053# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved. 

1054# 

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

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

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

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

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

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

1061# 

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

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

1064# 

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

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

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

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

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

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

1071# OTHER DEALINGS IN THE SOFTWARE.