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 copysign0, islistuple, isscalar, map1, _zip

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

13 isnear0, isnear1, _1_0

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

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

16 from pygeodesy.fsums import fsum1f_, _pos_self

17 from pygeodesy.interns import NN, _coincident_, _colinear_, _COMMASPACE_, _y_, _z_

18 from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _sys, _sys_version_info2

19 from pygeodesy.named import _NamedBase, _NotImplemented, _xother3

20 from pygeodesy.namedTuples import Vector3Tuple

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

22 property_doc_, _update_all

23 from pygeodesy.streprs import Fmt, strs

24 from pygeodesy.units import Float, Scalar

25 from pygeodesy.utily import atan2, fabs, sincos2

26

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

28

29__all__ = _ALL_LAZY.vector3dBase

30__version__ = '23.05.15'

31

32

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

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

35

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

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

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

39 - great circle normal to vector

40 - motion vector on earth's surface

41 - etc.

42 '''

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

44

45 _ll = None # original latlon, '_fromll'

46 _x = INT0 # X component

47 _y = INT0 # Y component

48 _z = INT0 # Z component

49

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

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

52

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

54 distance from earth centre or height relative to the surface

55 of the earth' sphere or ellipsoid.

56

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

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

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

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

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

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

63 @kwarg z: Z 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 ll: Optional latlon reference (C{LatLon}).

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

67

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

69 '''

70 if isscalar(x_xyz):

71 self._xyz(x_xyz, y, z)

72 else:

73 self.xyz = x_xyz

74 if ll:

75 self._ll = ll

76 if name:

77 self.name = name

78

79 def __abs__(self):

80 '''Return the norm of this vector.

81

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

83 '''

84 return self.length

85

86 def __add__(self, other):

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

88

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

90

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

92 '''

93 return self.plus(other)

94

95 def __bool__(self): # PYCHOK PyChecker

96 '''Is this vector non-zero?

97 '''

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

99

100 def __ceil__(self): # PYCHOK no cover

101 '''Not implemented.'''

102 return _NotImplemented(self)

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 n = self.others(other).length

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

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

114

115 cmp = __cmp__

116

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

118 '''Not implemented.'''

119 return _NotImplemented(self, other)

120

121 def __eq__(self, other):

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

123

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

125

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

127

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

129 '''

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

131

132 def __float__(self): # PYCHOK no cover

133 '''Not implemented.'''

134 return _NotImplemented(self)

135

136 def __floor__(self): # PYCHOK no cover

137 '''Not implemented.'''

138 return _NotImplemented(self)

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.others(other).length

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.others(other).length

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", page 397-398, O'Reilly 2016.

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

219 return _NotImplemented(self)

220

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

222 '''Not implemented.'''

223 return _NotImplemented(self, other, *mod)

224

225 def __isub__(self, other):

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

227

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

229

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

231 '''

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

233

234# def __iter__(self):

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

236# '''

237# return iter(self.xyz)

238

239 def __itruediv__(self, scalar):

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

241

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

243

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

245 '''

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

247

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

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

250

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

252

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

254

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

256 '''

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

258

259# def __len__(self):

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

261# '''

262# return len(self.xyz)

263

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

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

266

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

268

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

270

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

272 '''

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

274

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

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

277

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

279

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

281

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

283 '''

284 return self.cross(other)

285

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

287 '''Not implemented.'''

288 return _NotImplemented(self, other)

289

290 def __mul__(self, scalar):

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

292

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

294

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

296 '''

297 return self.times(scalar)

298

299 def __ne__(self, other):

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

301

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

303

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

305

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

307 '''

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

309

310 def __neg__(self):

311 '''Return the opposite of this vector.

312

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

314 '''

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

316

317 def __pos__(self): # PYCHOK no cover

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

319

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

321 '''

322 return self if _pos_self else self.copy()

323

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

325 '''Not implemented.'''

326 return _NotImplemented(self, other, *mod)

327

328 __radd__ = __add__ # PYCHOK no cover

329

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

331 '''Not implemented.'''

332 return _NotImplemented(self, other)

333

334# def __repr__(self):

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

336# '''

337# return self.toRepr()

338

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

340 '''Not implemented.'''

341 return _NotImplemented(self, other)

342

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

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

345

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

347

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

349

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

351 '''

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

353

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

355 '''Not implemented.'''

356 return _NotImplemented(self, other)

357

358 __rmul__ = __mul__

359

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

361 '''Not implemented.'''

362 return _NotImplemented(self, ndigits=ndigits)

363

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

365 '''Not implemented.'''

366 return _NotImplemented(self, other, *mod)

367

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

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

370

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

372

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

374

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

376 '''

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

378

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

380 '''Not implemented.'''

381 return _NotImplemented(self, scalar)

382

383 def __sizeof__(self): # PYCHOK not special in Python 2-

384 '''Return the current size of this vector in C{bytes}.

385 '''

386 # self._x, self._y, self._z, self._ll, ...

387 v = self.__dict__.values # avoid recursion

388 return sum(map1(_sys.getsizeof, (o for o in v() if o is not self)))

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

418

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

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

421 __div__ = __truediv__

422 __idiv__ = __itruediv__

423 __long__ = __int__

424 __nonzero__ = __bool__

425 __rdiv__ = __rtruediv__

426

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

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

429

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

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

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

433 positive if this->other is clockwise looking

434 along vSign or negative in opposite direction,

435 otherwise angle is unsigned.

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

437

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

439

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

441 '''

442 x = self.cross(other)

443 s = x.length

444 # use vSign as reference to set sign of s

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

446 s = -s

447

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

449 if wrap and fabs(a) > PI:

450 a -= copysign0(PI2, a)

451 return a

452

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

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

455 of this and an other vector.

456

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

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

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

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

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

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

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

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

465

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

467

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

469 '''

470 if not callable(fun2):

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

472

473 if fun2_kwds:

474 def _f2(a, b):

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

476 else:

477 _f2 = fun2

478

479 xyz = _other_x_y_z3(other_x, y_z)

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

481 return self.classof(*xyz)

482

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

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

485

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

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

488 non-L{NN}).

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

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

491

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

493

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

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

496

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

498 '''

499 other = self.others(other)

500

501 x = fsum1f_(self.y * other.z, -self.z * other.y)

502 y = fsum1f_(self.z * other.x, -self.x * other.z)

503 z = fsum1f_(self.x * other.y, -self.y * other.x)

504

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

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

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

508 s = self._fromll or self

509 r = other._fromll or other

510 t = _coincident_ if t else _colinear_

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

512

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

514

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

516 def crosserrors(self):

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

518 '''

519 return self._crosserrors

520

521 @crosserrors.setter # PYCHOK setter!

522 def crosserrors(self, raiser):

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

524 '''

525 self._crosserrors = bool(raiser)

526

527 def dividedBy(self, divisor):

528 '''Divide this vector by a scalar.

529

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

531

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

533

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

535

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

537 '''

538 d = Scalar(divisor=divisor)

539 try:

540 return self._times(_1_0 / d)

541 except (ValueError, ZeroDivisionError) as x:

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

543

544 def dot(self, other):

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

546

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

548

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

550

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

552 '''

553 return self.length2 if other is self else \

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

555

556 @deprecated_method

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

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

559 '''

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

561

562 @Property_RO

563 def euclid(self):

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

565

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

567 L{pygeodesy.euclid_}.

568 '''

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

570

571 def equirectangular(self, other):

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

573

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

575

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

577

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

579

580 @see: Property C{length2}.

581 '''

582 d = self.minus(other)

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

584

585 @Property

586 def _fromll(self):

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

588 '''

589 return self._ll

590

591 @_fromll.setter # PYCHOK setter!

592 def _fromll(self, ll):

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

594 '''

595 self._ll = ll or None

596

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

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

599 and an other vector.

600

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

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

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

604

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

606

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

608 '''

609 f = Scalar(fraction=fraction)

610 if isnear0(f): # PYCHOK no cover

611 r = self

612 else:

613 r = self.others(other)

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

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

616 return r

617

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

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

620

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

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

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

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

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

626

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

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

629

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

631

632 @see: Method C{isequalTo}.

633 '''

634 self.others(other)

635 n = 0

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

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

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

639 n += 1 # conjugate

640 elif fabs(a - b) > eps:

641 return False # unequal

642 return bool(n >= minum)

643

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

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

646

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

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

649 version of both vectors.

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

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

652

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

654

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

656

657 @see: Method C{isconjugateTo}.

658 '''

659 if units:

660 self.others(other)

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

662 else:

663 d = self.minus(other)

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

665

666 @Property_RO

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

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

669

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

671 '''

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

673

674 @Property_RO

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

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

677

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

679 '''

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

681

682 def minus(self, other):

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

684

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

686

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

688

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

690 '''

691 self.others(other)

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

693

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

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

696 '''

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

698

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

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

701

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

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

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

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

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

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

708

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

710

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

712 '''

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

714

715 def negate(self):

716 '''Return this vector in opposite direction.

717

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

719 '''

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

721

722 __neg__ = negate # PYCHOK no cover

723

724 @Property_RO

725 def _N_vector(self):

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

727 '''

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

729

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

731 '''Refined class comparison.

732

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

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

735 keyword arguments.

736

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

738

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

740 '''

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

742 if not isinstance(other, Vector3dBase):

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

744 return other

745

746 def plus(self, other):

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

748

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

750

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

752

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

754 '''

755 self.others(other)

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

757

758 sum = plus # alternate name

759

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

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

762 '''

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

764

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

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

767

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

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

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

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

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

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

774

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

776

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

778 '''

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

780

781 def rotate(self, axis, theta):

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

783

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

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

786

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

788

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

790 Rotation_matrix#Rotation_matrix_from_axis_and_angle>} and

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

792 Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix>}.

793 '''

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

795 d = _1_0 - c

796 if d or s:

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

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

799

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

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

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

803

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

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

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

807 else: # unrotated

808 x, y, z = self.xyz

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

810

811 @deprecated_method

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

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

814 return self.rotate(axis, theta)

815

816 def times(self, factor):

817 '''Multiply this vector by a scalar.

818

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

820

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

822

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

824 '''

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

826

827 def _times(self, s):

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

829 '''

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

831

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

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

834

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

836 X, Y, and Z components as scale factors

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

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

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

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

841

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

843

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

845 '''

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

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

848

849# @deprecated_method

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

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

852#

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

854# '''

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

856

857# @deprecated_method

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

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

860#

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

862# '''

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

864

865 @deprecated_method

866 def to3xyz(self): # PYCHOK no cover

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

868 '''

869 return self.xyz

870

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

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

873

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

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

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

877

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

879 '''

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

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

882

883 def unit(self, ll=None):

884 '''Normalize this vector to unit length.

885

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

887

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

889 '''

890 u = self._united

891 if ll:

892 u._fromll = ll

893 return u

894

895 @Property_RO

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

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

898 '''

899 n = self.length

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

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

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

903 else:

904 u = self.copy()

905 u._update(False, _united=u)

906 if self._fromll:

907 u._fromll = self._fromll

908 return u

909

910 @Property

911 def x(self):

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

913 '''

914 return self._x

915

916 @x.setter # PYCHOK setter!

917 def x(self, x):

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

919 '''

920 x = Float(x=x)

921 if self._x != x:

922 _update_all(self)

923 self._x = x

924

925 @Property

926 def xyz(self):

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

928 '''

929 return Vector3Tuple(self.x, self.y, self.z, name=self.name)

930

931 @xyz.setter # PYCHOK setter!

932 def xyz(self, xyz):

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

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

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

936 '''

937 if islistuple(xyz, 3):

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

939 else:

940 self._xyz(xyz)

941

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

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

944 '''

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

946 _update_all(self)

947 try:

948 self._x, \

949 self._y, \

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

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

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

953 return self

954

955 @Property_RO

956 def x2y2z2(self):

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

958 '''

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

960

961 @Property

962 def y(self):

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

964 '''

965 return self._y

966

967 @y.setter # PYCHOK setter!

968 def y(self, y):

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

970 '''

971 y = Float(y=y)

972 if self._y != y:

973 _update_all(self)

974 self._y = y

975

976 @Property

977 def z(self):

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

979 '''

980 return self._z

981

982 @z.setter # PYCHOK setter!

983 def z(self, z):

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

985 '''

986 z = Float(z=z)

987 if self._z != z:

988 _update_all(self)

989 self._z = z

990

991

992 def _other_x_y_z3(other_x, y_z):

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

994 '''

995 try:

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

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

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

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

1000

1001

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

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

1004 '''

1005 if y_z:

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

1007 for x in xyz.values():

1008 d.update(x=x)

1009 return d

1010 return xyz

1011

1012# **) MIT License

1013#

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

1015#

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

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

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

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

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

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

1022#

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

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

1025#

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

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

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

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

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

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

1032# OTHER DEALINGS IN THE SOFTWARE.