1

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

3

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

5

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

7 2011-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

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

12 _signOf, _zip

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

14 _float0, isnear0, isnear1, isneg0, \

15 _pos_self, _1_0

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

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

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

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

20 from pygeodesy.named import _NamedBase, _NotImplemented, _xother3

21# from pygeodesy.namedTuples import Vector3Tuple # _MODS

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

23 property_doc_, property_RO, _update_all

24 from pygeodesy.streprs import Fmt, strs, unstr

25 from pygeodesy.units import Float, Scalar

26 from pygeodesy.utily import atan2, sincos2, fabs

27

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

29

30__all__ = _ALL_LAZY.vector3dBase

31__version__ = '24.11.24'

32

33

34 class 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

1037 def _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.