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, _copysignINF, \

13 _float0, isnear0, isnear1, isneg0, \

14 _pos_self, _1_0

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

16 VectorError

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

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

19 _COMMASPACE_, _y_, _z_

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

21 _sys_version_info2

22 from pygeodesy.named import _NamedBase, _NotImplemented, _xother3

23# from pygeodesy.namedTuples import Vector3Tuple # _MODS

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

25 property_doc_, property_RO, _update_all

26 from pygeodesy.streprs import Fmt, strs

27 from pygeodesy.units import Float, Scalar

28 from pygeodesy.utily import sincos2, atan2, fabs

29

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

31

32__all__ = _ALL_LAZY.vector3dBase

33__version__ = '23.10.15'

34

35

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

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

38 '''

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

40

41 _ll = None # original latlon, '_fromll'

42 _x = INT0 # X component

43 _y = INT0 # Y component

44 _z = INT0 # Z component

45

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

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

48

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

50 distance from earth centre or height relative to the surface

51 of the earth' sphere or ellipsoid.

52

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

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

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

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

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

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

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

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

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

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

63

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

65 '''

66 if isscalar(x_xyz):

67 self._xyz(x_xyz, y, z)

68 else:

69 self.xyz = x_xyz

70 if ll:

71 self._ll = ll

72 if name:

73 self.name = name

74

75 def __abs__(self):

76 '''Return the norm of this vector.

77

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

79 '''

80 return self.length

81

82 def __add__(self, other):

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

84

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

86

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

88 '''

89 return self.plus(other)

90

91 def __bool__(self): # PYCHOK PyChecker

92 '''Is this vector non-zero?

93 '''

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

95

96 def __ceil__(self): # PYCHOK no cover

97 '''Not implemented.'''

98 return _NotImplemented(self)

99

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

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

102

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

104

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

106 '''

107 n = self.others(other).length

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

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

110

111 cmp = __cmp__

112

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

114 '''Not implemented.'''

115 return _NotImplemented(self, other)

116

117 def __eq__(self, other):

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

119

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

121

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

123

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

125 '''

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

127

128 def __float__(self): # PYCHOK no cover

129 '''Not implemented.'''

130 return _NotImplemented(self)

131

132 def __floor__(self): # PYCHOK no cover

133 '''Not implemented.'''

134 return _NotImplemented(self)

135

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

137 '''Not implemented.'''

138 return _NotImplemented(self, other)

139

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

141 '''Not implemented.'''

142 return _NotImplemented(self, *other)

143

144 def __ge__(self, other):

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

146

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

148

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

150

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

152 '''

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

154

155# def __getitem__(self, key):

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

157# '''

158# return self.xyz[key]

159

160 def __gt__(self, other):

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

162

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

164

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

166

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

168 '''

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

170

171 def __hash__(self): # PYCHOK no cover

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

173 '''

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

175

176 def __iadd__(self, other):

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

178

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

180

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

182 '''

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

184

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

186 '''Not implemented.'''

187 return _NotImplemented(self, other)

188

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

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

191

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

193

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

195

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

197 '''

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

199

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

201 '''Not implemented.'''

202 return _NotImplemented(self, other)

203

204 def __imul__(self, scalar):

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

206

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

208

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

210 '''

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

212

213 def __int__(self): # PYCHOK no cover

214 '''Not implemented.'''

215 return _NotImplemented(self)

216

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

218 '''Not implemented.'''

219 return _NotImplemented(self, other, *mod)

220

221 def __isub__(self, other):

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

223

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

225

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

227 '''

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

229

230# def __iter__(self):

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

232# '''

233# return iter(self.xyz)

234

235 def __itruediv__(self, scalar):

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

237

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

239

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

241 '''

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

243

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

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

246

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

248

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

250

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

252 '''

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

254

255# def __len__(self):

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

257# '''

258# return len(self.xyz)

259

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

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

262

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

264

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

266

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

268 '''

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

270

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

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

273

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

275

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

277

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

279 '''

280 return self.cross(other)

281

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

283 '''Not implemented.'''

284 return _NotImplemented(self, other)

285

286 def __mul__(self, scalar):

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

288

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

290

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

292 '''

293 return self.times(scalar)

294

295 def __ne__(self, other):

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

297

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

299

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

301

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

303 '''

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

305

306 def __neg__(self):

307 '''Return the opposite of this vector.

308

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

310 '''

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

312

313 def __pos__(self): # PYCHOK no cover

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

315

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

317 '''

318 return self if _pos_self else self.copy()

319

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

321 '''Not implemented.'''

322 return _NotImplemented(self, other, *mod)

323

324 __radd__ = __add__ # PYCHOK no cover

325

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

327 '''Not implemented.'''

328 return _NotImplemented(self, other)

329

330# def __repr__(self):

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

332# '''

333# return self.toRepr()

334

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

336 '''Not implemented.'''

337 return _NotImplemented(self, other)

338

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

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

341

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

343

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

345

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

347 '''

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

349

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

351 '''Not implemented.'''

352 return _NotImplemented(self, other)

353

354 __rmul__ = __mul__

355

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

357 '''Not implemented.'''

358 return _NotImplemented(self, ndigits=ndigits)

359

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

361 '''Not implemented.'''

362 return _NotImplemented(self, other, *mod)

363

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

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

366

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

368

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

370

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

372 '''

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

374

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

376 '''Not implemented.'''

377 return _NotImplemented(self, scalar)

378

379# def __str__(self):

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

381# '''

382# return self.toStr()

383

384 def __sub__(self, other):

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

386

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

388

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

390

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

392 '''

393 return self.minus(other)

394

395 def __truediv__(self, scalar):

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

397

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

399

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

401

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

403 '''

404 return self.dividedBy(scalar)

405

406 __trunc__ = __int__

407

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

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

410 __div__ = __truediv__

411 __idiv__ = __itruediv__

412 __long__ = __int__

413 __nonzero__ = __bool__

414 __rdiv__ = __rtruediv__

415

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

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

418

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

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

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

422 positive if this->other is clockwise looking

423 along vSign or negative in opposite direction,

424 otherwise angle is unsigned.

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

426

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

428

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

430 '''

431 x = self.cross(other)

432 s = x.length

433 # use vSign as reference to set sign of s

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

435 s = -s

436

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

438 if wrap and fabs(a) > PI:

439 a -= _copysign(PI2, a)

440 return a

441

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

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

444 of this and an other vector.

445

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

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

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

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

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

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

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

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

454

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

456

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

458 '''

459 if not callable(fun2):

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

461

462 if fun2_kwds:

463 def _f2(a, b):

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

465 else:

466 _f2 = fun2

467

468 xyz = _other_x_y_z3(other_x, y_z)

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

470 return self.classof(*xyz)

471

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

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

474

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

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

477 non-L{NN}).

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

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

480

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

482

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

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

485

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

487 '''

488 other = self.others(other)

489

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

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

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

493

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

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

496 r = other._fromll or other

497 s = self._fromll or self

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

499 t = _coincident_ if t else _colinear_

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

501

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

503

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

505 def crosserrors(self):

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

507 '''

508 return self._crosserrors

509

510 @crosserrors.setter # PYCHOK setter!

511 def crosserrors(self, raiser):

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

513 '''

514 self._crosserrors = bool(raiser)

515

516 def dividedBy(self, divisor):

517 '''Divide this vector by a scalar.

518

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

520

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

522

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

524

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

526 '''

527 d = Scalar(divisor=divisor)

528 try:

529 return self._times(_1_0 / d)

530 except (ValueError, ZeroDivisionError) as x:

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

532

533 def dot(self, other):

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

535

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

537

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

539

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

541 '''

542 return self.length2 if other is self else \

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

544

545 @deprecated_method

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

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

548 '''

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

550

551 @Property_RO

552 def euclid(self):

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

554

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

556 L{pygeodesy.euclid_}.

557 '''

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

559

560 def equirectangular(self, other):

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

562

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

564

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

566

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

568

569 @see: Property C{length2}.

570 '''

571 d = self.minus(other)

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

573

574 @Property

575 def _fromll(self):

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

577 '''

578 return self._ll

579

580 @_fromll.setter # PYCHOK setter!

581 def _fromll(self, ll):

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

583 '''

584 self._ll = ll or None

585

586 @property_RO

587 def homogeneous(self):

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

589 '''

590 z = self.z

591 if z:

592 x = self.x / z

593 y = self.y / z

594# z = _1_0

595 elif isneg0(z):

596 x = _copysignINF(-self.x)

597 y = _copysignINF(-self.y)

598# z = NAN

599 else:

600 x = _copysignINF(self.x)

601 y = _copysignINF(self.y)

602# z = NAN

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

604

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

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

607 and an other vector.

608

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

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

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

612

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

614

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

616 '''

617 f = Scalar(fraction=fraction)

618 if isnear0(f): # PYCHOK no cover

619 r = self

620 else:

621 r = self.others(other)

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

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

624 return r

625

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

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

628

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

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

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

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

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

634

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

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

637

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

639

640 @see: Method C{isequalTo}.

641 '''

642 self.others(other)

643 n = 0

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

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

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

647 n += 1 # conjugate

648 elif fabs(a - b) > eps:

649 return False # unequal

650 return bool(n >= minum)

651

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

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

654

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

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

657 version of both vectors.

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

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

660

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

662

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

664

665 @see: Method C{isconjugateTo}.

666 '''

667 if units:

668 self.others(other)

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

670 else:

671 d = self.minus(other)

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

673

674 @Property_RO

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

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

677

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

679 '''

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

681

682 @Property_RO

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

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

685

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

687 '''

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

689

690 def minus(self, other):

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

692

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

694

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

696

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

698 '''

699 self.others(other)

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

701

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

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

704 '''

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

706

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

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

709

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

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

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

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

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

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

716

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

718

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

720 '''

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

722

723 def negate(self):

724 '''Return this vector in opposite direction.

725

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

727 '''

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

729

730 __neg__ = negate # PYCHOK no cover

731

732 @Property_RO

733 def _N_vector(self):

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

735 '''

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

737

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

739 '''Refined class comparison.

740

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

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

743 keyword arguments.

744

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

746

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

748 '''

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

750 if not isinstance(other, Vector3dBase):

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

752 return other

753

754 def plus(self, other):

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

756

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

758

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

760

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

762 '''

763 self.others(other)

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

765

766 sum = plus # alternate name

767

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

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

770 '''

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

772

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

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

775

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

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

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

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

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

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

782

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

784

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

786 '''

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

788

789 def rotate(self, axis, theta):

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

791

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

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

794

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

796

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

798 Rotation_matrix#Rotation_matrix_from_axis_and_angle>} and

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

800 Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix>}.

801 '''

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

803 d = _1_0 - c

804 if d or s:

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

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

807

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

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

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

811

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

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

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

815 else: # unrotated

816 x, y, z = self.xyz

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

818

819 @deprecated_method

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

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

822 return self.rotate(axis, theta)

823

824 def times(self, factor):

825 '''Multiply this vector by a scalar.

826

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

828

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

830

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

832 '''

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

834

835 def _times(self, s):

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

837 '''

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

839

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

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

842

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

844 X, Y, and Z components as scale factors

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

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

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

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

849

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

851

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

853 '''

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

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

856

857# @deprecated_method

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

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

860#

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

862# '''

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

864

865# @deprecated_method

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

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

868#

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

870# '''

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

872

873 @deprecated_method

874 def to3xyz(self): # PYCHOK no cover

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

876 '''

877 return self.xyz

878

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

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

881

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

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

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

885

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

887 '''

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

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

890

891 def unit(self, ll=None):

892 '''Normalize this vector to unit length.

893

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

895

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

897 '''

898 u = self._united

899 if ll:

900 u._fromll = ll

901 return u

902

903 @Property_RO

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

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

906 '''

907 n = self.length

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

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

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

911 else:

912 u = self.copy()

913 u._update(False, _united=u)

914 if self._fromll:

915 u._fromll = self._fromll

916 return u

917

918 @Property

919 def x(self):

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

921 '''

922 return self._x

923

924 @x.setter # PYCHOK setter!

925 def x(self, x):

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

927 '''

928 x = Float(x=x)

929 if self._x != x:

930 _update_all(self)

931 self._x = x

932

933 @Property

934 def xyz(self):

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

936 '''

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

938

939 @xyz.setter # PYCHOK setter!

940 def xyz(self, xyz):

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

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

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

944 '''

945 if islistuple(xyz, 3):

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

947 else:

948 self._xyz(xyz)

949

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

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

952 '''

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

954 _update_all(self)

955 try:

956 self._x, \

957 self._y, \

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

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

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

961 return self

962

963 @Property_RO

964 def x2y2z2(self):

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

966 '''

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

968

969 @Property

970 def y(self):

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

972 '''

973 return self._y

974

975 @y.setter # PYCHOK setter!

976 def y(self, y):

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

978 '''

979 y = Float(y=y)

980 if self._y != y:

981 _update_all(self)

982 self._y = y

983

984 @Property

985 def z(self):

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

987 '''

988 return self._z

989

990 @z.setter # PYCHOK setter!

991 def z(self, z):

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

993 '''

994 z = Float(z=z)

995 if self._z != z:

996 _update_all(self)

997 self._z = z

998

999

1000 def _other_x_y_z3(other_x, y_z):

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

1002 '''

1003 try:

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

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

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

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

1008

1009

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

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

1012 '''

1013 if y_z:

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

1015 for x in xyz.values():

1016 d.update(x=x)

1017 return d

1018 return xyz

1019

1020

1021__all__ += _ALL_DOCS(Vector3dBase)

1022

1023# **) MIT License

1024#

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

1026#

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

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

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

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

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

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

1033#

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

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

1036#

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

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

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

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

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

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

1043# OTHER DEALINGS IN THE SOFTWARE.