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 _ # PYCHOK semicolon

13

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

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

16 _M_KM, _M_NM, _M_SM, _0_0, _1__90, _0_5, _2__PI, \

17 _1_0, _N_1_0, _10_0, _90_0, _180_0, _360_0, \

18 _copysign_0_0, _float, _isfinite, isnan, isnear0, \

19 _over, _umod_360, _umod_PI2

20 from pygeodesy.errors import _ValueError, _xkwds, _ALL_LAZY, _MODS

21 from pygeodesy.internals import _passargs, typename

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

23# from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS # from .errors

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

25 Meter, Meter2, Radians # Radians_

26

27 from math import acos, asin, atan2 as _atan2, cos, degrees, fabs, radians, \

28 sin, tan as _tan # pow

29

30__all__ = _ALL_LAZY.utily

31__version__ = '25.04.14'

32

33_G_DEG = _float( 400.0 / _360_0) # grades per degree

34_G_RAD = _float( 400.0 / PI2) # grades per radian

35_M_ACRE = _float( 4046.8564224) # square meter per acre, chain2m(1) * furlong2m(1)

36_M_CHAIN = _float( 20.1168) # meter per yard2m(1) * 22

37_M_FATHOM = _float( 1.8288) # meter per yard2m(1) * 2 or _M_NM * 1e-3

38_M_FOOT = _float( 0.3048) # meter per Int'l foot, 1 / 3.280_839_895_0131 = 10_000 / (254 * 12)

39_M_FOOT_GE = _float( 0.31608) # meter per German Fuss, 1 / 3.163_756_011_1364

40_M_FOOT_FR = _float( 0.3248406) # meter per French Pied-du-Roi or pied, 1 / 3.078_432_929_8739

41_M_FOOT_US = _float( 0.3048006096012192) # meter per US Survey foot, 1_200 / 3_937

42_M_FURLONG = _float( 201.168) # meter per furlong, 220 * yard2m(1) = 10 * m2chain(1)

43_M_HA = _float(10000.0) # square meter per hectare, 100 * 100

44# _M_KM = _float( 1000.0) # meter per kilo meter

45# _M_NM = _float( 1852.0) # meter per nautical mile

46# _M_SM = _float( 1609.344) # meter per statute mile

47_M_TOISE = _float( 1.9490436) # meter per French toise, 6 pieds = 6 / 3.078_432_929_8739

48_M_YARD_UK = _float( 0.9144) # meter per yard, 254 * 12 * 3 / 10_000 = 3 * _M_FOOT

49# sqrt(3) <https://WikiPedia.org/wiki/Square_root_of_3>

50_COS_30, _SIN_30 = 0.86602540378443864676, _0_5 # sqrt(3) / 2

51_COS_45 = _SIN_45 = 0.70710678118654752440 # sqrt(2) / 2

52

53

54 def _abs1nan(x):

55 '''(INTERNAL) Bracket C{x}.

56 '''

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

58

59

60 def acos1(x):

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

62 '''

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

64

65

66 def acre2ha(acres):

67 '''Convert acres to hectare.

68

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

70

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

72

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

74 '''

75 return m2ha(acre2m2(acres))

76

77

78 def acre2m2(acres):

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

80

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

82

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

84

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

86 '''

87 return Meter2(Float(acres=acres) * _M_ACRE)

88

89

90 def asin1(x):

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

92 '''

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

94

95

96 def atan1(y, x=_1_0):

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

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

99 '''

100 return _atan1u(y, x, atan2)

101

102

103 def atan1d(y, x=_1_0):

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

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

106

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

108 '''

109 return _atan1u(y, x, atan2d)

110

111

112 def _atan1u(y, x, _2u):

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

114 '''

115 if x < 0:

116 x = -x

117 y = -y

118 return _2u(y, x or _0_0)

119

120

121 atan2 = _atan2

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

123

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

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

126'''

127

128

129 def atan2b(y, x):

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

131

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

133 '''

134 b = atan2d(y, x)

135 if b < 0:

136 b += _360_0

137 return b or _0_0 # unsigned-0

138

139

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

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

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

143

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

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

146 '''

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

148 return _azireversed(d) if reverse else d

149

150

151 def _azireversed(azi): # in .rhumbBase

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

153 '''

154 return azi - _copysign(_180_0, azi)

155

156

157 def chain2m(chains):

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

159

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

161

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

163

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

165 '''

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

167

168

169 def circle4(earth, lat):

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

171

172 @arg earth: The earth radius (C{meter}), ellipsoid or datum

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

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

175

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

177

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

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

180

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

182

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

184 '''

185 E = _MODS.datums._earth_ellipsoid(earth)

186 return E.circle4(lat)

187

188

189 def cot(rad, **raiser_kwds):

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

191

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

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

194 ValueErrors or optionally, additional

195 ValueError keyword argments.

196

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

198

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

200 '''

201 try:

202 return _cotu(*sincos2(rad), **raiser_kwds)

203 except ZeroDivisionError:

204 raise _valueError(cot, rad, **raiser_kwds)

205

206

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

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

209

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

211

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

213

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

215 '''

216 try:

217 for r in rads:

218 yield _cotu(*sincos2(r), **raiser_kwds)

219 except ZeroDivisionError:

220 raise _valueError(cot_, r, **raiser_kwds)

221

222

223 def cotd(deg, **raiser_kwds):

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

225

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

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

228 ValueErrors or optionally, additional

229 ValueError keyword argments.

230

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

232

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

234 '''

235 try:

236 return _cotu(*sincos2d(deg), **raiser_kwds)

237 except ZeroDivisionError:

238 raise _valueError(cotd, deg, **raiser_kwds)

239

240

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

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

243

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

245

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

247

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

249 '''

250 try:

251 for d in degs:

252 yield _cotu(*sincos2d(d), **raiser_kwds)

253 except ZeroDivisionError:

254 raise _valueError(cotd_, d, **raiser_kwds)

255

256

257 def _cotu(s, c, **raiser_kwds):

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

259 '''

260 return _tanu(c, s, **raiser_kwds)

261

262

263 def degrees90(rad):

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

265

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

267

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

269 '''

270 return wrap90(degrees(rad))

271

272

273 def degrees180(rad):

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

275

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

277

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

279 '''

280 return wrap180(degrees(rad))

281

282

283 def degrees360(rad):

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

285

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

287

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

289 '''

290 return _umod_360(degrees(rad))

291

292

293 def degrees2grades(deg):

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

295

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

297

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

299 '''

300 return Float(grades=Degrees(deg) * _G_DEG)

301

302

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

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

305 at (geodetic) latitude.

306

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

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

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

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

311

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

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

314

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

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

317

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

319

320 @raise ValueError: Invalid B{C{deg}}, B{C{radius}} or B{C{lat}}.

321

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

323 '''

324 return _Radians2m(Lamd(deg=deg, clip=0), radius, lat)

325

326

327 def fathom2m(fathoms):

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

329

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

331

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

333

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

335

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

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

338 '''

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

340

341

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

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

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

345

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

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

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

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

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

351

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

353

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

355 '''

356 return Meter(Feet(feet) * (_M_FOOT_US if usurvey else

357 (_M_FOOT_FR if pied else

358 (_M_FOOT_GE if fuss else _M_FOOT))))

359

360

361 def furlong2m(furlongs):

362 '''Convert a furlong to meter.

363

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

365

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

367

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

369 '''

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

371

372

373 def grades(rad):

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

375

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

377

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

379 '''

380 return Float(grades=Radians(rad) * _G_RAD)

381

382

383 def grades400(rad):

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

385

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

387

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

389 '''

390 return Float(grades400=wrapPI2(rad) * _G_RAD)

391

392

393 def grades2degrees(gon):

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

395

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

397

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

399 '''

400 return Degrees(Float(gon=gon) / _G_DEG)

401

402

403 def grades2radians(gon):

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

405

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

407

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

409 '''

410 return Radians(Float(gon=gon) / _G_RAD)

411

412

413 def ha2acre(ha):

414 '''Convert hectare to acre.

415

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

417

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

419

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

421 '''

422 return m2acre(ha2m2(ha))

423

424

425 def ha2m2(ha):

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

427

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

429

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

431

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

433 '''

434 return Meter2(Float(ha=ha) * _M_HA)

435

436

437 def hav(rad):

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

439

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

441

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

443 '''

444 return sin(rad * _0_5)**2

445

446

447 def km2m(km):

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

449

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

451

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

453

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

455 '''

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

457

458

459 def _loneg(lon):

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

461 '''

462 return _180_0 - lon

463

464

465 def m2acre(meter2):

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

467

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

469

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

471

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

473 '''

474 return Float(acre=Meter2(meter2) / _M_ACRE)

475

476

477 def m2chain(meter):

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

479

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

481

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

483

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

485 '''

486 return Float(chain=Meter(meter) / _M_CHAIN) # * 0.049_709_695_378_986_715

487

488

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

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

491 at (geodetic) latitude.

492

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

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

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

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

497

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

499

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

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

502

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

504

505 @raise ValueError: Invalid B{C{distance}}, B{C{radius}} or B{C{lat}}.

506

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

508 '''

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

510

511

512 def m2fathom(meter):

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

514

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

516

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

518

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

520

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

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

523 '''

524 return Float(fathom=Meter(meter) / _M_FATHOM) # * 0.546_806_649

525

526

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

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

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

530

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

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

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

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

535 I{International} foot.

536

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

538

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

540 '''

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

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

543 return Float(feet=Meter(meter) / (_M_FOOT_US if usurvey else

544 (_M_FOOT_FR if pied else

545 (_M_FOOT_GE if fuss else _M_FOOT))))

546

547

548 def m2furlong(meter):

549 '''Convert meter to furlongs.

550

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

552

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

554

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

556 '''

557 return Float(furlong=Meter(meter) / _M_FURLONG) # * 0.004_970_969_54

558

559

560 def m2ha(meter2):

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

562

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

564

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

566

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

568 '''

569 return Float(ha=Meter2(meter2) / _M_HA)

570

571

572 def m2km(meter):

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

574

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

576

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

578

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

580 '''

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

582

583

584 def m2NM(meter):

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

586

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

588

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

590

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

592 '''

593 return Float(NM=Meter(meter) / _M_NM) # * 5.399_568_04e-4

594

595

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

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

598 at (geodetic) latitude.

599

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

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

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

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

604

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

606

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

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

609

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

611

612 @raise ValueError: Invalid B{C{distance}}, B{C{radius}} or B{C{lat}}.

613

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

615 '''

616 m = circle4(radius, lat).radius

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

618

619

620 def m2SM(meter):

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

622

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

624

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

626

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

628 '''

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

630

631

632 def m2toise(meter):

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

634

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

636

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

638

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

640

641 @see: Function L{m2fathom}.

642 '''

643 return Float(toise=Meter(meter) / _M_TOISE) # * 0.513_083_632_632_119

644

645

646 def m2yard(meter):

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

648

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

650

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

652

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

654 '''

655 return Float(yard=Meter(meter) / _M_YARD_UK) # * 1.093_613_298_337_707_8

656

657

658 def NM2m(nm):

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

660

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

662

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

664

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

666 '''

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

668

669

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

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

672 at (geodetic) latitude.

673

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

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

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

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

678

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

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

681

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

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

684

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

686

687 @raise ValueError: Invalid B{C{rad}}, B{C{radius}} or B{C{lat}}.

688

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

690 '''

691 return _Radians2m(Lam(rad=rad, clip=0), radius, lat)

692

693

694 def _Radians2m(rad, radius, lat):

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

696 '''

697 m = circle4(radius, lat).radius

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

699

700

701 def radiansPI(deg):

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

703

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

705

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

707 '''

708 return wrapPI(radians(deg))

709

710

711 def radiansPI2(deg):

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

713

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

715

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

717 '''

718 return _umod_PI2(radians(deg))

719

720

721 def radiansPI_2(deg):

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

723

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

725

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

727 '''

728 return wrapPI_2(radians(deg))

729

730

731 def _sin0cos2(q, r, sign, a, Q): # Quarter turn

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

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

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

735 '''

736 if r < PI_2:

737 s = sin(r)

738 if (a * 2) == Q:

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

740 elif (a * 3) == Q:

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

742 else:

743 c = cos(r)

744 else:

745 s, c = _1_0, _0_0

746 t = s, c, -s, -c, s

747# q &= 3

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

749 c = t[q + 1] or _0_0

750 return s, c

751

752

753 def SinCos2(x):

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

755

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

757

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

759 '''

760 return sincos2d(x) if isinstanceof(x, Degrees, Degrees_) else (

761# sincos2(x) if isinstanceof(x, Radians, Radians_) else

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

763

764

765 def sincos2(rad):

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

767

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

769

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

771

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

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

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

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

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

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

778 '''

779 if _isfinite(rad):

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

781 if q < 0:

782 q -= 1

783 r = rad - q * PI_2

784 t = _sin0cos2(q & 3, r, rad, fabs(r), PI_2)

785 else:

786 t = NAN, NAN

787 return t

788

789

790 def sincos2_(*rads):

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

792

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

794

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

796

797 @see: Function L{sincos2}.

798 '''

799 for r in rads:

800 s, c = sincos2(r)

801 yield s

802 yield c

803

804

805 def sincos2d(deg, adeg=_0_0):

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

807

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

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

810

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

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

813

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

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

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

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

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

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

820 '''

821 if _isfinite(deg):

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

823 if q < 0:

824 q -= 1

825 d = deg - q * _90_0

826 if adeg:

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

828 t = _sin0cos2(q & 3, radians(d), deg, fabs(d), _90_0)

829 else:

830 t = NAN, NAN

831 return t

832

833

834 def sincos2d_(*degs):

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

836

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

838

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

840

841 @see: Function L{sincos2d}.

842 '''

843 for d in degs:

844 s, c = sincos2d(d)

845 yield s

846 yield c

847

848

849 def sincostan3(rad):

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

851

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

853

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

855

856 @see: Function L{sincos2}.

857 '''

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

859

860

861 def _sincostan3(s, c):

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

863 '''

864 return s, c, _tanu(s, c, raiser=False)

865

866

867 def sincostan3d(deg):

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

869

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

871

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

873

874 @see: Function L{sincos2d}.

875 '''

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

877

878

879 def SM2m(sm):

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

881

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

883

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

885

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

887 '''

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

889

890

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

892 '''Compute the tangent of half angle.

893

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

895 @kwarg semi: Angle or edge name and index

896 for semi-circular error.

897

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

899

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

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

902 '''

903 # .formy.excessKarney_, .sphericalTrigonometry.areaOf

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

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

906 break

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

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

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

910

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

912

913

914 def tan(rad, **raiser_kwds):

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

916

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

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

919 ValueErrors or optionally, additional

920 ValueError keyword argments.

921

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

923

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

925 '''

926 try:

927 return _tanu(*sincos2(rad), **raiser_kwds)

928 except ZeroDivisionError:

929 raise _valueError(tan, rad, **raiser_kwds)

930

931

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

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

934

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

936

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

938

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

940 '''

941 try:

942 for r in rads:

943 yield _tanu(*sincos2(r), **raiser_kwds)

944 except ZeroDivisionError:

945 raise _valueError(tan_, r, **raiser_kwds)

946

947

948 def tand(deg, **raiser_kwds):

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

950

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

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

953 ValueErrors or optionally, additional

954 ValueError keyword argments.

955

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

957

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

959 '''

960 try:

961 return _tanu(*sincos2d(deg), **raiser_kwds)

962 except ZeroDivisionError:

963 raise _valueError(tand, deg, **raiser_kwds)

964

965

966 def tand_(*degs, **raiser_kwds):

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

968

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

970

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

972

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

974 '''

975 try:

976 for d in degs:

977 yield _tanu(*sincos2d(d), **raiser_kwds)

978 except ZeroDivisionError:

979 raise _valueError(tand_, d, **raiser_kwds)

980

981

982 def tanPI_2_2(rad):

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

984

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

986

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

988 '''

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

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

991

992

993 def _tanu(s, c, raiser=True, **unused):

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

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

996 '''

997 if s is NAN or isnan(s):

998 s = NAN

999 elif s:

1000 if raiser and isnear0(c):

1001 raise ZeroDivisionError()

1002 s = _over(s, c) if fabs(s) != fabs(c) else \

1003 _copysign(_1_0, (-s) if c < 0 else s)

1004 elif c < 0:

1005 s = -s # negate-0

1006 return s

1007

1008

1009 def toise2m(toises):

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

1011

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

1013

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

1015

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

1017

1018 @see: Function L{fathom2m}.

1019 '''

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

1021

1022

1023 def truncate(x, ndigits=None):

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

1025

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

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

1028 aka I{precision}.

1029

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

1031

1032 @see: Python function C{round}.

1033 '''

1034 if isint(ndigits):

1035 p = _10_0**ndigits

1036 x = int(x * p) / p

1037 return x

1038

1039

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

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

1042

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

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

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

1046 range (C{bool}).

1047

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

1049 C{degrees}).

1050

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

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

1053 '''

1054 d = lon2 - lon1

1055 if wrap:

1056 u = wrap180(d)

1057 if u != d:

1058 return u, (lon1 + u)

1059 return d, lon2

1060

1061

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

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

1064 '''

1065 lat, lon = p2.lat, p2.lon

1066 if wrap and _Wrap.normal:

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

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

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

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

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

1072 return p2

1073

1074

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

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

1077 '''

1078 w = wrap

1079 if w:

1080 w = _Wrap.normal

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

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

1083 p2 = _unrollon(p2, p3)

1084 return p2, p3, w # was wrapped?

1085

1086

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

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

1089

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

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

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

1093 range (C{bool}).

1094

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

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

1097

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

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

1100 '''

1101 r = rad2 - rad1

1102 if wrap:

1103 u = wrapPI(r)

1104 if u != r:

1105 return u, (rad1 + u)

1106 return r, rad2

1107

1108

1109 def _valueError(where, x, raiser=True, **kwds):

1110 '''(INTERNAL) Return a C{_ValueError} or C{None}.

1111 '''

1112 t = _MODS.streprs.Fmt.PAREN(typename(where), x)

1113 return _ValueError(t, **kwds) if raiser else None

1114

1115

1116 class _Wrap(object):

1117

1118 _normal = False # default

1119

1120 @property

1121 def normal(self):

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

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

1124 '''

1125 return self._normal

1126

1127 @normal.setter # PYCHOK setter!

1128 def normal(self, setting):

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

1130 '''

1131 m = _MODS.formy

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

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

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

1135 if t:

1136 self.latlon, self.philam = t

1137 self._normal = setting

1138

1139 def latlonDMS2(self, lat, lon, **DMS2_kwds):

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

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

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

1143 return self.latlon(lat, lon)

1144

1145# def normalatlon(self, *latlon):

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

1147

1148# def normalamphi(self, *philam):

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

1150

1151 def wraplatlon(self, lat, lon):

1152 return wrap90(lat), wrap180(lon)

1153

1154 latlon = wraplatlon # default

1155

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

1157 if wrap:

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

1159 lon21, lon2 = unroll180(lon1, lon2)

1160 else:

1161 lon21 = lon2 - lon1

1162 return lon21, lat2, lon2

1163

1164 def _latlonop(self, wrap):

1165 if wrap and self._normal is not None:

1166 return self.latlon

1167 else:

1168 return _passargs

1169

1170 def wraphilam(self, phi, lam):

1171 return wrapPI_2(phi), wrapPI(lam)

1172

1173 philam = wraphilam # default

1174

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

1176 if wrap:

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

1178 lam21, lam2 = unrollPI(lam1, lam2)

1179 else:

1180 lam21 = lam2 - lam1

1181 return lam21, phi2, lam2

1182

1183 def _philamop(self, wrap):

1184 if wrap and self._normal is not None:

1185 return self.philam

1186 else:

1187 return _passargs

1188

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

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

1191 '''

1192 if wrap and self._normal is not None:

1193 lat, lon = ll.latlon

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

1195 _n = self.latlon

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

1197 ll.latlon = _n(lat, lon)

1198 return ll

1199

1200_Wrap = _Wrap() # PYCHOK singleton

1201

1202

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

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

1205#

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

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

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

1209#

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

1211# '''

1212# a = float(angle)

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

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

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

1216# a %= modulo

1217# if a > wrap:

1218# a -= modulo

1219# return a

1220

1221

1222 def wrap90(deg):

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

1224

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

1226

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

1228 '''

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

1230

1231

1232 def wrap180(deg):

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

1234

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

1236

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

1238 '''

1239 d = float(deg)

1240 w = _umod_360(d)

1241 if w > _180_0:

1242 w -= _360_0

1243 elif d < 0 and w == _180_0:

1244 w = -w

1245 return w

1246

1247

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

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

1250

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

1252

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

1254 '''

1255 return _umod_360(float(deg))

1256

1257

1258 def wrapPI(rad):

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

1260

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

1262

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

1264 '''

1265 r = float(rad)

1266 w = _umod_PI2(r)

1267 if w > PI:

1268 w -= PI2

1269 elif r < 0 and w == PI:

1270 w = -PI

1271 return w

1272

1273

1274 def wrapPI2(rad):

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

1276

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

1278

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

1280 '''

1281 return _umod_PI2(float(rad))

1282

1283

1284 def wrapPI_2(rad):

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

1286

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

1288

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

1290 '''

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

1292

1293

1294# def wraplatlon(lat, lon):

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

1296# '''

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

1298

1299

1300 def wrap_normal(*normal):

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

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

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

1304

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

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

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

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

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

1310 To get the current setting, do not specify.

1311

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

1313 '''

1314 t = _Wrap.normal

1315 if normal:

1316 _Wrap.normal = normal[0]

1317 return t

1318

1319

1320# def wraphilam(phi, lam,):

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

1322# '''

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

1324

1325

1326 def _wrapu(w, H, Q):

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

1328 '''

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

1330

1331

1332 def yard2m(yards):

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

1334

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

1336

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

1338

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

1340 '''

1341 return Float(yards=yards) * _M_YARD_UK

1342

1343# **) MIT License

1344#

1345# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved.

1346#

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

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

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

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

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

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

1353#

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

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

1356#

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

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

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

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

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

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

1363# OTHER DEALINGS IN THE SOFTWARE.