1

2# -*- coding: utf-8 -*-

3

4 u'''(INTERNAL) Private, 3-D vector base class C{Vector3dBase}.

5

6 A pure Python implementation of vector-based functions by I{(C) Chris Veness

7 2011-2015} published under the same MIT Licence**, see U{Vector-based geodesy

8<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}.

9'''

10

11 from pygeodesy.basics import _copysign, islistuple, isscalar, map1, \

12 map2, _zip

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

14 _float0, isnear0, isnear1, isneg0, \

15 _pos_self, _0_0, _1_0

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

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

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

19 _COMMASPACE_, _xyz_

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

27 from pygeodesy.units import Float, Scalar

28# from pygeodesy.utily import sincos2 # _MODS

29

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

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

32

33__all__ = _ALL_LAZY.vector3dBase

34__version__ = '24.05.10'

35

36

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

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

39 '''

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

41

42 _ll = None # original latlon, '_fromll'

43# _x = INT0 # X component

44# _y = INT0 # Y component

45# _z = INT0 # Z component

46

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

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

49

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

51 distance from earth centre or height relative to the surface

52 of the earth' sphere or ellipsoid.

53

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

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

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

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

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

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

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

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

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

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

64

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

66 '''

67 self._x, \

68 self._y, \

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

70 _xyz3(type(self), x_xyz)

71 if ll:

72 self._ll = ll

73 if name:

74 self.name = name

75

76 def __abs__(self):

77 '''Return the norm of this vector.

78

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

80 '''

81 return self.length

82

83 def __add__(self, other):

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

85

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

87

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

89 '''

90 return self.plus(other)

91

92 def __bool__(self): # PYCHOK PyChecker

93 '''Is this vector non-zero?

94 '''

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

96

97 def __ceil__(self): # PYCHOK no cover

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

99

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

101 '''

102 return self._mapped(ceil)

103

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

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

106

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

108

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

110 '''

111 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 '''Return a vector with the C{floor} of these components.

138

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

140 '''

141 return self._mapped(floor)

142

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

144 '''Not implemented.'''

145 return _NotImplemented(self, other)

146

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

148 '''Not implemented.'''

149 return _NotImplemented(self, *other)

150

151 def __ge__(self, other):

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

153

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

155

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

157

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

159 '''

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

161

162# def __getitem__(self, key):

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

164# '''

165# return self.xyz[key]

166

167 def __gt__(self, other):

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

169

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

171

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

173

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

175 '''

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

177

178 def __hash__(self): # PYCHOK no cover

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

180 '''

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

182

183 def __iadd__(self, other):

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

185

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

187

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

189 '''

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

191

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

193 '''Not implemented.'''

194 return _NotImplemented(self, other)

195

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

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

198

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

200

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

202

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

204 '''

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

206

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

208 '''Not implemented.'''

209 return _NotImplemented(self, other)

210

211 def __imul__(self, scalar):

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

213

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

215

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

217 '''

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

219

220 def __int__(self): # PYCHOK no cover

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

222

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

224 '''

225 v = self.classof(_0_0)

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

227 return v

228

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

230 '''Not implemented.'''

231 return _NotImplemented(self, other, *mod)

232

233 def __isub__(self, other):

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

235

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

237

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

239 '''

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

241

242# def __iter__(self):

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

244# '''

245# return iter(self.xyz)

246

247 def __itruediv__(self, scalar):

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

249

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

251

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

253 '''

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

255

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

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

258

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

260

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

262

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

264 '''

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

266

267# def __len__(self):

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

269# '''

270# return len(self.xyz)

271

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

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

274

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

276

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

278

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

280 '''

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

282

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

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

285

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

287

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

289

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

291 '''

292 return self.cross(other)

293

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

295 '''Not implemented.'''

296 return _NotImplemented(self, other)

297

298 def __mul__(self, scalar):

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

300

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

302

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

304 '''

305 return self.times(scalar)

306

307 def __ne__(self, other):

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

309

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

311

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

313

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

315 '''

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

317

318 def __neg__(self):

319 '''Return the opposite of this vector.

320

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

322 '''

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

324

325 def __pos__(self): # PYCHOK no cover

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

327

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

329 '''

330 return self if _pos_self else self.copy()

331

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

333 '''Not implemented.'''

334 return _NotImplemented(self, other, *mod)

335

336 __radd__ = __add__ # PYCHOK no cover

337

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

339 '''Not implemented.'''

340 return _NotImplemented(self, other)

341

342# def __repr__(self):

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

344# '''

345# return self.toRepr()

346

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

348 '''Not implemented.'''

349 return _NotImplemented(self, other)

350

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

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

353

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

355

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

357

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

359 '''

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

361

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

363 '''Not implemented.'''

364 return _NotImplemented(self, other)

365

366 __rmul__ = __mul__

367

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

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

370

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

372

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

374 '''

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

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

377

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

379 '''Not implemented.'''

380 return _NotImplemented(self, other, *mod)

381

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

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

384

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

386

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

388

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

390 '''

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

392

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

394 '''Not implemented.'''

395 return _NotImplemented(self, scalar)

396

397# def __str__(self):

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

399# '''

400# return self.toStr()

401

402 def __sub__(self, other):

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

404

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

406

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

408

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

410 '''

411 return self.minus(other)

412

413 def __truediv__(self, scalar):

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

415

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

417

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

419

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

421 '''

422 return self.dividedBy(scalar)

423

424 def __trunc__(self): # PYCHOK no cover

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

426

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

428 '''

429 return self._mapped(trunc)

430

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

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

433 __div__ = __truediv__

434 __idiv__ = __itruediv__

435 __long__ = __int__

436 __nonzero__ = __bool__

437 __rdiv__ = __rtruediv__

438

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

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

441

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

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

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

445 positive if this->other is clockwise looking

446 along vSign or negative in opposite direction,

447 otherwise angle is unsigned.

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

449

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

451

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

453 '''

454 x = self.cross(other)

455 s = x.length

456 # use vSign as reference to set sign of s

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

458 s = -s

459

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

461 if wrap and fabs(a) > PI:

462 a -= _copysign(PI2, a)

463 return a

464

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

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

467 of this and an other vector.

468

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

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

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

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

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

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

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

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

477

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

479

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

481 '''

482 _xcallable(fun2=fun2)

483 if fun2_kwds:

484 def _f2(a, b):

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

486 else:

487 _f2 = fun2

488

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

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

491 return self.classof(*xyz)

492

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

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

495

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

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

498 non-L{NN}).

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

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

501

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

503

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

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

506

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

508 '''

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

510 x, y, z = self.xyz

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

512 (z * X - Z * x),

513 (x * Y - X * y))

514

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

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

517 r = other._fromll or other

518 s = self._fromll or self

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

520 t = _coincident_ if t else _colinear_

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

522

523 return self.classof(*xyz)

524

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

526 def crosserrors(self):

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

528 '''

529 return self._crosserrors

530

531 @crosserrors.setter # PYCHOK setter!

532 def crosserrors(self, raiser):

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

534 '''

535 self._crosserrors = bool(raiser)

536

537 def dividedBy(self, divisor):

538 '''Divide this vector by a scalar.

539

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

541

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

543

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

545

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

547 '''

548 d = Scalar(divisor=divisor)

549 try:

550 return self._times(_1_0 / d)

551 except (ValueError, ZeroDivisionError) as x:

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

553

554 def dot(self, other):

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

556

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

558

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

560

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

562 '''

563 return self.length2 if other is self else \

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

565

566 @deprecated_method

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

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

569 '''

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

571

572 @Property_RO

573 def euclid(self):

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

575

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

577 L{pygeodesy.euclid_}.

578 '''

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

580

581 def equirectangular(self, other):

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

583

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

585

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

587

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

589

590 @see: Property C{length2}.

591 '''

592 d = self.minus(other)

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

594

595 @Property

596 def _fromll(self):

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

598 '''

599 return self._ll

600

601 @_fromll.setter # PYCHOK setter!

602 def _fromll(self, ll):

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

604 '''

605 self._ll = ll or None

606

607 @property_RO

608 def homogeneous(self):

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

610 '''

611 x, y, z = self.xyz

612 if z:

613 x = x / z # /= chokes PyChecker

614 y = y / z

615# z = _1_0

616 else:

617 if isneg0(z):

618 x = -x

619 y = -y

620 x = _copysignINF(x)

621 y = _copysignINF(y)

622# z = NAN

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

624

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

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

627 and an other vector.

628

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

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

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

632

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

634

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

636 '''

637 f = Scalar(fraction=fraction)

638 if isnear0(f): # PYCHOK no cover

639 r = self

640 else:

641 r = self.others(other)

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

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

644 return r

645

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

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

648

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

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

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

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

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

654

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

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

657

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

659

660 @see: Method C{isequalTo}.

661 '''

662 self.others(other)

663 n = 0

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

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

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

667 n += 1 # conjugate

668 elif fabs(a - b) > eps:

669 return False # unequal

670 return bool(n >= minum)

671

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

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

674

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

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

677 version of both vectors.

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

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

680

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

682

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

684

685 @see: Method C{isconjugateTo}.

686 '''

687 if units:

688 self.others(other)

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

690 else:

691 d = self.minus(other)

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

693

694 @Property_RO

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

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

697

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

699 '''

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

701

702 @Property_RO

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

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

705

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

707 '''

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

709

710 def _mapped(self, func):

711 '''(INTERNAL) Map these components.

712 '''

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

714

715 def minus(self, other):

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

717

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

719

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

721

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

723 '''

724 xyz = self.others(other).xyz

725 return self._minus(*xyz)

726

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

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

729 '''

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

731

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

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

734

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

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

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

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

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

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

741

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

743

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

745 '''

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

747

748 def negate(self):

749 '''Return this vector in opposite direction.

750

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

752 '''

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

754

755 __neg__ = negate # PYCHOK no cover

756

757 @Property_RO

758 def _N_vector(self):

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

760 '''

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

762

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

764 '''Refined class comparison.

765

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

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

768 keyword arguments.

769

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

771

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

773 '''

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

775 if not isinstance(other, Vector3dBase):

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

777 return other

778

779 def plus(self, other):

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

781

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

783

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

785

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

787 '''

788 xyz = self.others(other).xyz

789 return self._plus(*xyz)

790

791 sum = plus # alternate name

792

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

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

795 '''

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

797

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

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

800

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

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

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

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

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

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

807

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

809

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

811 '''

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

813

814 def rotate(self, axis, theta):

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

816

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

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

819

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

821

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

823 Rotation_matrix#Rotation_matrix_from_axis_and_angle>} and

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

825 Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix>}.

826 '''

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

828 d = _1_0 - c

829 if d or s:

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

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

832

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

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

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

836

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

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

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

840 else: # unrotated

841 x, y, z = self.xyz

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

843

844 @deprecated_method

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

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

847 return self.rotate(axis, theta)

848

849 def times(self, factor):

850 '''Multiply this vector by a scalar.

851

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

853

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

855

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

857 '''

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

859

860 def _times(self, s):

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

862 '''

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

864

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

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

867

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

869 X, Y, and Z components as scale factors

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

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

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

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

874

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

876

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

878 '''

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

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

881

882# @deprecated_method

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

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

885#

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

887# '''

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

889

890# @deprecated_method

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

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

893#

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

895# '''

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

897

898 @deprecated_method

899 def to3xyz(self): # PYCHOK no cover

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

901 '''

902 return self.xyz

903

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

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

906

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

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

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

910

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

912 '''

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

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

915

916 def unit(self, ll=None):

917 '''Normalize this vector to unit length.

918

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

920

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

922 '''

923 u = self._united

924 if ll:

925 u._fromll = ll

926 return u

927

928 @Property_RO

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

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

931 '''

932 n = self.length

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

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

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

936 else:

937 u = self.copy()

938 u._update(False, _united=u)

939 if self._fromll:

940 u._fromll = self._fromll

941 return u

942

943 @Property

944 def x(self):

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

946 '''

947 return self._x

948

949 @x.setter # PYCHOK setter!

950 def x(self, x):

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

952 '''

953 x = Float(x=x)

954 if self._x != x:

955 _update_all(self, needed=3)

956 self._x = x

957

958 @Property

959 def xyz(self):

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

961 '''

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

963

964 @xyz.setter # PYCHOK setter!

965 def xyz(self, xyz):

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

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

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

969 '''

970 self._xyz(xyz)

971

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

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

974 '''

975 _update_all(self, needed=3)

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

977 return self

978

979 @property_RO

980 def x2y2z2(self):

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

982 '''

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

984

985 @Property

986 def y(self):

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

988 '''

989 return self._y

990

991 @y.setter # PYCHOK setter!

992 def y(self, y):

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

994 '''

995 y = Float(y=y)

996 if self._y != y:

997 _update_all(self, needed=3)

998 self._y = y

999

1000 @Property

1001 def z(self):

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

1003 '''

1004 return self._z

1005

1006 @z.setter # PYCHOK setter!

1007 def z(self, z):

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

1009 '''

1010 z = Float(z=z)

1011 if self._z != z:

1012 _update_all(self, needed=3)

1013 self._z = z

1014

1015

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

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

1018 '''

1019 try:

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

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

1022 x_xyz.xyz)

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

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

1025 return x_y_z

1026

1027

1028__all__ += _ALL_DOCS(Vector3dBase)

1029

1030# **) MIT License

1031#

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

1033#

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

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

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

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

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

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

1040#

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

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

1043#

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

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

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

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

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

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

1050# OTHER DEALINGS IN THE SOFTWARE.