Coverage for pygeodesy/utily.py: 96%

247 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-04-21 13:14 -0400

1 

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

3 

4u'''Various utility functions. 

5 

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

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

8U{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 

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

12 

13from pygeodesy.basics import copysign0, isint 

14from 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 

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

19from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS 

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

21 

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

23 

24__all__ = _ALL_LAZY.utily 

25__version__ = '23.04.14' 

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 

42def 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 

48def 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 

61def 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 

74def 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 

80def 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 

88def 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 

99def 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 

124def 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 

136def 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 

158def 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 

174def 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 

191def 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 

207def 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 

224def 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 

234def 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 

244def 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 

254def 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 

264def 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 

291def 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 

306def 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 

325def 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 

337def 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 

347def 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 

357def 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 

367def 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 

377def 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 

389def 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 

401def 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 

426def 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 

441def 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 

462def 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 

474def 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 

486def 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 

498def 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 

524def 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 

536def 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 

550def 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 

562def 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 

574def 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 

601def _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 

608def 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 

618def 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 

628def 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 

638def _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 

655def 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 

679def 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 

694def 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 

724def SinCos2(x): 

725 '''Get C{sin} and C{cos} of I{typed} angle. 

726 

727 @arg x: Angle (L{Degrees}, L{Radians} or C{radians}). 

728 

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

730 ''' 

731 return sincos2d(x) if isinstance(x, Degrees) else ( 

732 sincos2(x) if isinstance(x, Radians) else 

733 sincos2(float(x))) # assume C{radians} 

734 

735 

736def sincos2d_(*degs): 

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

738 

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

740 

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

742 

743 @see: Function L{sincos2d}. 

744 ''' 

745 for d in degs: 

746 s, c = sincos2d(d) 

747 yield s 

748 yield c 

749 

750 

751def sincostan3(rad): 

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

753 

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

755 

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

757 

758 @see: Function L{sincos2}. 

759 ''' 

760 rad %= PI2 # see ._wrap comments 

761 if rad: 

762 s, c = sincos2(rad) 

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

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

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

766 c = _1_0 

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

768 return s, c, t 

769 

770 

771def SM2m(sm): 

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

773 

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

775 

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

777 

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

779 ''' 

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

781 

782 

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

784 '''Compute the tangent of half angle. 

785 

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

787 @kwarg semi: Angle or edge name and index 

788 for semi-circular error. 

789 

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

791 

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

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

794 ''' 

795 # .formy.excessKarney_, .sphericalTrigonometry.areaOf 

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

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

798 break 

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

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

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

802 

803 return tan(rad * _0_5) 

804 

805 

806def tand(deg, **error_kwds): 

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

808 

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

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

811 

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

813 

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

815 ''' 

816 s, c = sincos2d(deg) 

817 if isnear0(c): 

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

819 return s / c 

820 

821 

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

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

824 

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

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

827 

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

829 

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

831 ''' 

832 for d in degs: 

833 yield tand(d, **error_kwds) 

834 

835 

836def tanPI_2_2(rad): 

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

838 

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

840 

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

842 ''' 

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

844 

845 

846def toise2m(toises): 

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

848 

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

850 

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

852 

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

854 

855 @see: Function L{fathom2m}. 

856 ''' 

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

858 

859 

860def truncate(x, ndigits=None): 

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

862 

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

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

865 aka I{precision}. 

866 

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

868 

869 @see: Python function C{round}. 

870 ''' 

871 if isint(ndigits): 

872 p = _10_0**ndigits 

873 x = int(x * p) / p 

874 return x 

875 

876 

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

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

879 

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

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

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

883 

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

885 C{degrees}). 

886 

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

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

889 ''' 

890 d = lon2 - lon1 

891 if wrap and fabs(d) > _180_0: 

892 u = _wrap(d, _180_0, _360_0) 

893 if u != d: 

894 return u, lon1 + u 

895 return d, lon2 

896 

897 

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

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

900 ''' 

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

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

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

904 return p2 

905 

906 

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

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

909 

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

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

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

913 

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

915 C{radians}). 

916 

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

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

919 ''' 

920 r = rad2 - rad1 

921 if wrap and fabs(r) > PI: 

922 u = _wrap(r, PI, PI2) 

923 if u != r: 

924 return u, rad1 + u 

925 return r, rad2 

926 

927 

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

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

930 ''' 

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

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

933 

934 

935def _wrap(angle, wrap, modulo): 

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

937 

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

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

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

941 

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

943 ''' 

944 a = float(angle) 

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

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

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

948 a %= modulo 

949 if a > wrap: 

950 a -= modulo 

951 return a 

952 

953 

954def wrap90(deg): 

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

956 

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

958 

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

960 ''' 

961 return _wrap(deg, _90_0, _360_0) 

962 

963 

964def wrap180(deg): 

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

966 

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

968 

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

970 ''' 

971 return _wrap(deg, _180_0, _360_0) 

972 

973 

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

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

976 

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

978 

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

980 ''' 

981 return _wrap(deg, _360_0, _360_0) 

982 

983 

984def wrapPI(rad): 

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

986 

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

988 

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

990 ''' 

991 return _wrap(rad, PI, PI2) 

992 

993 

994def wrapPI2(rad): 

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

996 

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

998 

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

1000 ''' 

1001 return _wrap(rad, PI2, PI2) 

1002 

1003 

1004def wrapPI_2(rad): 

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

1006 

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

1008 

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

1010 ''' 

1011 return _wrap(rad, PI_2, PI2) 

1012 

1013 

1014def yard2m(yards): 

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

1016 

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

1018 

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

1020 

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

1022 ''' 

1023 return Float(yards=yards) * _M_YARD_UK 

1024 

1025# **) MIT License 

1026# 

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

1028# 

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

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

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

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

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

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

1035# 

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

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

1038# 

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

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

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

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

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

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

1045# OTHER DEALINGS IN THE SOFTWARE.