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, _zip

12 from pygeodesy.constants import EPS, EPS0, INT0, PI, PI2, _float0, \

13 isnear0, isnear1, _pos_self, _1_0

14 from pygeodesy.errors import CrossError, _InvalidError, _IsnotError, \

15 VectorError

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

17 from pygeodesy.interns import NN, _coincident_, _colinear_, \

18 _COMMASPACE_, _y_, _z_

19 from pygeodesy.lazily import _ALL_LAZY, _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_, _update_all

25 from pygeodesy.streprs import Fmt, strs

26 from pygeodesy.units import Float, Scalar

27 from pygeodesy.utily import sincos2, atan2, fabs

28

29# from math import atan2, fabs # from .utily

30

31__all__ = _ALL_LAZY.vector3dBase

32__version__ = '23.09.29'

33

34

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

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

37

38 In a geodesy context, these may be used to represent:

39 - n-vector representing a normal to point on earth's surface

40 - earth-centered, earth-fixed cartesian (= spherical n-vector)

41 - great circle normal to vector

42 - motion vector on earth's surface

43 - etc.

44 '''

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

46

47 _ll = None # original latlon, '_fromll'

48 _x = INT0 # X component

49 _y = INT0 # Y component

50 _z = INT0 # Z component

51

52 def __init__(self, x_xyz, y=INT0, z=INT0, ll=None, name=NN):

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

54

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

56 distance from earth centre or height relative to the surface

57 of the earth' sphere or ellipsoid.

58

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

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

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

62 C{list} of 3+ C{scalar} values).

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

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

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

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

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

68 @kwarg name: Optional name (C{str}).

69

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

71 '''

72 if isscalar(x_xyz):

73 self._xyz(x_xyz, y, z)

74 else:

75 self.xyz = x_xyz

76 if ll:

77 self._ll = ll

78 if name:

79 self.name = name

80

81 def __abs__(self):

82 '''Return the norm of this vector.

83

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

85 '''

86 return self.length

87

88 def __add__(self, other):

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

90

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

92

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

94 '''

95 return self.plus(other)

96

97 def __bool__(self): # PYCHOK PyChecker

98 '''Is this vector non-zero?

99 '''

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

101

102 def __ceil__(self): # PYCHOK no cover

103 '''Not implemented.'''

104 return _NotImplemented(self)

105

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

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

108

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

110

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

112 '''

113 n = self.others(other).length

114 return -1 if self.length < n else (

115 +1 if self.length > n else 0)

116

117 cmp = __cmp__

118

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

120 '''Not implemented.'''

121 return _NotImplemented(self, other)

122

123 def __eq__(self, other):

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

125

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

127

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

129

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

131 '''

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

133

134 def __float__(self): # PYCHOK no cover

135 '''Not implemented.'''

136 return _NotImplemented(self)

137

138 def __floor__(self): # PYCHOK no cover

139 '''Not implemented.'''

140 return _NotImplemented(self)

141

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

143 '''Not implemented.'''

144 return _NotImplemented(self, other)

145

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

147 '''Not implemented.'''

148 return _NotImplemented(self, *other)

149

150 def __ge__(self, other):

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

152

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

154

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

156

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

158 '''

159 return self.length >= self.others(other).length

160

161# def __getitem__(self, key):

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

163# '''

164# return self.xyz[key]

165

166 def __gt__(self, other):

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

168

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

170

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

172

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

174 '''

175 return self.length > self.others(other).length

176

177 def __hash__(self): # PYCHOK no cover

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

179 '''

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

181

182 def __iadd__(self, other):

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

184

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

186

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

188 '''

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

190

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

192 '''Not implemented.'''

193 return _NotImplemented(self, other)

194

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

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

197

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

199

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

201

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

203 '''

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

205

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

207 '''Not implemented.'''

208 return _NotImplemented(self, other)

209

210 def __imul__(self, scalar):

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

212

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

214

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

216 '''

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

218

219 def __int__(self): # PYCHOK no cover

220 '''Not implemented.'''

221 return _NotImplemented(self)

222

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

224 '''Not implemented.'''

225 return _NotImplemented(self, other, *mod)

226

227 def __isub__(self, other):

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

229

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

231

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

233 '''

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

235

236# def __iter__(self):

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

238# '''

239# return iter(self.xyz)

240

241 def __itruediv__(self, scalar):

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

243

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

245

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

247 '''

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

249

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

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

252

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

254

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

256

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

258 '''

259 return self.length <= self.others(other).length

260

261# def __len__(self):

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

263# '''

264# return len(self.xyz)

265

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

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

268

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

270

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

272

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

274 '''

275 return self.length < self.others(other).length

276

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

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

279

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

281

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

283

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

285 '''

286 return self.cross(other)

287

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

289 '''Not implemented.'''

290 return _NotImplemented(self, other)

291

292 def __mul__(self, scalar):

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

294

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

296

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

298 '''

299 return self.times(scalar)

300

301 def __ne__(self, other):

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

303

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

305

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

307

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

309 '''

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

311

312 def __neg__(self):

313 '''Return the opposite of this vector.

314

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

316 '''

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

318

319 def __pos__(self): # PYCHOK no cover

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

321

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

323 '''

324 return self if _pos_self else self.copy()

325

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

327 '''Not implemented.'''

328 return _NotImplemented(self, other, *mod)

329

330 __radd__ = __add__ # PYCHOK no cover

331

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

333 '''Not implemented.'''

334 return _NotImplemented(self, other)

335

336# def __repr__(self):

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

338# '''

339# return self.toRepr()

340

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

342 '''Not implemented.'''

343 return _NotImplemented(self, other)

344

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

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

347

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

349

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

351

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

353 '''

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

355

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

357 '''Not implemented.'''

358 return _NotImplemented(self, other)

359

360 __rmul__ = __mul__

361

362 def __round__(self, ndigits=None): # PYCHOK no cover

363 '''Not implemented.'''

364 return _NotImplemented(self, ndigits=ndigits)

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 __trunc__ = __int__

413

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

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

416 __div__ = __truediv__

417 __idiv__ = __itruediv__

418 __long__ = __int__

419 __nonzero__ = __bool__

420 __rdiv__ = __rtruediv__

421

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

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

424

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

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

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

428 positive if this->other is clockwise looking

429 along vSign or negative in opposite direction,

430 otherwise angle is unsigned.

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

432

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

434

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

436 '''

437 x = self.cross(other)

438 s = x.length

439 # use vSign as reference to set sign of s

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

441 s = -s

442

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

444 if wrap and fabs(a) > PI:

445 a -= _copysign(PI2, a)

446 return a

447

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

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

450 of this and an other vector.

451

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

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

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

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

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

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

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

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

460

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

462

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

464 '''

465 if not callable(fun2):

466 raise _IsnotError(callable.__name__, fun2=fun2)

467

468 if fun2_kwds:

469 def _f2(a, b):

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

471 else:

472 _f2 = fun2

473

474 xyz = _other_x_y_z3(other_x, y_z)

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

476 return self.classof(*xyz)

477

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

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

480

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

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

483 non-L{NN}).

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

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

486

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

488

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

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

491

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

493 '''

494 other = self.others(other)

495

496 x = self.y * other.z - self.z * other.y

497 y = self.z * other.x - self.x * other.z

498 z = self.x * other.y - self.y * other.x

499

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

501 and max(map1(fabs, x, y, z)) < eps0:

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

503 s = self._fromll or self

504 r = other._fromll or other

505 t = _coincident_ if t else _colinear_

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

507

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

509

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

511 def crosserrors(self):

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

513 '''

514 return self._crosserrors

515

516 @crosserrors.setter # PYCHOK setter!

517 def crosserrors(self, raiser):

518 '''Raise L{CrossError} exceptions (C{bool}).

519 '''

520 self._crosserrors = bool(raiser)

521

522 def dividedBy(self, divisor):

523 '''Divide this vector by a scalar.

524

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

526

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

528

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

530

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

532 '''

533 d = Scalar(divisor=divisor)

534 try:

535 return self._times(_1_0 / d)

536 except (ValueError, ZeroDivisionError) as x:

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

538

539 def dot(self, other):

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

541

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

543

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

545

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

547 '''

548 return self.length2 if other is self else \

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

550

551 @deprecated_method

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

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

554 '''

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

556

557 @Property_RO

558 def euclid(self):

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

560

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

562 L{pygeodesy.euclid_}.

563 '''

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

565

566 def equirectangular(self, other):

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

568

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

570

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

572

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

574

575 @see: Property C{length2}.

576 '''

577 d = self.minus(other)

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

579

580 @Property

581 def _fromll(self):

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

583 '''

584 return self._ll

585

586 @_fromll.setter # PYCHOK setter!

587 def _fromll(self, ll):

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

589 '''

590 self._ll = ll or None

591

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

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

594 and an other vector.

595

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

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

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

599

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

601

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

603 '''

604 f = Scalar(fraction=fraction)

605 if isnear0(f): # PYCHOK no cover

606 r = self

607 else:

608 r = self.others(other)

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

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

611 return r

612

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

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

615

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

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

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

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

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

621

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

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

624

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

626

627 @see: Method C{isequalTo}.

628 '''

629 self.others(other)

630 n = 0

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

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

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

634 n += 1 # conjugate

635 elif fabs(a - b) > eps:

636 return False # unequal

637 return bool(n >= minum)

638

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

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

641

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

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

644 version of both vectors.

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

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

647

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

649

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

651

652 @see: Method C{isconjugateTo}.

653 '''

654 if units:

655 self.others(other)

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

657 else:

658 d = self.minus(other)

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

660

661 @Property_RO

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

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

664

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

666 '''

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

668

669 @Property_RO

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

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

672

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

674 '''

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

676

677 def minus(self, other):

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

679

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

681

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

683

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

685 '''

686 self.others(other)

687 return self._minus(other.x, other.y, other.z)

688

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

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

691 '''

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

693

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

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

696

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

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

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

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

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

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

703

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

705

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

707 '''

708 return self._minus(*_other_x_y_z3(other_x, y_z))

709

710 def negate(self):

711 '''Return this vector in opposite direction.

712

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

714 '''

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

716

717 __neg__ = negate # PYCHOK no cover

718

719 @Property_RO

720 def _N_vector(self):

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

722 '''

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

724

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

726 '''Refined class comparison.

727

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

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

730 keyword arguments.

731

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

733

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

735 '''

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

737 if not isinstance(other, Vector3dBase):

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

739 return other

740

741 def plus(self, other):

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

743

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

745

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

747

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

749 '''

750 self.others(other)

751 return self._plus(other.x, other.y, other.z)

752

753 sum = plus # alternate name

754

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

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

757 '''

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

759

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

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

762

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

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

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

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

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

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

769

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

771

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

773 '''

774 return self._plus(*_other_x_y_z3(other_x, y_z))

775

776 def rotate(self, axis, theta):

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

778

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

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

781

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

783

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

785 Rotation_matrix#Rotation_matrix_from_axis_and_angle>} and

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

787 Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix>}.

788 '''

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

790 d = _1_0 - c

791 if d or s:

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

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

794

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

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

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

798

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

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

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

802 else: # unrotated

803 x, y, z = self.xyz

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

805

806 @deprecated_method

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

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

809 return self.rotate(axis, theta)

810

811 def times(self, factor):

812 '''Multiply this vector by a scalar.

813

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

815

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

817

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

819 '''

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

821

822 def _times(self, s):

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

824 '''

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

826

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

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

829

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

831 X, Y, and Z components as scale factors

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

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

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

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

836

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

838

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

840 '''

841 x, y, z = _other_x_y_z3(other_x, y_z)

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

843

844# @deprecated_method

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

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

847#

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

849# '''

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

851

852# @deprecated_method

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

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

855#

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

857# '''

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

859

860 @deprecated_method

861 def to3xyz(self): # PYCHOK no cover

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

863 '''

864 return self.xyz

865

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

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

868

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

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

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

872

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

874 '''

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

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

877

878 def unit(self, ll=None):

879 '''Normalize this vector to unit length.

880

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

882

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

884 '''

885 u = self._united

886 if ll:

887 u._fromll = ll

888 return u

889

890 @Property_RO

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

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

893 '''

894 n = self.length

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

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

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

898 else:

899 u = self.copy()

900 u._update(False, _united=u)

901 if self._fromll:

902 u._fromll = self._fromll

903 return u

904

905 @Property

906 def x(self):

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

908 '''

909 return self._x

910

911 @x.setter # PYCHOK setter!

912 def x(self, x):

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

914 '''

915 x = Float(x=x)

916 if self._x != x:

917 _update_all(self)

918 self._x = x

919

920 @Property

921 def xyz(self):

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

923 '''

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

925

926 @xyz.setter # PYCHOK setter!

927 def xyz(self, xyz):

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

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

930 or a C{tuple} or C{list} of 3+ C{scalar} values).

931 '''

932 if islistuple(xyz, 3):

933 self._xyz(*xyz[:3])

934 else:

935 self._xyz(xyz)

936

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

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

939 '''

940 if len(self.__dict__) > 3: # any other than initial ._x, ._y and ._z attrs

941 _update_all(self)

942 try:

943 self._x, \

944 self._y, \

945 self._z = map1(_float0, x_xyz, *y_z) if y_z else x_xyz.xyz

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

947 raise VectorError(cause=x, **_xyzkwds(y_z, x_xyz=x_xyz))

948 return self

949

950 @Property_RO

951 def x2y2z2(self):

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

953 '''

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

955

956 @Property

957 def y(self):

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

959 '''

960 return self._y

961

962 @y.setter # PYCHOK setter!

963 def y(self, y):

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

965 '''

966 y = Float(y=y)

967 if self._y != y:

968 _update_all(self)

969 self._y = y

970

971 @Property

972 def z(self):

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

974 '''

975 return self._z

976

977 @z.setter # PYCHOK setter!

978 def z(self, z):

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

980 '''

981 z = Float(z=z)

982 if self._z != z:

983 _update_all(self)

984 self._z = z

985

986

987 def _other_x_y_z3(other_x, y_z):

988 '''(INTERNAL) Helper for C{Vector3dBase.apply} and C{Vector3dBase.times_}.

989 '''

990 try:

991 return map1(_float0, other_x, *y_z) if y_z else \

992 (other_x.x, other_x.y, other_x.z) # not .xyz!

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

994 raise _InvalidError(cause=x, **_xyzkwds(y_z, other_x=other_x))

995

996

997 def _xyzkwds(y_z, **xyz): # PYCHOK no cover

998 '''(INTERANL) Helper for C{_other_x_y_z3} and C{Vector3dBase._xyz}.

999 '''

1000 if y_z:

1001 d = dict(_zip((_y_, _z_), y_z)) # if y_z else {}, strict=True

1002 for x in xyz.values():

1003 d.update(x=x)

1004 return d

1005 return xyz

1006

1007# **) MIT License

1008#

1009# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.

1010#

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

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

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

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

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

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

1017#

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

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

1020#

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

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

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

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

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

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

1027# OTHER DEALINGS IN THE SOFTWARE.