Coverage for pygeodesy/utily.py: 94%

1

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

3

4 u'''Various utility functions.

5

6 After I{Karney}'s C++ U{Math<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}

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

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

9 and published under the same MIT Licence**.

10'''

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

12 from __future__ import division as _; del _ # noqa: E702 ;

13

14 from pygeodesy.basics import _copysign, isinstanceof, isint, isstr

15 from pygeodesy.constants import EPS, EPS0, NAN, PI, PI2, PI_2, PI_4, PI_6, R_M, \

16 _M_KM, _M_NM, _M_SM, _SQRT2_2 as _COS_45, \

17 _SQRT3_2 as _COS_30, _0_0, _0_5, _1_0, _N_1_0, \

18 _10_0, _90_0, _180_0, _360_0, _copysign_0_0, \

19 _copysignINF, _float, _isfinite, isnan, isnear0, \

20 _over_1, _umod_360, _umod_PI2, OVERFLOW

21 from pygeodesy.errors import _ValueError, _xkwds, _xkwds_get

22 from pygeodesy.internals import _Enum, _passargs, typename

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

24 from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS

25 from pygeodesy.units import Degrees, Degrees_, Feet, Float, Lam, Lamd, \

26 Meter, Meter2, Radians # Radians_

27

28 from math import acos, asin, asinh, atan2 as _atan2, cos, degrees, fabs, \

29 radians, sin, sinh, tan as _tan # pow

30

31 __all__ = _ALL_LAZY.utily

32 __version__ = '26.01.14'

33

34 _SIN_30 = _0_5

35 _SIN_45 = _COS_45

36

37 _G = _Enum( # grades per ...

38 DEG = _float( 400.0 / _360_0), # degree

39 RAD = _float( 400.0 / PI2)) # radian

40

41 _M = _Enum( # meter per ...

42 ACRE = _float( 4046.8564224), # acre, chain2m(1) * furlong2m(1), squared

43 CHAIN = _float( 20.1168), # yard2m(1) * 22

44 FATHOM = _float( 1.8288), # yard2m(1) * 2 or _M.NM * 1e-3

45 FOOT = _float( 0.3048), # Int'l foot, 1 / 3.280_839_895_0131 == (254 * 12) / 10_000

46 FOOT_GE = _float( 0.31608), # German Fuss, 1 / 3.163_756_011_1364

47 FOOT_FR = _float( 0.3248406), # French Pied-du-Roi or pied, 1 / 3.078_432_929_8739

48 FOOT_US = _float( 0.3048006096012192), # US Survey foot, 1_200 / 3_937

49 FURLONG = _float( 201.168), # furlong, 220 * yard2m(1) = 10 * m2chain(1)

50 HA = _float(10000.0), # hectare, 100 * 100, squared

51 KM = _M_KM, # kilo meter

52 NM = _M_NM, # nautical mile

53 SM = _M_SM, # statute mile

54 TOISE = _float( 1.9490436), # French toise, 6 pieds = 6 / 3.078_432_929_8739

55 YARD_UK = _float( 0.9144)) # yard, 254 * 12 * 3 / 10_000 = 3 * _M.FOOT

56

57

58 def _abs1nan(x):

59 '''(INTERNAL) Bracket C{-1 < x < 1 or isnan(x)}.

60 '''

61 return _N_1_0 < x < _1_0 or isnan(x)

62

63

64 def acos1(x):

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

66 '''

67 return acos(x) if _abs1nan(x) else (PI if x < 0 else _0_0)

68

69

70 def acre2ha(acres):

71 '''Convert acres to hectare.

72

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

74

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

76

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

78 '''

79 return m2ha(acre2m2(acres))

80

81

82 def acre2m2(acres):

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

84

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

86

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

88

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

90 '''

91 return Meter2(Float(acres=acres) * _M.ACRE)

92

93

94 def agdf(phi):

95 '''Inverse U{Gudermannian function

96 <https://WikiPedia.org/wiki/Gudermannian_function>}.

97

98 @arg phi: Angle (C{radians}).

99

100 @return: Gudermannian (psi, C{float}).

101

102 @see: Function L{gdf}.

103 '''

104 return asinh(tan(phi))

105

106

107 def asin1(x):

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

109 '''

110 return asin(x) if _abs1nan(x) else _copysign(PI_2, x)

111

112

113 def atan1(y, x=_1_0):

114 '''Return C{atan(B{y} / B{x})} angle in C{radians} M{[-PI/2..+PI/2]}

115 using C{atan2} for consistency and to avoid C{ZeroDivisionError}.

116 '''

117 return _atan1u(y, x, atan2)

118

119

120 def atan1d(y, x=_1_0):

121 '''Return C{atan(B{y} / B{x})} angle in C{degrees} M{[-90..+90]}

122 using C{atan2d} for consistency and to avoid C{ZeroDivisionError}.

123

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

125 '''

126 return _atan1u(y, x, atan2d)

127

128

129 def _atan1u(y, x, _2u):

130 '''(INTERNAL) Helper for functions C{atan1} and C{atan1d}.

131 '''

132 if x < 0:

133 x = -x

134 y = -y

135 return _2u(y, x or _0_0)

136

137

138 atan2 = _atan2

139'''Return C{atan2(B{y}, B{x})} in radians M{[-PI..+PI]}.

140

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

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

143'''

144

145

146 def atan2b(y, x):

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

148

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

150 '''

151 b = atan2d(y, x)

152 if b < 0:

153 b += _360_0

154 return b or _0_0 # unsigned-0

155

156

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

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

159 optionally I{reversed} (by 180 degrees for C{azimuth}s).

160

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

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

163 '''

164 d = degrees(_atan2(y, x)) # preserves signed-0

165 return _azireversed(d) if reverse else d

166

167

168 def _azireversed(azi): # in .rhumbBase

169 '''(INTERNAL) Return the I{reverse} B{C{azi}} in degrees M{[-180..+180]}.

170 '''

171 return azi - _copysign(_180_0, azi)

172

173

174 def chain2m(chains):

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

176

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

178

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

180

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

182 '''

183 return Meter(Float(chains=chains) * _M.CHAIN)

184

185

186 def circle4(earth, lat):

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

188

189 @arg earth: The earth radius (C{meter}) or an ellipsoid or datum

190 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).

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

192

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

194

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

196 L{rangerrors<pygeodesy.rangerrors>} is C{True}.

197

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

199

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

201 '''

202 E = _MODS.datums._earth_ellipsoid(earth)

203 return E.circle4(lat)

204

205

206 def _circle4radius(earth, lat, **radius):

207 '''(INTERNAL) Get C{circle4(earth, lat).radius}.

208 '''

209 e = _xkwds_get(radius, radius=earth) if radius else earth

210 return e if e is R_M and not lat else circle4(e, lat).radius

211

212

213 def cot(rad, **raiser_kwds):

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

215

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

217 @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid

218 ValueErrors and optional, additional

219 ValueError keyword argments.

220

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

222

223 @raise Error: If L{pygeodesy.isnear0}C{(sin(B{rad})}.

224 '''

225 try:

226 return _cotu(*sincos2(rad))

227 except ZeroDivisionError:

228 return _nonfinite(cot, rad, **raiser_kwds)

229

230

231 def cot_(*rads, **raiser_kwds):

232 '''Yield the C{cotangent} of angle(s) in C{radians}.

233

234 @arg rads: One or more angles (each in C{radians}).

235

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

237

238 @see: Function L{pygeodesy.cot} for further details.

239 '''

240 try:

241 for r in rads:

242 yield _cotu(*sincos2(r))

243 except ZeroDivisionError:

244 yield _nonfinite(cot_, r, **raiser_kwds)

245

246

247 def cotd(deg, **raiser_kwds):

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

249

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

251 @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid

252 ValueErrors and optional, additional

253 ValueError keyword argments.

254

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

256

257 @raise Error: If L{pygeodesy.isnear0}C{(sin(B{deg})}.

258 '''

259 try:

260 return _cotu(*sincos2d(deg))

261 except ZeroDivisionError:

262 return _nonfinite(cotd, deg, **raiser_kwds)

263

264

265 def cotd_(*degs, **raiser_kwds):

266 '''Yield the C{cotangent} of angle(s) in C{degrees}.

267

268 @arg degs: One or more angles (each in C{degrees}).

269

270 @return: Yield C{cotd(B{deg})} for each angle.

271

272 @see: Function L{pygeodesy.cotd} for further details.

273 '''

274 try:

275 for d in degs:

276 yield _cotu(*sincos2d(d))

277 except ZeroDivisionError:

278 yield _nonfinite(cotd_, d, **raiser_kwds)

279

280

281 def _cotu(s, c):

282 '''(INTERNAL) Helper for functions C{cot}, C{cotd}, C{cot_} and C{cotd_}.

283 '''

284 return _tanu(c, s)

285

286

287 def degrees90(rad):

288 '''Convert radians to degrees and wrap M{[-90..+90)}.

289

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

291

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

293 '''

294 return wrap90(degrees(rad))

295

296

297 def degrees180(rad):

298 '''Convert radians to degrees and wrap M{[-180..+180)}.

299

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

301

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

303 '''

304 return wrap180(degrees(rad))

305

306

307 def degrees360(rad):

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

309

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

311

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

313 '''

314 return _umod_360(degrees(rad))

315

316

317 def degrees2grades(deg):

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

319

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

321

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

323 '''

324 return Float(grades=Degrees(deg) * _G.DEG)

325

326

327 def degrees2m(deg, earth=R_M, lat=0, **radius):

328 '''Convert an angle to a distance along the equator or along a parallel

329 at (geodetic) latitude.

330

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

332 @arg earth: The earth radius (C{meter}) or an ellipsoid or datum

333 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).

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

335 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.

336

337 @return: Distance (C{meter}, same units as B{C{earth}} or polar and

338 equatorial radii) or C{0.0} for near-polar B{C{lat}}.

339

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

341 L{rangerrors<pygeodesy.rangerrors>} is C{True}.

342

343 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.

344

345 @raise ValueError: Invalid B{C{deg}}, B{C{earth}} or B{C{lat}}.

346

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

348 '''

349 m = _circle4radius(earth, lat, **radius)

350 return _Radians2m(Lamd(deg=deg, clip=0), m)

351

352

353 def fathom2m(fathoms):

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

355

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

357

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

359

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

361

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

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

364 '''

365 return Meter(Float(fathoms=fathoms) * _M.FATHOM)

366

367

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

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

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

371

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

373 @kwarg usurvey: If C{True}, convert I{US Survey} foot else ...

374 @kwarg pied: If C{True}, convert French I{pied-du-Roi} else ...

375 @kwarg fuss: If C{True}, convert German I{Fuss}, otherwise

376 I{International} foot to C{meter}.

377

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

379

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

381 '''

382 return Meter(Feet(feet) * (_M.FOOT_US if usurvey else

383 (_M.FOOT_FR if pied else

384 (_M.FOOT_GE if fuss else _M.FOOT))))

385

386

387 def furlong2m(furlongs):

388 '''Convert a furlong to meter.

389

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

391

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

393

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

395 '''

396 return Meter(Float(furlongs=furlongs) * _M.FURLONG)

397

398

399 def gdf(psi):

400 '''U{Gudermannian function

401 <https://WikiPedia.org/wiki/Gudermannian_function>}.

402

403 @arg psi: Gudermannian (C{float}).

404

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

406

407 @see: Function L{agdf}.

408 '''

409 return atan1(sinh(psi))

410

411

412 def grades(rad):

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

414

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

416

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

418 '''

419 return Float(grades=Radians(rad) * _G.RAD)

420

421

422 def grades400(rad):

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

424

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

426

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

428 '''

429 return Float(grades400=wrapPI2(rad) * _G.RAD)

430

431

432 def grades2degrees(gon):

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

434

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

436

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

438 '''

439 return Degrees(Float(gon=gon) / _G.DEG)

440

441

442 def grades2radians(gon):

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

444

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

446

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

448 '''

449 return Radians(Float(gon=gon) / _G.RAD)

450

451

452 def ha2acre(ha):

453 '''Convert hectare to acre.

454

455 @arg ha: Value in hectare (C{scalar}).

456

457 @return: Value in acres (C{float}).

458

459 @raise ValueError: Invalid B{C{ha}}.

460 '''

461 return m2acre(ha2m2(ha))

462

463

464 def ha2m2(ha):

465 '''Convert hectare to I{square} meter.

466

467 @arg ha: Value in hectare (C{scalar}).

468

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

470

471 @raise ValueError: Invalid B{C{ha}}.

472 '''

473 return Meter2(Float(ha=ha) * _M.HA)

474

475

476 def hav(rad):

477 '''Return the U{haversine<https://WikiPedia.org/wiki/Haversine_formula>} of an angle.

478

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

480

481 @return: C{sin(B{rad} / 2)**2}.

482 '''

483 return sin(rad * _0_5)**2

484

485

486 def km2m(km):

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

488

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

490

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

492

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

494 '''

495 return Meter(Float(km=km) * _M.KM)

496

497

498 def _loneg(lon):

499 '''(INTERNAL) "Complement" of C{lon}.

500 '''

501 return _180_0 - lon

502

503

504 def m2acre(meter2):

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

506

507 @arg meter2: Value in C{meter^2} (C{scalar}).

508

509 @return: Value in acres (C{float}).

510

511 @raise ValueError: Invalid B{C{meter2}}.

512 '''

513 return Float(acre=Meter2(meter2) / _M.ACRE)

514

515

516 def m2chain(meter):

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

518

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

520

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

522

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

524 '''

525 return Float(chain=Meter(meter) / _M.CHAIN) # * 0.049_709_695_378_986_715

526

527

528 def m2degrees(distance, earth=R_M, lat=0, **radius):

529 '''Convert a distance to an angle along the equator or along a parallel

530 at (geodetic) latitude.

531

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

533 @kwarg earth: Mean earth radius (C{meter}) or an ellipsoid or datum

534 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).

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

536 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.

537

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

539

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

541 L{rangerrors<pygeodesy.rangerrors>} is C{True}.

542

543 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.

544

545 @raise ValueError: Invalid B{C{distance}}, B{C{earth}} or B{C{lat}}.

546

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

548 '''

549 return degrees(m2radians(distance, earth=earth, lat=lat, **radius))

550

551

552 def m2fathom(meter):

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

554

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

556

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

558

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

560

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

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

563 '''

564 return Float(fathom=Meter(meter) / _M.FATHOM) # * 0.546_806_649

565

566

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

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

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

570

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

572 @kwarg usurvey: If C{True}, convert to I{US Survey} foot else ...

573 @kwarg pied: If C{True}, convert to French I{pied-du-Roi} else ...

574 @kwarg fuss: If C{True}, convert to German I{Fuss}, otherwise to

575 I{International} foot.

576

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

578

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

580 '''

581 # * 3.280_833_333_333_3333, US Survey 3_937 / 1_200

582 # * 3.280_839_895_013_1235, Int'l 10_000 / (254 * 12)

583 return Float(feet=Meter(meter) / (_M.FOOT_US if usurvey else

584 (_M.FOOT_FR if pied else

585 (_M.FOOT_GE if fuss else _M.FOOT))))

586

587

588 def m2furlong(meter):

589 '''Convert meter to furlongs.

590

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

592

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

594

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

596 '''

597 return Float(furlong=Meter(meter) / _M.FURLONG) # * 0.004_970_969_54

598

599

600 def m2ha(meter2):

601 '''Convert I{square} meter to hectare.

602

603 @arg meter2: Value in C{meter^2} (C{scalar}).

604

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

606

607 @raise ValueError: Invalid B{C{meter2}}.

608 '''

609 return Float(ha=Meter2(meter2) / _M.HA)

610

611

612 def m2km(meter):

613 '''Convert meter to kilo meter (Km).

614

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

616

617 @return: Value in Km (C{float}).

618

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

620 '''

621 return Float(km=Meter(meter) / _M.KM)

622

623

624 def m2NM(meter):

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

626

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

628

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

630

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

632 '''

633 return Float(NM=Meter(meter) / _M.NM) # * 5.399_568_04e-4

634

635

636 def m2radians(distance, earth=R_M, lat=0, **radius):

637 '''Convert a distance to an angle along the equator or along a parallel

638 at (geodetic) latitude.

639

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

641 @kwarg earth: Mean earth radius (C{meter}) or an ellipsoid or datum

642 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).

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

644 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.

645

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

647

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

649 L{rangerrors<pygeodesy.rangerrors>} is C{True}.

650

651 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.

652

653 @raise ValueError: Invalid B{C{distance}}, B{C{earth}} or B{C{lat}}.

654

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

656 '''

657 m = _circle4radius(earth, lat, **radius)

658 return _copysign_0_0(distance) if m < EPS0 else \

659 Radians(Float(distance=distance) / m)

660

661

662 def m2SM(meter):

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

664

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

666

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

668

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

670 '''

671 return Float(SM=Meter(meter) / _M.SM) # * 6.213_699_49e-4 == 1 / 1_609.344

672

673

674 def m2toise(meter):

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

676

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

678

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

680

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

682

683 @see: Function L{m2fathom}.

684 '''

685 return Float(toise=Meter(meter) / _M.TOISE) # * 0.513_083_632_632_119

686

687

688 def m2yard(meter):

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

690

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

692

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

694

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

696 '''

697 return Float(yard=Meter(meter) / _M.YARD_UK) # * 1.093_613_298_337_707_8

698

699

700 def NM2m(nm):

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

702

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

704

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

706

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

708 '''

709 return Meter(Float(nm=nm) * _M.NM)

710

711

712 def _nonfinite(where, x, raiser=True, **kwds): # PYCHOK no cover

713 '''(INTERNAL) Raise a C{_ValueError} or return C{INF} or C{NINF}.

714 '''

715 if raiser:

716 t = typename(where)

717 t = _MODS.streprs.Fmt.PAREN(t, x)

718 raise _ValueError(t, **kwds)

719 return _copysignINF(x)

720

721

722 def radians2m(rad, earth=R_M, lat=0, **radius):

723 '''Convert an angle to a distance along the equator or along a parallel

724 at (geodetic) latitude.

725

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

727 @kwarg earth: Mean earth radius (C{meter}) or an ellipsoid or datum

728 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).

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

730 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.

731

732 @return: Distance (C{meter}, same units as B{C{earth}} or polar and

733 equatorial radii) or C{0.0} for near-polar B{C{lat}}.

734

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

736 L{rangerrors<pygeodesy.rangerrors>} is C{True}.

737

738 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.

739

740 @raise ValueError: Invalid B{C{rad}}, B{C{earth}} or B{C{lat}}.

741

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

743 '''

744 m = _circle4radius(earth, lat, **radius)

745 return _Radians2m(Lam(rad=rad, clip=0), m)

746

747

748 def _Radians2m(rad, m):

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

750 '''

751 return _copysign_0_0(rad) if m < EPS0 else (rad * m)

752

753

754 def radiansPI(deg):

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

756

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

758

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

760 '''

761 return wrapPI(radians(deg))

762

763

764 def radiansPI2(deg):

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

766

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

768

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

770 '''

771 return _umod_PI2(radians(deg))

772

773

774 def radiansPI_2(deg):

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

776

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

778

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

780 '''

781 return wrapPI_2(radians(deg))

782

783

784 def _sin0cos2(q, r, sign):

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

786 C{sin} zero I{signed} with B{C{sign}}, like Karney's U{Math.sind and .cosd

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

788 '''

789 q &= 3 # -1: 3, -2: 2, -3: 1, -4: 0 ...

790 if r < PI_2: # Quarter turn

791 a = fabs(r)

792 if a:

793 s = sin(r)

794 if a == PI_4:

795 s, c = _copysign(_SIN_45, s), _COS_45

796 elif a == PI_6:

797 s, c = _copysign(_SIN_30, s), _COS_30

798 else:

799 c = cos(r)

800 else:

801 s, c = _0_0, _1_0

802 else:

803 s, c = _1_0, _0_0

804 t = s, c, -s, -c, s

805 s = t[q] or _copysign_0_0(sign)

806 c = t[q + 1] or _0_0

807 return s, c

808

809

810 def SinCos2(x, unit=Radians):

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

812

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

814 @kwarg unit: The C{B{x}} unit (L{Degrees}, L{Radians}).

815

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

817 '''

818 return sincos2d(x) if unit is Degrees or unit is degrees or isinstanceof(x, Degrees, Degrees_) else (

819# sincos2(x) if unit is Radians or unit is radians or isinstanceof(x, Radians, Radians_) else

820 sincos2(Radians(x))) # assume C{radians}

821

822

823 def sincos2(rad):

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

825

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

827

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

829

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

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

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

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

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

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

836 '''

837 if _isfinite(rad):

838 q, r = divmod(rad, PI_2)

839 t = _sin0cos2(int(q), r, rad)

840 else:

841 t = NAN, NAN

842 return t

843

844

845 def sincos2_(*rads):

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

847

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

849

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

851

852 @see: Function L{sincos2}.

853 '''

854 for r in rads:

855 s, c = sincos2(r)

856 yield s

857 yield c

858

859

860 def sincos2d(deg, adeg=_0_0):

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

862

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

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

865

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

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

868

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

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

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

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

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

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

875 '''

876 if _isfinite(deg):

877 q, d = divmod(deg, _90_0)

878 if adeg:

879 d = _MODS.karney._around(d + adeg)

880 t = _sin0cos2(int(q), radians(d), deg)

881 else:

882 t = NAN, NAN

883 return t

884

885

886 def sincos2d_(*degs):

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

888

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

890

891 @return: Yield C{sind(B{deg})} and C{cosd(B{deg})} for each angle.

892

893 @see: Function L{sincos2d}.

894 '''

895 for d in degs:

896 s, c = sincos2d(d)

897 yield s

898 yield c

899

900

901 def sincostan3(rad):

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

903

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

905

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

907

908 @see: Function L{sincos2}.

909 '''

910 return _sincostan3(*sincos2(float(rad)))

911

912

913 def _sincostan3(s, c):

914 '''(INTERNAL) Helper for C{sincostan3} and C{sincostan3d}.

915 '''

916 return s, c, _over_1(s, c) # _tanu(s, c, raiser=False)

917

918

919 def sincostan3d(deg):

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

921

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

923

924 @return: 3-Tuple (C{sind(B{deg})}, C{cosd(B{deg})}, C{tand(B{deg})}).

925

926 @see: Function L{sincos2d}.

927 '''

928 return _sincostan3(*sincos2d(float(deg)))

929

930

931 def SM2m(sm):

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

933

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

935

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

937

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

939 '''

940 return Meter(Float(sm=sm) * _M.SM)

941

942

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

944 '''Compute the tangent of half angle.

945

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

947 @kwarg semi: Angle or edge name and index

948 for semi-circular error.

949

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

951

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

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

954 '''

955 # .formy.excessKarney_, .sphericalTrigonometry.areaOf

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

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

958 break

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

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

961 raise _ValueError(n, rad, txt=_semi_circular_)

962

963 return _tan(rad * _0_5) if _isfinite(rad) else NAN

964

965

966 def tan(rad, **raiser_kwds):

967 '''Return the C{tangent} of an angle in C{radians}.

968

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

970 @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid

971 ValueErrors and optional, additional

972 ValueError keyword argments.

973

974 @return: C{tan(B{rad})}.

975

976 @raise Error: If L{pygeodesy.isnear0}C{(cos(B{rad})}.

977 '''

978 try:

979 return _tanu(*sincos2(rad))

980 except ZeroDivisionError:

981 return _nonfinite(tan, rad, **raiser_kwds)

982

983

984 def tan_(*rads, **raiser_kwds):

985 '''Yield the C{tangent} of angle(s) in C{radians}.

986

987 @arg rads: One or more angles (each in C{radians}).

988

989 @return: Yield C{tan(B{rad})} for each angle.

990

991 @see: Function L{pygeodesy.tan} for futher details.

992 '''

993 try:

994 for r in rads:

995 yield _tanu(*sincos2(r))

996 except ZeroDivisionError:

997 yield _nonfinite(tan_, r, **raiser_kwds)

998

999

1000 def tand(deg, **raiser_clamp_kwds):

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

1002

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

1004 @kwarg raiser_clamp_kwds: Use C{B{raiser}=False} to avoid

1005 ValueErrors, C{B{clamp}=}L{OVERFLOW} and

1006 optional, additional ValueError keyword

1007 argments.

1008

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

1010

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

1012 '''

1013 try:

1014 return _tanu(*sincos2d(deg))

1015 except ZeroDivisionError:

1016 return _nonfinite(tand, deg, **raiser_clamp_kwds)

1017

1018

1019 def tand_(*degs, **raiser_clamp_kwds):

1020 '''Yield the C{tangent} of angle(s) in C{degrees}.

1021

1022 @arg degs: One or more angles (each in C{degrees}).

1023

1024 @return: Yield C{tand(B{deg})} for each angle.

1025

1026 @see: Function L{pygeodesy.tand} for futher details.

1027 '''

1028 try:

1029 for d in degs:

1030 yield _tanu(*sincos2d(d), **raiser_clamp_kwds)

1031 except ZeroDivisionError:

1032 yield _nonfinite(tand_, d, **raiser_clamp_kwds)

1033

1034

1035 def tanPI_2_2(rad):

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

1037

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

1039

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

1041 '''

1042 return _tan((rad + PI_2) * _0_5) if _isfinite(rad) else (

1043 NAN if isnan(rad) else _copysign(_90_0, rad))

1044

1045

1046 def _tanu(s, c, clamp=OVERFLOW, **unused):

1047 '''(INTERNAL) Helper for functions C{_cotu}, C{sincostan3},

1048 C{sincostan3d}, C{tan}, C{tan_}, C{tand} and C{tand_}.

1049 '''

1050 if s is NAN or isnan(s):

1051 t = NAN

1052 elif isnear0(c):

1053 raise ZeroDivisionError()

1054 else:

1055 t = _over_1(s, c)

1056 if clamp:

1057 t = min(clamp, max(t, -clamp))

1058 return t

1059

1060

1061 def toise2m(toises):

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

1063

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

1065

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

1067

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

1069

1070 @see: Function L{fathom2m}.

1071 '''

1072 return Meter(Float(toises=toises) * _M.TOISE)

1073

1074

1075 def truncate(x, ndigits=None):

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

1077

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

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

1080 aka I{precision}.

1081

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

1083

1084 @see: Python function C{round}.

1085 '''

1086 if isint(ndigits):

1087 p = _10_0**ndigits

1088 x = int(x * p) / p

1089 return x

1090

1091

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

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

1094

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

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

1097 @kwarg wrap: If C{True}, wrap and unroll to the M{(-180..+180]}

1098 range (C{bool}).

1099

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

1101 C{degrees}).

1102

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

1104 <https://GeographicLib.SourceForge.io/html/python/interface.html#outmask>}.

1105 '''

1106 d = lon2 - lon1

1107 if wrap:

1108 u = wrap180(d)

1109 if u != d:

1110 return u, (lon1 + u)

1111 return d, lon2

1112

1113

1114 def _unrollon(p1, p2, wrap=False): # unroll180 == .karney._unroll2

1115 '''(INTERNAL) Wrap/normalize, unroll and replace longitude.

1116 '''

1117 lat, lon = p2.lat, p2.lon

1118 if wrap and _Wrap.normal:

1119 lat, lon = _Wrap.latlon(lat, lon)

1120 _, lon = unroll180(p1.lon, lon, wrap=True)

1121 if lat != p2.lat or fabs(lon - p2.lon) > EPS:

1122 p2 = p2.dup(lat=lat, lon=wrap180(lon))

1123 # p2 = p2.copy(); p2.latlon = lat, wrap180(lon)

1124 return p2

1125

1126

1127 def _unrollon3(p1, p2, p3, wrap=False):

1128 '''(INTERNAL) Wrap/normalize, unroll 2 points.

1129 '''

1130 w = wrap

1131 if w: # PYCHOK no cover

1132 w = _Wrap.normal

1133 p2 = _unrollon(p1, p2, wrap=w)

1134 p3 = _unrollon(p1, p3, wrap=w)

1135 p2 = _unrollon(p2, p3)

1136 return p2, p3, w # was wrapped?

1137

1138

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

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

1141

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

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

1144 @kwarg wrap: If C{True}, wrap and unroll to the M{(-PI..+PI]}

1145 range (C{bool}).

1146

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

1148 (C{radians}, C{radians}).

1149

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

1151 <https://GeographicLib.SourceForge.io/html/python/interface.html#outmask>}.

1152 '''

1153 r = rad2 - rad1

1154 if wrap:

1155 u = wrapPI(r)

1156 if u != r:

1157 return u, (rad1 + u)

1158 return r, rad2

1159

1160

1161 class _Wrap(object):

1162

1163 _normal = False # default

1164

1165 @property

1166 def normal(self):

1167 '''Get the current L{normal} setting (C{True},

1168 C{False} or C{None}).

1169 '''

1170 return self._normal

1171

1172 @normal.setter # PYCHOK setter!

1173 def normal(self, setting): # PYCHOK no cover

1174 '''Set L{normal} to C{True}, C{False} or C{None}.

1175 '''

1176 m = _MODS.formy

1177 t = {True: (m.normal, m.normal_),

1178 False: (self.wraplatlon, self.wraphilam),

1179 None: (_passargs, _passargs)}.get(setting, ())

1180 if t:

1181 self.latlon, self.philam = t

1182 self._normal = setting

1183

1184 def latlonDMS2(self, lat, lon, **DMS2_kwds): # PYCHOK no cover

1185 if isstr(lat) or isstr(lon):

1186 kwds = _xkwds(DMS2_kwds, clipLon=0, clipLat=0)

1187 lat, lon = _MODS.dms.parseDMS2(lat, lon, **kwds)

1188 return self.latlon(lat, lon)

1189

1190# def normalatlon(self, *latlon):

1191# return _MODS.formy.normal(*latlon)

1192

1193# def normalamphi(self, *philam):

1194# return _MODS.formy.normal_(*philam)

1195

1196 def wraplatlon(self, lat, lon):

1197 return wrap90(lat), wrap180(lon)

1198

1199 latlon = wraplatlon # default

1200

1201 def latlon3(self, lon1, lat2, lon2, wrap):

1202 if wrap:

1203 lat2, lon2 = self.latlon(lat2, lon2)

1204 lon21, lon2 = unroll180(lon1, lon2)

1205 else:

1206 lon21 = lon2 - lon1

1207 return lon21, lat2, lon2

1208

1209 def _latlonop(self, wrap):

1210 if wrap and self._normal is not None:

1211 return self.latlon

1212 else:

1213 return _passargs

1214

1215 def wraphilam(self, phi, lam):

1216 return wrapPI_2(phi), wrapPI(lam)

1217

1218 philam = wraphilam # default

1219

1220 def philam3(self, lam1, phi2, lam2, wrap):

1221 if wrap:

1222 phi2, lam2 = self.philam(phi2, lam2)

1223 lam21, lam2 = unrollPI(lam1, lam2)

1224 else:

1225 lam21 = lam2 - lam1

1226 return lam21, phi2, lam2

1227

1228 def _philamop(self, wrap):

1229 if wrap and self._normal is not None:

1230 return self.philam

1231 else:

1232 return _passargs

1233

1234 def point(self, ll, wrap=True): # in .points._fractional, -.PointsIter.iterate, ...

1235 '''Return C{ll} or a copy, I{normalized} or I{wrap}'d.

1236 '''

1237 if wrap and self._normal is not None:

1238 lat, lon = ll.latlon

1239 if fabs(lon) > _180_0 or fabs(lat) > _90_0:

1240 _n = self.latlon

1241 ll = ll.copy(name=typename(_n))

1242 ll.latlon = _n(lat, lon)

1243 return ll

1244

1245 _Wrap = _Wrap() # PYCHOK singleton

1246

1247

1248# def _wrap(angle, wrap, modulo):

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

1250#

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

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

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

1254#

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

1256# '''

1257# a = float(angle)

1258# if not (wrap - modulo) <= a < wrap:

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

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

1261# a %= modulo

1262# if a > wrap:

1263# a -= modulo

1264# return a

1265

1266

1267 def wrap90(deg):

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

1269

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

1271

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

1273 '''

1274 return _wrapu(wrap180(deg), _180_0, _90_0)

1275

1276

1277 def wrap180(deg):

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

1279

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

1281

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

1283 '''

1284 d = float(deg)

1285 w = _umod_360(d)

1286 if w > _180_0:

1287 w -= _360_0

1288 elif d < 0 and w == _180_0:

1289 w = -w

1290 return w

1291

1292

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

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

1295

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

1297

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

1299 '''

1300 return _umod_360(float(deg))

1301

1302

1303 def wrapPI(rad):

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

1305

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

1307

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

1309 '''

1310 r = float(rad)

1311 w = _umod_PI2(r)

1312 if w > PI:

1313 w -= PI2

1314 elif r < 0 and w == PI:

1315 w = -PI

1316 return w

1317

1318

1319 def wrapPI2(rad):

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

1321

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

1323

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

1325 '''

1326 return _umod_PI2(float(rad))

1327

1328

1329 def wrapPI_2(rad):

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

1331

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

1333

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

1335 '''

1336 return _wrapu(wrapPI(rad), PI, PI_2)

1337

1338

1339# def wraplatlon(lat, lon):

1340# '''Both C{wrap90(B{lat})} and C{wrap180(B{lon})}.

1341# '''

1342# return wrap90(lat), wrap180(lon)

1343

1344

1345 def wrap_normal(*normal):

1346 '''Define the operation for the keyword argument C{B{wrap}=True},

1347 across L{pygeodesy}: I{wrap}, I{normalize} or I{no-op}. For

1348 backward compatibility, the default is I{wrap}.

1349

1350 @arg normal: If C{True}, I{normalize} lat- and longitude using

1351 L{normal} or L{normal_}, if C{False}, I{wrap} the

1352 lat- and longitude individually by L{wrap90} or

1353 L{wrapPI_2} respectively L{wrap180}, L{wrapPI} or

1354 if C{None}, leave lat- and longitude I{unchanged}.

1355 To get the current setting, do not specify.

1356

1357 @return: The previous L{wrap_normal} setting (C{bool} or C{None}).

1358 '''

1359 t = _Wrap.normal

1360 if normal:

1361 _Wrap.normal = normal[0]

1362 return t

1363

1364

1365# def wraphilam(phi, lam,):

1366# '''Both C{wrapPI_2(B{phi})} and C{wrapPI(B{lam})}.

1367# '''

1368# return wrapPI_2(phi), wrapPI(lam)

1369

1370

1371 def _wrapu(w, H, Q):

1372 '''(INTERNAL) Helper for functions C{wrap180} and C{wrapPI}.

1373 '''

1374 return (w - H) if w > Q else ((w + H) if w < (-Q) else w)

1375

1376

1377 def yard2m(yards):

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

1379

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

1381

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

1383

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

1385 '''

1386 return Float(yards=yards) * _M.YARD_UK

1387

1388# **) MIT License

1389#

1390# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved.

1391#

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

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

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

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

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

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

1398#

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

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

1401#

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

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

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

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

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

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

1408# OTHER DEALINGS IN THE SOFTWARE.