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, 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, 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, atan2, fabs

28

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

30

31__all__ = _ALL_LAZY.vector3dBase

32__version__ = '24.08.18'

33

34

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

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

37 '''

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

39

40 _ll = None # original latlon, '_fromll'

41# _x = INT0 # X component

42# _y = INT0 # Y component

43# _z = INT0 # Z component

44

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

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

47

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

49 distance from earth centre or height relative to the surface

50 of the earth' sphere or ellipsoid.

51

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

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

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

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

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

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

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

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

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

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

62

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

64 '''

65 self._x, \

66 self._y, \

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

68 _xyz3(type(self), x_xyz)

69 if ll:

70 self._ll = ll

71 if name:

72 self.name = name

73

74 def __abs__(self):

75 '''Return the norm of this vector.

76

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

78 '''

79 return self.length

80

81 def __add__(self, other):

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

83

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

85

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

87 '''

88 return self.plus(other)

89

90 def __bool__(self): # PYCHOK PyChecker

91 '''Is this vector non-zero?

92

93 @see: Method C{bools}.

94 '''

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

96

97 def __ceil__(self): # PYCHOK no cover

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

99

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

101 '''

102 return self._mapped(_ceil)

103

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

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

106

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

108

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

110 '''

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

112

113 cmp = __cmp__

114

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

116 '''Not implemented.'''

117 return _NotImplemented(self, other)

118

119 def __eq__(self, other):

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

121

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

123

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

125

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

127 '''

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

129

130 def __float__(self): # PYCHOK no cover

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

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

133

134 def __floor__(self): # PYCHOK no cover

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

136

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

138 '''

139 return self._mapped(_floor)

140

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

142 '''Not implemented.'''

143 return _NotImplemented(self, other)

144

145 def __format__(self, *other): # PYCHOK no cover

146 '''Not implemented.'''

147 return _NotImplemented(self, *other)

148

149 def __ge__(self, other):

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

151

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

153

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

155

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

157 '''

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

159

160# def __getitem__(self, key):

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

162# '''

163# return self.xyz[key]

164

165 def __gt__(self, other):

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

167

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

169

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

171

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

173 '''

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

175

176 def __hash__(self): # PYCHOK no cover

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

178 '''

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

180

181 def __iadd__(self, other):

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

183

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

185

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

187 '''

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

189

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

191 '''Not implemented.'''

192 return _NotImplemented(self, other)

193

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

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

196

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

198

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

200

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

202 '''

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

204

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

206 '''Not implemented.'''

207 return _NotImplemented(self, other)

208

209 def __imul__(self, scalar):

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

211

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

213

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

215 '''

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

217

218 def __int__(self): # PYCHOK no cover

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

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

221

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

223 '''Not implemented.'''

224 return _NotImplemented(self, other, *mod)

225

226 def __isub__(self, other):

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

228

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

230

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

232 '''

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

234

235# def __iter__(self):

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

237# '''

238# return iter(self.xyz3)

239

240 def __itruediv__(self, scalar):

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

242

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

244

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

246 '''

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

248

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

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

251

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

253

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

255

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

257 '''

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

259

260# def __len__(self):

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

262# '''

263# return len(self.xyz)

264

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

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

267

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

269

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

271

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

273 '''

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

275

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

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

278

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

280

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

282

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

284 '''

285 return self.cross(other)

286

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

288 '''Not implemented.'''

289 return _NotImplemented(self, other)

290

291 def __mul__(self, scalar):

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

293

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

295

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

297 '''

298 return self.times(scalar)

299

300 def __ne__(self, other):

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

302

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

304

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

306

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

308 '''

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

310

311 def __neg__(self):

312 '''Return the opposite of this vector.

313

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

315 '''

316 return self.negate()

317

318 def __pos__(self): # PYCHOK no cover

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

320

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

322 '''

323 return self if _pos_self else self.copy()

324

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

326 '''Not implemented.'''

327 return _NotImplemented(self, other, *mod)

328

329 __radd__ = __add__ # PYCHOK no cover

330

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

332 '''Not implemented.'''

333 return _NotImplemented(self, other)

334

335# def __repr__(self):

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

337# '''

338# return self.toRepr()

339

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

341 '''Not implemented.'''

342 return _NotImplemented(self, other)

343

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

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

346

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

348

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

350

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

352 '''

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

354

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

356 '''Not implemented.'''

357 return _NotImplemented(self, other)

358

359 __rmul__ = __mul__

360

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

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

363

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

365

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

367 '''

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

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

370

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

372 '''Not implemented.'''

373 return _NotImplemented(self, other, *mod)

374

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

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

377

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

379

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

381

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

383 '''

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

385

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

387 '''Not implemented.'''

388 return _NotImplemented(self, scalar)

389

390# def __str__(self):

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

392# '''

393# return self.toStr()

394

395 def __sub__(self, other):

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

397

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

399

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

401

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

403 '''

404 return self.minus(other)

405

406 def __truediv__(self, scalar):

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

408

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

410

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

412

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

414 '''

415 return self.dividedBy(scalar)

416

417 def __trunc__(self): # PYCHOK no cover

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

419

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

421 '''

422 return self._mapped(_trunc)

423

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

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

426 __div__ = __truediv__

427 __idiv__ = __itruediv__

428 __long__ = __int__

429 __nonzero__ = __bool__

430 __rdiv__ = __rtruediv__

431

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

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

434

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

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

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

438 positive if this->other is clockwise looking

439 along vSign or negative in opposite direction,

440 otherwise angle is unsigned.

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

442

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

444

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

446 '''

447 x = self.cross(other)

448 s = x.length

449 # use vSign as reference to set sign of s

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

451 s = -s

452

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

454 if wrap and fabs(a) > PI:

455 a -= _copysign(PI2, a)

456 return a

457

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

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

460 of this and an other vector.

461

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

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

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

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

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

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

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

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

470

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

472

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

474 '''

475 _xcallable(fun2=fun2)

476 if fun2_kwds:

477 def _f2(a, b):

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

479 else:

480 _f2 = fun2

481

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

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

484 return self.classof(*xyz)

485

486 def bools(self):

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

488 '''

489 return self._mapped(bool)

490

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

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

493

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

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

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

497 C{z}.

498

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

500

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

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

503

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

505 '''

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

507 x, y, z = self.xyz3

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

509 (z * X - x * Z),

510 (x * Y - y * X))

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) # name__=self.cross

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 fdot(

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

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.xyz3))

577

578 def equirectangular(self, other):

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

580

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

582

583 @return: The length 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.xyz3))

591

592 def fabs(self):

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

594 '''

595 return self._mapped(fabs)

596

597 def floats(self):

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

599 '''

600 return self._mapped(_float0)

601

602 @Property

603 def _fromll(self):

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

605 '''

606 return self._ll

607

608 @_fromll.setter # PYCHOK setter!

609 def _fromll(self, ll):

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

611 '''

612 self._ll = ll or None

613

614 @property_RO

615 def homogeneous(self):

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

617 '''

618 x, y, z = self.xyz3

619 if z:

620 x = x / z # /= chokes PyChecker

621 y = y / z

622# z = _1_0

623 else:

624 if isneg0(z):

625 x = -x

626 y = -y

627 x = _copysignINF(x)

628 y = _copysignINF(y)

629# z = NAN

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

631

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

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

634 and an other vector.

635

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

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

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

639

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

641

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

643 '''

644 f = Scalar(fraction=fraction)

645 if isnear0(f): # PYCHOK no cover

646 r = self

647 else:

648 r = self.others(other)

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

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

651 return r

652

653 def ints(self):

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

655 '''

656 return self._mapped(int)

657

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

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

660

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

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

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

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

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

666

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

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

669

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

671

672 @see: Method C{isequalTo}.

673 '''

674 self.others(other)

675 n = 0

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

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

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

679 n += 1 # conjugate

680 elif fabs(a - b) > eps:

681 return False # unequal

682 return bool(n >= minum)

683

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

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

686

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

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

689 version of both vectors.

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

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

692

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

694

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

696

697 @see: Method C{isconjugateTo}.

698 '''

699 if units:

700 self.others(other)

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

702 else:

703 d = self.minus(other)

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

705

706 @Property_RO

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

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

709

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

711 '''

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

713

714 @Property_RO

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

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

717

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

719 '''

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

721

722 def _mapped(self, func):

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

724 '''

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

726

727 def minus(self, other):

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

729

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

731

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

733

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

735 '''

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

737

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

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

740 '''

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

742

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

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

745

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

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

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

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

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

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

752

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

754

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

756 '''

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

758

759 def negate(self):

760 '''Return the opposite of this vector.

761

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

763 '''

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

765

766 @Property_RO

767 def _N_vector(self):

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

769 '''

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

771

772 def _other_cmp(self, other):

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

774 '''

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

776

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

778 '''Refined class comparison.

779

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

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

782 keyword arguments.

783

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

785

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

787 '''

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

789 if not isinstance(other, Vector3dBase):

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

791 return other

792

793 def plus(self, other):

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

795

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

797

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

799

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

801 '''

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

803

804 sum = plus # alternate name

805

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

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

808 '''

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

810

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

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

813

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

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

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

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

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

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

820

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

822

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

824 '''

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

826

827 def rotate(self, axis, theta):

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

829

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

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

832

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

834

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

836 Rotation_matrix#Rotation_matrix_from_axis_and_angle>} and

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

838 Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix>}.

839 '''

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

841 d = _1_0 - c

842 if d or s:

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

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

845

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

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

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

849

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

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

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

853 else: # unrotated

854 x, y, z = self.xyz3

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

856

857 @deprecated_method

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

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

860 return self.rotate(axis, theta)

861

862 def times(self, factor):

863 '''Multiply this vector by a scalar.

864

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

866

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

868

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

870 '''

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

872

873 def _times(self, s):

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

875 '''

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

877

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

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

880

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

882 X, Y, and Z components as scale factors

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

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

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

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

887

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

889

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

891 '''

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

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

894

895# @deprecated_method

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

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

898#

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

900# '''

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

902

903# @deprecated_method

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

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

906#

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

908# '''

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

910

911 @deprecated_method

912 def to3xyz(self): # PYCHOK no cover

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

914 '''

915 return self.xyz

916

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

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

919

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

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

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

923

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

925 '''

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

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

928

929 def unit(self, ll=None):

930 '''Normalize this vector to unit length.

931

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

933

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

935 '''

936 u = self._united

937 if ll:

938 u._fromll = ll

939 return u

940

941 @Property_RO

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

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

944 '''

945 n = self.length

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

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

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

949 else:

950 u = self.copy()

951 u._update(False, _united=u)

952 if self._fromll:

953 u._fromll = self._fromll

954 return u

955

956 @Property

957 def x(self):

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

959 '''

960 return self._x

961

962 @x.setter # PYCHOK setter!

963 def x(self, x):

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

965 '''

966 x = Float(x=x)

967 if self._x != x:

968 _update_all(self, needed=3)

969 self._x = x

970

971 @Property

972 def xyz(self):

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

974 '''

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

976

977 @xyz.setter # PYCHOK setter!

978 def xyz(self, xyz):

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

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

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

982 '''

983 self._xyz(xyz)

984

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

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

987 '''

988 _update_all(self, needed=3)

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

990 return self

991

992 @property_RO

993 def xyz3(self):

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

995 '''

996 return self.x, self.y, self.z

997

998 @property_RO

999 def x2y2z2(self):

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

1001 '''

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

1003

1004 @Property

1005 def y(self):

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

1007 '''

1008 return self._y

1009

1010 @y.setter # PYCHOK setter!

1011 def y(self, y):

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

1013 '''

1014 y = Float(y=y)

1015 if self._y != y:

1016 _update_all(self, needed=3)

1017 self._y = y

1018

1019 @Property

1020 def z(self):

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

1022 '''

1023 return self._z

1024

1025 @z.setter # PYCHOK setter!

1026 def z(self, z):

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

1028 '''

1029 z = Float(z=z)

1030 if self._z != z:

1031 _update_all(self, needed=3)

1032 self._z = z

1033

1034

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

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

1037 '''

1038 try:

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

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

1041 x_xyz.xyz) # .xyz3

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

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

1044 return xyz3

1045

1046

1047__all__ += _ALL_DOCS(Vector3dBase)

1048

1049# **) MIT License

1050#

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

1052#

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

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

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

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

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

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

1059#

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

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

1062#

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

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

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

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

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

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

1069# OTHER DEALINGS IN THE SOFTWARE.