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-2015} 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, \

12 map2, _signOf, _zip

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

14 _float0, isnear0, isnear1, isneg0, \

15 _pos_self, _0_0, _1_0

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

17 from pygeodesy.fmath import euclid_, fdot, hypot_, hypot2_

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

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

20 _sys_version_info2

21 from pygeodesy.named import _NamedBase, _NotImplemented, _xother3

22# from pygeodesy.namedTuples import Vector3Tuple # _MODS

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

24 property_doc_, property_RO, _update_all

25 from pygeodesy.streprs import Fmt, strs, unstr

26 from pygeodesy.units import Float, Scalar

27# from pygeodesy.utily import sincos2 # _MODS

28

29# from builtints import hash, int, isinstance, map, max, round, type, zip

30 from math import atan2, ceil, fabs, floor, trunc

31

32__all__ = _ALL_LAZY.vector3dBase

33__version__ = '24.06.07'

34

35

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

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

38 '''

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

40

41 _ll = None # original latlon, '_fromll'

42# _x = INT0 # X component

43# _y = INT0 # Y component

44# _z = INT0 # Z component

45

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

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

48

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

50 distance from earth centre or height relative to the surface

51 of the earth' sphere or ellipsoid.

52

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

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

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

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

57 @kwarg y: Y 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 z: Z component of vector (C{scalar}), ignored if B{C{x_xyz}}

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

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

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

63

64 @raise VectorError: Invalid B{C{x_xyz}}.

65 '''

66 self._x, \

67 self._y, \

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

69 _xyz3(type(self), x_xyz)

70 if ll:

71 self._ll = ll

72 if name:

73 self.name = name

74

75 def __abs__(self):

76 '''Return the norm of this vector.

77

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

79 '''

80 return self.length

81

82 def __add__(self, other):

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

84

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

86

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

88 '''

89 return self.plus(other)

90

91 def __bool__(self): # PYCHOK PyChecker

92 '''Is this vector non-zero?

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.'''

131 return _NotImplemented(self)

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 __format__(self, *other): # PYCHOK no cover

145 '''Not implemented.'''

146 return _NotImplemented(self, *other)

147

148 def __ge__(self, other):

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

150

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

152

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

154

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

156 '''

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

158

159# def __getitem__(self, key):

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

161# '''

162# return self.xyz[key]

163

164 def __gt__(self, other):

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

166

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

168

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

170

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

172 '''

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

174

175 def __hash__(self): # PYCHOK no cover

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

177 '''

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

179

180 def __iadd__(self, other):

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

182

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

184

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

186 '''

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

188

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

190 '''Not implemented.'''

191 return _NotImplemented(self, other)

192

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

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

195

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

197

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

199

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

201 '''

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

203

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

205 '''Not implemented.'''

206 return _NotImplemented(self, other)

207

208 def __imul__(self, scalar):

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

210

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

212

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

214 '''

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

216

217 def __int__(self): # PYCHOK no cover

218 '''Return a vector with the C{int} of these components.

219

220 @return: Int-ed (L{Vector3d}).

221 '''

222 v = self.classof(_0_0)

223 v._x, v._y, v._z = map2(int, self.xyz)

224 return v

225

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

227 '''Not implemented.'''

228 return _NotImplemented(self, other, *mod)

229

230 def __isub__(self, other):

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

232

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

234

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

236 '''

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

238

239# def __iter__(self):

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

241# '''

242# return iter(self.xyz)

243

244 def __itruediv__(self, scalar):

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

246

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

248

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

250 '''

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

252

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

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

255

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

257

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

259

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

261 '''

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

263

264# def __len__(self):

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

266# '''

267# return len(self.xyz)

268

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

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

271

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

273

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

275

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

277 '''

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

279

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

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

282

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

284

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

286

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

288 '''

289 return self.cross(other)

290

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

292 '''Not implemented.'''

293 return _NotImplemented(self, other)

294

295 def __mul__(self, scalar):

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

297

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

299

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

301 '''

302 return self.times(scalar)

303

304 def __ne__(self, other):

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

306

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

308

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

310

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

312 '''

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

314

315 def __neg__(self):

316 '''Return the opposite of this vector.

317

318 @return: This instance negated (L{Vector3d})

319 '''

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

321

322 def __pos__(self): # PYCHOK no cover

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

324

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

326 '''

327 return self if _pos_self else self.copy()

328

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

330 '''Not implemented.'''

331 return _NotImplemented(self, other, *mod)

332

333 __radd__ = __add__ # PYCHOK no cover

334

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

336 '''Not implemented.'''

337 return _NotImplemented(self, other)

338

339# def __repr__(self):

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

341# '''

342# return self.toRepr()

343

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

345 '''Not implemented.'''

346 return _NotImplemented(self, other)

347

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

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

350

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

352

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

354

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

356 '''

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

358

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

360 '''Not implemented.'''

361 return _NotImplemented(self, other)

362

363 __rmul__ = __mul__

364

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

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

367

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

369

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

371 '''

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

373 return self.classof(*(round(_, *ndigits) for _ in self.xyz))

374

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

376 '''Not implemented.'''

377 return _NotImplemented(self, other, *mod)

378

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

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

381

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

383

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

385

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

387 '''

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

389

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

391 '''Not implemented.'''

392 return _NotImplemented(self, scalar)

393

394# def __str__(self):

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

396# '''

397# return self.toStr()

398

399 def __sub__(self, other):

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

401

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

403

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

405

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

407 '''

408 return self.minus(other)

409

410 def __truediv__(self, scalar):

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

412

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

414

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

416

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

418 '''

419 return self.dividedBy(scalar)

420

421 def __trunc__(self): # PYCHOK no cover

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

423

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

425 '''

426 return self._mapped(trunc)

427

428 if _sys_version_info2 < (3, 0): # PYCHOK no cover

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

430 __div__ = __truediv__

431 __idiv__ = __itruediv__

432 __long__ = __int__

433 __nonzero__ = __bool__

434 __rdiv__ = __rtruediv__

435

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

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

438

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

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

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

442 positive if this->other is clockwise looking

443 along vSign or negative in opposite direction,

444 otherwise angle is unsigned.

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

446

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

448

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

450 '''

451 x = self.cross(other)

452 s = x.length

453 # use vSign as reference to set sign of s

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

455 s = -s

456

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

458 if wrap and fabs(a) > PI:

459 a -= _copysign(PI2, a)

460 return a

461

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

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

464 of this and an other vector.

465

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

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

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

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

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

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

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

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

474

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

476

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

478 '''

479 _xcallable(fun2=fun2)

480 if fun2_kwds:

481 def _f2(a, b):

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

483 else:

484 _f2 = fun2

485

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

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

488 return self.classof(*xyz)

489

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

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

492

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

494 @kwarg raiser: Optional, L{CrossError} label if raised (C{str},

495 non-L{NN}).

496 @kwarg eps0: Near-zero tolerance (C{scalar}), same units as

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

498

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

500

501 @raise CrossError: Zero or near-zero cross product and both

502 B{C{raiser}} and L{pygeodesy.crosserrors} set.

503

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

505 '''

506 X, Y, Z = self.others(other).xyz

507 x, y, z = self.xyz

508 xyz = ((y * Z - Y * z),

509 (z * X - Z * x),

510 (x * Y - X * y))

511

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

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

514 r = other._fromll or other

515 s = self._fromll or self

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

517 t = _coincident_ if t else _colinear_

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

519

520 return self.classof(*xyz)

521

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

523 def crosserrors(self):

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

525 '''

526 return self._crosserrors

527

528 @crosserrors.setter # PYCHOK setter!

529 def crosserrors(self, raiser):

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

531 '''

532 self._crosserrors = bool(raiser)

533

534 def dividedBy(self, divisor):

535 '''Divide this vector by a scalar.

536

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

538

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

540

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

542

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

544 '''

545 d = Scalar(divisor=divisor)

546 try:

547 return self._times(_1_0 / d)

548 except (ValueError, ZeroDivisionError) as x:

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

550

551 def dot(self, other):

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

553

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

555

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

557

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

559 '''

560 return self.length2 if other is self else \

561 fdot(self.xyz, *self.others(other).xyz)

562

563 @deprecated_method

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

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

566 '''

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

568

569 @Property_RO

570 def euclid(self):

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

572

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

574 L{pygeodesy.euclid_}.

575 '''

576 return Float(euclid=euclid_(self.x, self.y, self.z))

577

578 def equirectangular(self, other):

579 '''I{Approximate} the different between this and an other vector.

580

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

582

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

584

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

586

587 @see: Property C{length2}.

588 '''

589 d = self.minus(other)

590 return Float(equirectangular=hypot2_(d.x, d.y, d.z))

591

592 @Property

593 def _fromll(self):

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

595 '''

596 return self._ll

597

598 @_fromll.setter # PYCHOK setter!

599 def _fromll(self, ll):

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

601 '''

602 self._ll = ll or None

603

604 @property_RO

605 def homogeneous(self):

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

607 '''

608 x, y, z = self.xyz

609 if z:

610 x = x / z # /= chokes PyChecker

611 y = y / z

612# z = _1_0

613 else:

614 if isneg0(z):

615 x = -x

616 y = -y

617 x = _copysignINF(x)

618 y = _copysignINF(y)

619# z = NAN

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

621

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

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

624 and an other vector.

625

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

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

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

629

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

631

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

633 '''

634 f = Scalar(fraction=fraction)

635 if isnear0(f): # PYCHOK no cover

636 r = self

637 else:

638 r = self.others(other)

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

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

641 return r

642

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

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

645

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

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

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

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

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

651

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

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

654

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

656

657 @see: Method C{isequalTo}.

658 '''

659 self.others(other)

660 n = 0

661 for a, b in zip(self.xyz, other.xyz):

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

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

664 n += 1 # conjugate

665 elif fabs(a - b) > eps:

666 return False # unequal

667 return bool(n >= minum)

668

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

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

671

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

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

674 version of both vectors.

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

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

677

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

679

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

681

682 @see: Method C{isconjugateTo}.

683 '''

684 if units:

685 self.others(other)

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

687 else:

688 d = self.minus(other)

689 return max(map(fabs, d.xyz)) < eps

690

691 @Property_RO

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

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

694

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

696 '''

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

698

699 @Property_RO

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

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

702

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

704 '''

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

706

707 def _mapped(self, func):

708 '''(INTERNAL) Map these components.

709 '''

710 return self.classof(*map2(func, self.xyz))

711

712 def minus(self, other):

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

714

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

716

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

718

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

720 '''

721 xyz = self.others(other).xyz

722 return self._minus(*xyz)

723

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

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

726 '''

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

728

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

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

731

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

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

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

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

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

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

738

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

740

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

742 '''

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

744

745 def negate(self):

746 '''Return this vector in opposite direction.

747

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

749 '''

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

751

752 __neg__ = negate # PYCHOK no cover

753

754 @Property_RO

755 def _N_vector(self):

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

757 '''

758 return _MODS.nvectorBase._N_vector_(*self.xyz, name=self.name)

759

760 def _other_cmp(self, other):

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

762 '''

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

764

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

766 '''Refined class comparison.

767

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

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

770 keyword arguments.

771

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

773

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

775 '''

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

777 if not isinstance(other, Vector3dBase):

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

779 return other

780

781 def plus(self, other):

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

783

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

785

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

787

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

789 '''

790 xyz = self.others(other).xyz

791 return self._plus(*xyz)

792

793 sum = plus # alternate name

794

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

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

797 '''

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

799

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

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

802

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

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

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

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

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

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

809

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

811

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

813 '''

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

815

816 def rotate(self, axis, theta):

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

818

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

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

821

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

823

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

825 Rotation_matrix#Rotation_matrix_from_axis_and_angle>} and

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

827 Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix>}.

828 '''

829 s, c = _MODS.utily.sincos2(theta) # rotation angle

830 d = _1_0 - c

831 if d or s:

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

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

834

835 ax, ay, az = r.xyz # quaternion-derived rotation matrix

836 bx, by, bz = r.times(d).xyz

837 sx, sy, sz = r.times(s).xyz

838

839 x = fdot(p, ax * bx + c, ax * by - sz, ax * bz + sy)

840 y = fdot(p, ay * bx + sz, ay * by + c, ay * bz - sx)

841 z = fdot(p, az * bx - sy, az * by + sx, az * bz + c)

842 else: # unrotated

843 x, y, z = self.xyz

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

845

846 @deprecated_method

847 def rotateAround(self, axis, theta): # PYCHOK no cover

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

849 return self.rotate(axis, theta)

850

851 def times(self, factor):

852 '''Multiply this vector by a scalar.

853

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

855

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

857

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

859 '''

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

861

862 def _times(self, s):

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

864 '''

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

866

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

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

869

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

871 X, Y, and Z components as scale factors

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

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

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

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

876

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

878

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

880 '''

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

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

883

884# @deprecated_method

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

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

887#

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

889# '''

890# return _MODS.formy.n_xyz2philam(self.x, self.y, self.z)

891

892# @deprecated_method

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

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

895#

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

897# '''

898# return _MODS.formy.n_xyz2latlon(self.x, self.y, self.z)

899

900 @deprecated_method

901 def to3xyz(self): # PYCHOK no cover

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

903 '''

904 return self.xyz

905

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

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

908

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

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

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

912

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

914 '''

915 t = sep.join(strs(self.xyz, prec=prec))

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

917

918 def unit(self, ll=None):

919 '''Normalize this vector to unit length.

920

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

922

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

924 '''

925 u = self._united

926 if ll:

927 u._fromll = ll

928 return u

929

930 @Property_RO

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

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

933 '''

934 n = self.length

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

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

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

938 else:

939 u = self.copy()

940 u._update(False, _united=u)

941 if self._fromll:

942 u._fromll = self._fromll

943 return u

944

945 @Property

946 def x(self):

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

948 '''

949 return self._x

950

951 @x.setter # PYCHOK setter!

952 def x(self, x):

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

954 '''

955 x = Float(x=x)

956 if self._x != x:

957 _update_all(self, needed=3)

958 self._x = x

959

960 @Property

961 def xyz(self):

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

963 '''

964 return _MODS.namedTuples.Vector3Tuple(self.x, self.y, self.z, name=self.name)

965

966 @xyz.setter # PYCHOK setter!

967 def xyz(self, xyz):

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

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

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

971 '''

972 self._xyz(xyz)

973

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

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

976 '''

977 _update_all(self, needed=3)

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

979 return self

980

981 @property_RO

982 def x2y2z2(self):

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

984 '''

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

986

987 @Property

988 def y(self):

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

990 '''

991 return self._y

992

993 @y.setter # PYCHOK setter!

994 def y(self, y):

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

996 '''

997 y = Float(y=y)

998 if self._y != y:

999 _update_all(self, needed=3)

1000 self._y = y

1001

1002 @Property

1003 def z(self):

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

1005 '''

1006 return self._z

1007

1008 @z.setter # PYCHOK setter!

1009 def z(self, z):

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

1011 '''

1012 z = Float(z=z)

1013 if self._z != z:

1014 _update_all(self, needed=3)

1015 self._z = z

1016

1017

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

1019 '''(INTERNAL) Helper for C{Vector3dBase.__init__}, C{-.apply}, C{-.times_} and C{-._xyz}.

1020 '''

1021 try:

1022 x_y_z = map1(_float0, x_xyz, *y_z) if y_z else ( # islistuple for VectorXTuple

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

1024 x_xyz.xyz)

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

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

1027 return x_y_z

1028

1029

1030__all__ += _ALL_DOCS(Vector3dBase)

1031

1032# **) MIT License

1033#

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

1035#

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

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

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

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

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

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

1042#

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

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

1045#

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

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

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

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

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

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

1052# OTHER DEALINGS IN THE SOFTWARE.