1

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

3

4 u'''Various utility functions.

5

6 After I{(C) Chris Veness 2011-2015} published under the same MIT Licence**, see

7 U{Latitude/Longitude<https://www.Movable-Type.co.UK/scripts/latlong.html>} and

8 U{Vector-based geodesy<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}.

9'''

10# make sure int/int division yields float quotient, see .basics

11 from __future__ import division as _; del _ # PYCHOK semicolon

12

13 from pygeodesy.basics import copysign0, isint

14 from pygeodesy.constants import EPS, EPS0, INF, NAN, NEG0, NINF, PI, PI2, PI_2, R_M, \

15 _float as _F, _isfinite, isnan, isnear0, isneg0, _M_KM, \

16 _M_NM, _M_SM, _0_0, _1__90, _0_5, _1_0, _N_1_0, _2__PI, \

17 _10_0, _90_0, _N_90_0, _180_0, _N_180_0, _360_0, _400_0

18 from pygeodesy.interns import _edge_, _radians_, _semi_circular_, _SPACE_

19 from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS

20 from pygeodesy.units import Degrees, Feet, Float, Lam, Lam_, Meter, Meter2, Radians

21

22 from math import acos, asin, atan2, cos, degrees, fabs, radians, sin, tan # pow

23

24__all__ = _ALL_LAZY.utily

25__version__ = '22.10.23'

26

27# read constant name "_M_UNIT" as "meter per unit"

28_M_CHAIN = _F( 20.1168) # yard2m(1) * 22

29_M_FATHOM = _F( 1.8288) # yard2m(1) * 2 or _M_NM * 1e-3

30_M_FOOT_GE = _F( 0.31608) # German Fuss (1 / 3.1637560111364)

31_M_FOOT_FR = _F( 0.3248406) # French Pied-du-Roi or pied (1 / 3.0784329298739)

32_M_FOOT_INTL = _F( 0.3048) # Int'l (1 / 3.2808398950131 = 10_000 / (254 * 12))

33_M_FOOT_USRV = _F( 0.3048006096012192) # US Survey (1200 / 3937)

34_M_FURLONG = _F( 201.168) # 220 * yard2m(1) == 10 * m2chain(1)

35# _M_KM = _F(1000.0) # kilo meter

36# _M_NM = _F(1852.0) # nautical mile

37# _M_SM = _F(1609.344) # statute mile

38_M_TOISE = _F( 1.9490436) # French toise, 6 pieds (6 / 3.0784329298739)

39_M_YARD_UK = _F( 0.9144) # 254 * 12 * 3 / 10_000 == 3 * ft2m(1) Int'l

40

41

42 def acos1(x):

43 '''Return C{math.acos(max(-1, min(1, B{x})))}.

44 '''

45 return acos(x) if fabs(x) < _1_0 else (PI if x < 0 else _0_0)

46

47

48 def acre2ha(acres):

49 '''Convert acres to hectare.

50

51 @arg acres: Value in acres (C{scalar}).

52

53 @return: Value in C{hectare} (C{float}).

54

55 @raise ValueError: Invalid B{C{acres}}.

56 '''

57 # 0.40468564224 == acre2m2(1) / 10_000

58 return Float(ha=Float(acres) * 0.40468564224)

59

60

61 def acre2m2(acres):

62 '''Convert acres to I{square} meter.

63

64 @arg acres: Value in acres (C{scalar}).

65

66 @return: Value in C{meter^2} (C{float}).

67

68 @raise ValueError: Invalid B{C{acres}}.

69 '''

70 # 4046.8564224 == chain2m(1) * furlong2m(1)

71 return Meter2(Float(acres) * 4046.8564224)

72

73

74 def asin1(x):

75 '''Return C{math.asin(max(-1, min(1, B{x})))}.

76 '''

77 return asin(x) if fabs(x) < _1_0 else (-PI_2 if x < 0 else PI_2) # -PI_2, not PI3_2!

78

79

80 def atand(y_x):

81 '''Return C{atan(B{y_x})} angle in C{degrees}.

82

83 @see: Function L{pygeodesy.atan2d}.

84 '''

85 return atan2d(y_x, _1_0)

86

87

88 def atan2b(y, x):

89 '''Return C{atan2(B{y}, B{x})} in degrees M{[0..+360]}.

90

91 @see: Function L{pygeodesy.atan2d}.

92 '''

93 d = atan2d(y, x)

94 if d < 0:

95 d += _360_0

96 return d

97

98

99 def atan2d(y, x, reverse=False):

100 '''Return C{atan2(B{y}, B{x})} in degrees M{[-180..+180]},

101 optionally reversed (by 180 degrees for C{azi2}).

102

103 @see: I{Karney}'s C++ function U{Math.atan2d

104 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}.

105 '''

106 if fabs(y) > fabs(x) > 0:

107 if y < 0: # q = 3

108 d = degrees(atan2(x, -y)) - _90_0

109 else: # q = 2

110 d = _90_0 - degrees(atan2(x, y))

111 elif x < 0: # q = 1

112 d = copysign0(_180_0, y) - degrees(atan2(y, -x))

113 elif x > 0: # q = 0

114 d = degrees(atan2(y, x)) if y else _0_0

115 elif isnan(x) or isnan(y):

116 return NAN

117 else: # x == 0

118 d = _N_90_0 if y < 0 else (_90_0 if y > 0 else _0_0)

119 if reverse:

120 d += _180_0 if d < 0 else _N_180_0

121 return d

122

123

124 def chain2m(chains):

125 '''Convert I{UK} chains to meter.

126

127 @arg chains: Value in chains (C{scalar}).

128

129 @return: Value in C{meter} (C{float}).

130

131 @raise ValueError: Invalid B{C{chains}}.

132 '''

133 return Meter(Float(chains=chains) * _M_CHAIN)

134

135

136 def circle4(earth, lat):

137 '''Get the equatorial or a parallel I{circle of latitude}.

138

139 @arg earth: The earth radius, ellipsoid or datum

140 (C{meter}, L{Ellipsoid}, L{Ellipsoid2},

141 L{Datum} or L{a_f2Tuple}).

142 @arg lat: Geodetic latitude (C{degrees90}, C{str}).

143

144 @return: A L{Circle4Tuple}C{(radius, height, lat, beta)}

145 instance.

146

147 @raise RangeError: Latitude B{C{lat}} outside valid range and

148 L{pygeodesy.rangerrors} set to C{True}.

149

150 @raise TypeError: Invalid B{C{earth}}.

151

152 @raise ValueError: B{C{earth}} or B{C{lat}}.

153 '''

154 E = _MODS.datums._spherical_datum(earth).ellipsoid

155 return E.circle4(lat)

156

157

158 def cot(rad, **error_kwds):

159 '''Return the C{cotangent} of an angle in C{radians}.

160

161 @arg rad: Angle (C{radians}).

162 @kwarg error_kwds: Error to raise (C{ValueError}).

163

164 @return: C{cot(B{rad})}.

165

166 @raise ValueError: L{pygeodesy.isnear0}C{(sin(B{rad})}.

167 '''

168 s, c = sincos2(rad)

169 if isnear0(s):

170 raise _valueError(cot, rad, **error_kwds)

171 return c / s

172

173

174 def cot_(*rads, **error_kwds):

175 '''Return the C{cotangent} of angle(s) in C{radiansresection}.

176

177 @arg rads: One or more angles (C{radians}).

178 @kwarg error_kwds: Error to raise (C{ValueError}).

179

180 @return: Yield the C{cot(B{rad})} for each angle.

181

182 @raise ValueError: See L{pygeodesy.cot}.

183 '''

184 try:

185 for r in rads:

186 yield cot(r)

187 except ValueError:

188 raise _valueError(cot_, r, **error_kwds)

189

190

191 def cotd(deg, **error_kwds):

192 '''Return the C{cotangent} of an angle in C{degrees}.

193

194 @arg deg: Angle (C{degrees}).

195 @kwarg error_kwds: Error to raise (C{ValueError}).

196

197 @return: C{cot(B{deg})}.

198

199 @raise ValueError: L{pygeodesy.isnear0}C{(sin(B{deg})}.

200 '''

201 s, c = sincos2d(deg)

202 if isnear0(s):

203 raise _valueError(cotd, deg, **error_kwds)

204 return c / s

205

206

207 def cotd_(*degs, **error_kwds):

208 '''Return the C{cotangent} of angle(s) in C{degrees}.

209

210 @arg degs: One or more angles (C{degrees}).

211 @kwarg error_kwds: Error to raise (C{ValueError}).

212

213 @return: Yield the C{cot(B{deg})} for each angle.

214

215 @raise ValueError: See L{pygeodesy.cotd}.

216 '''

217 try:

218 for d in degs:

219 yield cotd(d)

220 except ValueError:

221 raise _valueError(cotd_, d, **error_kwds)

222

223

224 def degrees90(rad):

225 '''Convert radians to degrees and wrap M{[-270..+90]}.

226

227 @arg rad: Angle (C{radians}).

228

229 @return: Angle, wrapped (C{degrees90}).

230 '''

231 return _wrap(degrees(rad), _90_0, _360_0)

232

233

234 def degrees180(rad):

235 '''Convert radians to degrees and wrap M{[-180..+180]}.

236

237 @arg rad: Angle (C{radians}).

238

239 @return: Angle, wrapped (C{degrees180}).

240 '''

241 return _wrap(degrees(rad), _180_0, _360_0)

242

243

244 def degrees360(rad):

245 '''Convert radians to degrees and wrap M{[0..+360)}.

246

247 @arg rad: Angle (C{radians}).

248

249 @return: Angle, wrapped (C{degrees360}).

250 '''

251 return _wrap(degrees(rad), _360_0, _360_0)

252

253

254 def degrees2grades(deg):

255 '''Convert degrees to I{grades} (aka I{gons} or I{gradians}).

256

257 @arg deg: Angle (C{degrees}).

258

259 @return: Angle (C{grades}).

260 '''

261 return Degrees(deg) * _400_0 / _360_0

262

263

264 def degrees2m(deg, radius=R_M, lat=0):

265 '''Convert an angle to a distance along the equator or

266 along the parallel at an other (geodetic) latitude.

267

268 @arg deg: The angle (C{degrees}).

269 @kwarg radius: Mean earth radius, ellipsoid or datum

270 (C{meter}, L{Ellipsoid}, L{Ellipsoid2},

271 L{Datum} or L{a_f2Tuple}).

272 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).

273

274 @return: Distance (C{meter}, same units as B{C{radius}}

275 or equatorial and polar radii) or C{0.0} for

276 near-polar B{C{lat}}.

277

278 @raise RangeError: Latitude B{C{lat}} outside valid range and

279 L{pygeodesy.rangerrors} set to C{True}.

280

281 @raise TypeError: Invalid B{C{radius}}.

282

283 @raise ValueError: Invalid B{C{deg}}, B{C{radius}} or

284 B{C{lat}}.

285

286 @see: Function L{radians2m} and L{m2degrees}.

287 '''

288 return _radians2m(Lam_(deg=deg, clip=0), radius, lat)

289

290

291 def fathom2m(fathoms):

292 '''Convert I{Imperial} fathom to meter.

293

294 @arg fathoms: Value in fathoms (C{scalar}).

295

296 @return: Value in C{meter} (C{float}).

297

298 @raise ValueError: Invalid B{C{fathoms}}.

299

300 @see: Function L{toise2m}, U{Fathom<https://WikiPedia.org/wiki/Fathom>}

301 and U{Klafter<https://WikiPedia.org/wiki/Klafter>}.

302 '''

303 return Meter(Float(fathoms=fathoms) * _M_FATHOM)

304

305

306 def ft2m(feet, usurvey=False, pied=False, fuss=False):

307 '''Convert I{International}, I{US Survey}, I{French} or I{German}

308 B{C{feet}} to C{meter}.

309

310 @arg feet: Value in feet (C{scalar}).

311 @kwarg usurvey: Convert I{US Survey} foot if C{True} else ...

312 @kwarg pied: Convert French I{pied-du-Roi} if C{True} else ...

313 @kwarg fuss: Convert German I{Fuss} if C{True}, otherwise

314 I{International} feet to C{meter}.

315

316 @return: Value in C{meter} (C{float}).

317

318 @raise ValueError: Invalid B{C{feet}}.

319 '''

320 return Meter(Feet(feet) * (_M_FOOT_USRV if usurvey else

321 (_M_FOOT_FR if pied else

322 (_M_FOOT_GE if fuss else _M_FOOT_INTL))))

323

324

325 def furlong2m(furlongs):

326 '''Convert a furlong to meter.

327

328 @arg furlongs: Value in furlongs (C{scalar}).

329

330 @return: Value in C{meter} (C{float}).

331

332 @raise ValueError: Invalid B{C{furlongs}}.

333 '''

334 return Meter(Float(furlongs=furlongs) * _M_FURLONG)

335

336

337 def grades(rad):

338 '''Convert radians to I{grades} (aka I{gons} or I{gradians}).

339

340 @arg rad: Angle (C{radians}).

341

342 @return: Angle (C{grades}).

343 '''

344 return Float(grades=Float(rad=rad) * _400_0 / PI2)

345

346

347 def grades400(rad):

348 '''Convert radians to I{grades} (aka I{gons} or I{gradians}) and wrap M{[0..+400)}.

349

350 @arg rad: Angle (C{radians}).

351

352 @return: Angle, wrapped (C{grades}).

353 '''

354 return Float(grades400=_wrap(grades(rad), _400_0, _400_0))

355

356

357 def grades2degrees(gon):

358 '''Convert I{grades} (aka I{gons} or I{gradians}) to C{degrees}.

359

360 @arg gon: Angle (C{grades}).

361

362 @return: Angle (C{degrees}).

363 '''

364 return Degrees(Float(gon=gon) * _360_0 / _400_0)

365

366

367 def grades2radians(gon):

368 '''Convert I{grades} (aka I{gons} or I{gradians}) to C{radians}.

369

370 @arg gon: Angle (C{grades}).

371

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

373 '''

374 return Radians(Float(gon=gon) * PI2 / _400_0)

375

376

377 def km2m(km):

378 '''Convert kilo meter to meter (m).

379

380 @arg km: Value in kilo meter (C{scalar}).

381

382 @return: Value in meter (C{float}).

383

384 @raise ValueError: Invalid B{C{km}}.

385 '''

386 return Meter(Float(km=km) * _M_KM)

387

388

389 def m2chain(meter):

390 '''Convert meter to I{UK} chains.

391

392 @arg meter: Value in meter (C{scalar}).

393

394 @return: Value in C{chains} (C{float}).

395

396 @raise ValueError: Invalid B{C{meter}}.

397 '''

398 return Float(chain=Meter(meter) / _M_CHAIN) # * 0.049709695378986715

399

400

401 def m2degrees(distance, radius=R_M, lat=0):

402 '''Convert a distance to an angle along the equator or

403 along the parallel at an other (geodetic) latitude.

404

405 @arg distance: Distance (C{meter}, same units as B{C{radius}}).

406 @kwarg radius: Mean earth radius, ellipsoid or datum (C{meter},

407 an L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or

408 L{a_f2Tuple}).

409 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).

410

411 @return: Angle (C{degrees}) or C{INF} for near-polar B{C{lat}}.

412

413 @raise RangeError: Latitude B{C{lat}} outside valid range and

414 L{pygeodesy.rangerrors} set to C{True}.

415

416 @raise TypeError: Invalid B{C{radius}}.

417

418 @raise ValueError: Invalid B{C{distance}}, B{C{radius}}

419 or B{C{lat}}.

420

421 @see: Function L{m2radians} and L{degrees2m}.

422 '''

423 return degrees(m2radians(distance, radius=radius, lat=lat))

424

425

426 def m2fathom(meter):

427 '''Convert meter to I{Imperial} fathoms.

428

429 @arg meter: Value in meter (C{scalar}).

430

431 @return: Value in C{fathoms} (C{float}).

432

433 @raise ValueError: Invalid B{C{meter}}.

434

435 @see: Function L{m2toise}, U{Fathom<https://WikiPedia.org/wiki/Fathom>}

436 and U{Klafter<https://WikiPedia.org/wiki/Klafter>}.

437 '''

438 return Float(fathom=Meter(meter) / _M_FATHOM) # * 0.546806649

439

440

441 def m2ft(meter, usurvey=False, pied=False, fuss=False):

442 '''Convert meter to I{International}, I{US Survey}, I{French} or

443 or I{German} feet (C{ft}).

444

445 @arg meter: Value in meter (C{scalar}).

446 @kwarg usurvey: Convert to I{US Survey} foot if C{True} else ...

447 @kwarg pied: Convert to French I{pied-du-Roi} if C{True} else ...

448 @kwarg fuss: Convert to German I{Fuss} if C{True}, otherwise to

449 I{International} foot.

450

451 @return: Value in C{feet} (C{float}).

452

453 @raise ValueError: Invalid B{C{meter}}.

454 '''

455 # * 3.2808333333333333, US Survey 3937 / 1200

456 # * 3.2808398950131235, Int'l 10_000 / (254 * 12)

457 return Float(feet=Meter(meter) / (_M_FOOT_USRV if usurvey else

458 (_M_FOOT_FR if pied else

459 (_M_FOOT_GE if fuss else _M_FOOT_INTL))))

460

461

462 def m2furlong(meter):

463 '''Convert meter to furlongs.

464

465 @arg meter: Value in meter (C{scalar}).

466

467 @return: Value in C{furlongs} (C{float}).

468

469 @raise ValueError: Invalid B{C{meter}}.

470 '''

471 return Float(furlong=Meter(meter) / _M_FURLONG) # * 0.00497096954

472

473

474 def m2km(meter):

475 '''Convert meter to kilo meter (km).

476

477 @arg meter: Value in meter (C{scalar}).

478

479 @return: Value in km (C{float}).

480

481 @raise ValueError: Invalid B{C{meter}}.

482 '''

483 return Float(km=Meter(meter) / _M_KM)

484

485

486 def m2NM(meter):

487 '''Convert meter to nautical miles (NM).

488

489 @arg meter: Value in meter (C{scalar}).

490

491 @return: Value in C{NM} (C{float}).

492

493 @raise ValueError: Invalid B{C{meter}}.

494 '''

495 return Float(NM=Meter(meter) / _M_NM) # * 5.39956804e-4

496

497

498 def m2radians(distance, radius=R_M, lat=0):

499 '''Convert a distance to an angle along the equator or

500 along the parallel at an other (geodetic) latitude.

501

502 @arg distance: Distance (C{meter}, same units as B{C{radius}}).

503 @kwarg radius: Mean earth radius, ellipsoid or datum (C{meter},

504 an L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or

505 L{a_f2Tuple}).

506 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).

507

508 @return: Angle (C{radians}) or C{INF} for near-polar B{C{lat}}.

509

510 @raise RangeError: Latitude B{C{lat}} outside valid range and

511 L{pygeodesy.rangerrors} set to C{True}.

512

513 @raise TypeError: Invalid B{C{radius}}.

514

515 @raise ValueError: Invalid B{C{distance}}, B{C{radius}}

516 or B{C{lat}}.

517

518 @see: Function L{m2degrees} and L{radians2m}.

519 '''

520 m = circle4(radius, lat).radius

521 return INF if m < EPS0 else Radians(Float(distance=distance) / m)

522

523

524 def m2SM(meter):

525 '''Convert meter to statute miles (SM).

526

527 @arg meter: Value in meter (C{scalar}).

528

529 @return: Value in C{SM} (C{float}).

530

531 @raise ValueError: Invalid B{C{meter}}.

532 '''

533 return Float(SM=Meter(meter) / _M_SM) # * 6.21369949e-4 == 1 / 1609.344

534

535

536 def m2toise(meter):

537 '''Convert meter to French U{toises<https://WikiPedia.org/wiki/Toise>}.

538

539 @arg meter: Value in meter (C{scalar}).

540

541 @return: Value in C{toises} (C{float}).

542

543 @raise ValueError: Invalid B{C{meter}}.

544

545 @see: Function L{m2fathom}.

546 '''

547 return Float(toise=Meter(meter) / _M_TOISE) # * 0.513083632632119

548

549

550 def m2yard(meter):

551 '''Convert meter to I{UK} yards.

552

553 @arg meter: Value in meter (C{scalar}).

554

555 @return: Value in C{yards} (C{float}).

556

557 @raise ValueError: Invalid B{C{meter}}.

558 '''

559 return Float(yard=Meter(meter) / _M_YARD_UK) # * 1.0936132983377078

560

561

562 def NM2m(nm):

563 '''Convert nautical miles to meter (m).

564

565 @arg nm: Value in nautical miles (C{scalar}).

566

567 @return: Value in meter (C{float}).

568

569 @raise ValueError: Invalid B{C{nm}}.

570 '''

571 return Meter(Float(nm=nm) * _M_NM)

572

573

574 def radians2m(rad, radius=R_M, lat=0):

575 '''Convert an angle to a distance along the equator or

576 along the parallel at an other (geodetic) latitude.

577

578 @arg rad: The angle (C{radians}).

579 @kwarg radius: Mean earth radius, ellipsoid or datum

580 (C{meter}, L{Ellipsoid}, L{Ellipsoid2},

581 L{Datum} or L{a_f2Tuple}).

582 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).

583

584 @return: Distance (C{meter}, same units as B{C{radius}}

585 or equatorial and polar radii) or C{0.0} for

586 near-polar B{C{lat}}.

587

588 @raise RangeError: Latitude B{C{lat}} outside valid range and

589 L{pygeodesy.rangerrors} set to C{True}.

590

591 @raise TypeError: Invalid B{C{radius}}.

592

593 @raise ValueError: Invalid B{C{rad}}, B{C{radius}} or

594 B{C{lat}}.

595

596 @see: Function L{degrees2m} and L{m2radians}.

597 '''

598 return _radians2m(Lam(rad=rad, clip=0), radius, lat)

599

600

601 def _radians2m(rad, radius, lat):

602 '''(INTERNAL) Helper for C{degrees2m} and C{radians2m}.

603 '''

604 m = circle4(radius, lat).radius

605 return _0_0 if m < EPS0 else (rad * m)

606

607

608 def radiansPI(deg):

609 '''Convert and wrap degrees to radians M{[-PI..+PI]}.

610

611 @arg deg: Angle (C{degrees}).

612

613 @return: Radians, wrapped (C{radiansPI})

614 '''

615 return _wrap(radians(deg), PI, PI2)

616

617

618 def radiansPI2(deg):

619 '''Convert and wrap degrees to radians M{[0..+2PI)}.

620

621 @arg deg: Angle (C{degrees}).

622

623 @return: Radians, wrapped (C{radiansPI2})

624 '''

625 return _wrap(radians(deg), PI2, PI2)

626

627

628 def radiansPI_2(deg):

629 '''Convert and wrap degrees to radians M{[-3PI/2..+PI/2]}.

630

631 @arg deg: Angle (C{degrees}).

632

633 @return: Radians, wrapped (C{radiansPI_2})

634 '''

635 return _wrap(radians(deg), PI_2, PI2)

636

637

638 def _sin0cos2(q, r, sign):

639 '''(INTERNAL) 2-tuple (C{sin(r), cos(r)}) in quadrant C{0 <= B{q} <= 3}

640 and C{sin} zero I{signed} with B{C{sign}}.

641 '''

642 if r < PI_2:

643 s, c = sin(r), cos(r)

644 t = s, c, -s, -c, s

645 else: # r == PI_2

646 t = _1_0, _0_0, _N_1_0, _0_0, _1_0

647# else: # r == 0, testUtility failures

648# t = _0_0, _1_0, _0_0, _N_1_0, _0_0

649# q &= 3

650 s = t[q] or (NEG0 if sign < 0 else _0_0)

651 c = t[q + 1] or _0_0

652 return s, c

653

654

655 def sincos2(rad):

656 '''Return the C{sine} and C{cosine} of an angle in C{radians}.

657

658 @arg rad: Angle (C{radians}).

659

660 @return: 2-Tuple (C{sin(B{rad})}, C{cos(B{rad})}).

661

662 @see: U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/

663 classGeographicLib_1_1Math.html#sincosd>} function U{sincosd

664 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/

665 python/geographiclib/geomath.py#l155>} and C++ U{sincosd

666 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/

667 include/GeographicLib/Math.hpp#l558>}.

668 '''

669 if _isfinite(rad):

670 q = int(rad * _2__PI) # int(math.floor)

671 if q < 0:

672 q -= 1

673 t = _sin0cos2(q & 3, rad - q * PI_2, rad)

674 else:

675 t = NAN, NAN

676 return t

677

678

679 def sincos2_(*rads):

680 '''Return the C{sine} and C{cosine} of angle(s) in C{radians}.

681

682 @arg rads: One or more angles (C{radians}).

683

684 @return: Yield the C{sin(B{rad})} and C{cos(B{rad})} for each angle.

685

686 @see: function L{sincos2}.

687 '''

688 for r in rads:

689 s, c = sincos2(r)

690 yield s

691 yield c

692

693

694 def sincos2d(deg, **adeg):

695 '''Return the C{sine} and C{cosine} of an angle in C{degrees}.

696

697 @arg deg: Angle (C{degrees}).

698 @kwarg adeg: Optional correction (C{degrees}).

699

700 @return: 2-Tuple (C{sin(B{deg_})}, C{cos(B{deg_})}, C{B{deg_} =

701 B{deg} + B{adeg}}).

702

703 @see: U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/

704 classGeographicLib_1_1Math.html#sincosd>} function U{sincosd

705 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/

706 python/geographiclib/geomath.py#l155>} and C++ U{sincosd

707 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/

708 include/GeographicLib/Math.hpp#l558>}.

709 '''

710 if _isfinite(deg):

711 q = int(deg * _1__90) # int(math.floor)

712 if q < 0:

713 q -= 1

714 d = deg - q * _90_0

715 if adeg:

716 t = _MODS.errors._xkwds_get(adeg, adeg=_0_0)

717 d = _MODS.karney._around(d + t)

718 t = _sin0cos2(q & 3, radians(d), deg)

719 else:

720 t = NAN, NAN

721 return t

722

723

724 def sincos2d_(*degs):

725 '''Return the C{sine} and C{cosine} of angle(s) in C{degrees}.

726

727 @arg degs: One or more angles (C{degrees}).

728

729 @return: Yield the C{sin(B{deg})} and C{cos(B{deg})} for each angle.

730

731 @see: Function L{sincos2d}.

732 '''

733 for d in degs:

734 s, c = sincos2d(d)

735 yield s

736 yield c

737

738

739 def sincostan3(rad):

740 '''Return the C{sine}, C{cosine} and C{tangent} of an angle in C{radians}.

741

742 @arg rad: Angle (C{radians}).

743

744 @return: 3-Tuple (C{sin(B{rad})}, C{cos(B{rad})}, C{tan(B{rad})}).

745

746 @see: Function L{sincos2}.

747 '''

748 rad %= PI2 # see ._wrap comments

749 if rad:

750 s, c = sincos2(rad)

751 t = (s / c) if c else (NINF if s < 0

752 else (INF if s > 0 else s))

753 else: # sin(-0.0) == tan(-0.0) = -0.0

754 c = _1_0

755 s = t = NEG0 if isneg0(rad) else _0_0

756 return s, c, t

757

758

759 def SM2m(sm):

760 '''Convert statute miles to meter (m).

761

762 @arg sm: Value in statute miles (C{scalar}).

763

764 @return: Value in meter (C{float}).

765

766 @raise ValueError: Invalid B{C{sm}}.

767 '''

768 return Meter(Float(sm=sm) * _M_SM)

769

770

771 def tan_2(rad, **semi): # edge=1

772 '''Compute the tangent of half angle.

773

774 @arg rad: Angle (C{radians}).

775 @kwarg semi: Angle or edge name and index

776 for semi-circular error.

777

778 @return: M{tan(rad / 2)} (C{float}).

779

780 @raise ValueError: If B{C{rad}} is semi-circular

781 and B{C{semi}} is given.

782 '''

783 # .formy.excessKarney_, .sphericalTrigonometry.areaOf

784 if semi and isnear0(fabs(rad) - PI):

785 for n, v in semi.items():

786 break

787 n = _SPACE_(n, _radians_) if not isint(v) else \

788 _SPACE_(_MODS.streprs.Fmt.SQUARE(**semi), _edge_)

789 raise _MODS.errors._ValueError(n, rad, txt=_semi_circular_)

790

791 return tan(rad * _0_5)

792

793

794 def tand(deg, **error_kwds):

795 '''Return the C{tangent} of an angle in C{degrees}.

796

797 @arg deg: Angle (C{degrees}).

798 @kwarg error_kwds: Error to raise (C{ValueError}).

799

800 @return: C{tan(B{deg})}.

801

802 @raise ValueError: If L{pygeodesy.isnear0}C{(cos(B{deg})}.

803 '''

804 s, c = sincos2d(deg)

805 if isnear0(c):

806 raise _valueError(tand, deg, **error_kwds)

807 return s / c

808

809

810 def tand_(*degs, **error_kwds):

811 '''Return the C{tangent} of angle(s) in C{degrees}.

812

813 @arg degs: One or more angles (C{degrees}).

814 @kwarg error_kwds: Error to raise (C{ValueError}).

815

816 @return: Yield the C{tan(B{deg})} for each angle.

817

818 @raise ValueError: See L{pygeodesy.tand}.

819 '''

820 for d in degs:

821 yield tand(d, **error_kwds)

822

823

824 def tanPI_2_2(rad):

825 '''Compute the tangent of half angle, 90 degrees rotated.

826

827 @arg rad: Angle (C{radians}).

828

829 @return: M{tan((rad + PI/2) / 2)} (C{float}).

830 '''

831 return tan((rad + PI_2) * _0_5)

832

833

834 def toise2m(toises):

835 '''Convert French U{toises<https://WikiPedia.org/wiki/Toise>} to meter.

836

837 @arg toises: Value in toises (C{scalar}).

838

839 @return: Value in C{meter} (C{float}).

840

841 @raise ValueError: Invalid B{C{toises}}.

842

843 @see: Function L{fathom2m}.

844 '''

845 return Meter(Float(toises=toises) * _M_TOISE)

846

847

848 def truncate(x, ndigits=None):

849 '''Truncate to the given number of digits.

850

851 @arg x: Value to truncate (C{scalar}).

852 @kwarg ndigits: Number of digits (C{int}),

853 aka I{precision}.

854

855 @return: Truncated B{C{x}} (C{float}).

856

857 @see: Python function C{round}.

858 '''

859 if isint(ndigits):

860 p = _10_0**ndigits

861 x = int(x * p) / p

862 return x

863

864

865 def unroll180(lon1, lon2, wrap=True):

866 '''Unroll longitudinal delta and wrap longitude in degrees.

867

868 @arg lon1: Start longitude (C{degrees}).

869 @arg lon2: End longitude (C{degrees}).

870 @kwarg wrap: Wrap and unroll to the M{(-180..+180]} range (C{bool}).

871

872 @return: 2-Tuple C{(B{lon2}-B{lon1}, B{lon2})} unrolled (C{degrees},

873 C{degrees}).

874

875 @see: Capability C{LONG_UNROLL} in U{GeographicLib

876 <https://GeographicLib.SourceForge.io/C++/doc/python/interface.html#outmask>}.

877 '''

878 d = lon2 - lon1

879 if wrap and fabs(d) > _180_0:

880 u = _wrap(d, _180_0, _360_0)

881 if u != d:

882 return u, lon1 + u

883 return d, lon2

884

885

886 def _unrollon(p1, p2): # unroll180 == .karney._unroll2

887 '''(INTERNAL) Wrap, unroll and replace longitude if different.

888 '''

889 _, lon = unroll180(p1.lon, p2.lon, wrap=True)

890 if fabs(lon - p2.lon) > EPS:

891 p2 = p2.dup(lon=wrap180(lon))

892 return p2

893

894

895 def unrollPI(rad1, rad2, wrap=True):

896 '''Unroll longitudinal delta and wrap longitude in radians.

897

898 @arg rad1: Start longitude (C{radians}).

899 @arg rad2: End longitude (C{radians}).

900 @kwarg wrap: Wrap and unroll to the M{(-PI..+PI]} range (C{bool}).

901

902 @return: 2-Tuple C{(B{rad2}-B{rad1}, B{rad2})} unrolled (C{radians},

903 C{radians}).

904

905 @see: Capability C{LONG_UNROLL} in U{GeographicLib

906 <https://GeographicLib.SourceForge.io/C++/doc/python/interface.html#outmask>}.

907 '''

908 r = rad2 - rad1

909 if wrap and fabs(r) > PI:

910 u = _wrap(r, PI, PI2)

911 if u != r:

912 return u, rad1 + u

913 return r, rad2

914

915

916 def _valueError(where, x, **kwds):

917 '''(INTERNAL) Return a C{_ValueError}.

918 '''

919 x = _MODS.streprs.Fmt.PAREN(where.__name__, x)

920 return _MODS.errors._ValueError(x, **kwds)

921

922

923 def _wrap(angle, wrap, modulo):

924 '''(INTERNAL) Angle wrapper M{((wrap-modulo)..+wrap]}.

925

926 @arg angle: Angle (C{degrees}, C{radians} or C{grades}).

927 @arg wrap: Range (C{degrees}, C{radians} or C{grades}).

928 @arg modulo: Upper limit (360 C{degrees}, PI2 C{radians} or 400 C{grades}).

929

930 @return: The B{C{angle}}, wrapped (C{degrees}, C{radians} or C{grades}).

931 '''

932 a = float(angle)

933 if not (wrap - modulo) <= a < wrap:

934 # math.fmod(-1.5, 3.14) == -1.5, but -1.5 % 3.14 == 1.64

935 # math.fmod(-1.5, 360) == -1.5, but -1.5 % 360 == 358.5

936 a %= modulo

937 if a > wrap:

938 a -= modulo

939 return a

940

941

942 def wrap90(deg):

943 '''Wrap degrees to M{[-270..+90]}.

944

945 @arg deg: Angle (C{degrees}).

946

947 @return: Degrees, wrapped (C{degrees90}).

948 '''

949 return _wrap(deg, _90_0, _360_0)

950

951

952 def wrap180(deg):

953 '''Wrap degrees to M{[-180..+180]}.

954

955 @arg deg: Angle (C{degrees}).

956

957 @return: Degrees, wrapped (C{degrees180}).

958 '''

959 return _wrap(deg, _180_0, _360_0)

960

961

962 def wrap360(deg): # see .streprs._umod_360

963 '''Wrap degrees to M{[0..+360)}.

964

965 @arg deg: Angle (C{degrees}).

966

967 @return: Degrees, wrapped (C{degrees360}).

968 '''

969 return _wrap(deg, _360_0, _360_0)

970

971

972 def wrapPI(rad):

973 '''Wrap radians to M{[-PI..+PI]}.

974

975 @arg rad: Angle (C{radians}).

976

977 @return: Radians, wrapped (C{radiansPI}).

978 '''

979 return _wrap(rad, PI, PI2)

980

981

982 def wrapPI2(rad):

983 '''Wrap radians to M{[0..+2PI)}.

984

985 @arg rad: Angle (C{radians}).

986

987 @return: Radians, wrapped (C{radiansPI2}).

988 '''

989 return _wrap(rad, PI2, PI2)

990

991

992 def wrapPI_2(rad):

993 '''Wrap radians to M{[-3PI/2..+PI/2]}.

994

995 @arg rad: Angle (C{radians}).

996

997 @return: Radians, wrapped (C{radiansPI_2}).

998 '''

999 return _wrap(rad, PI_2, PI2)

1000

1001

1002 def yard2m(yards):

1003 '''Convert I{UK} yards to meter.

1004

1005 @arg yards: Value in yards (C{scalar}).

1006

1007 @return: Value in C{meter} (C{float}).

1008

1009 @raise ValueError: Invalid B{C{yards}}.

1010 '''

1011 return Float(yards=yards) * _M_YARD_UK

1012

1013# **) MIT License

1014#

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

1016#

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

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

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

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

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

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

1023#

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

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

1026#

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

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

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

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

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

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

1033# OTHER DEALINGS IN THE SOFTWARE.