Coverage for pygeodesy/frechet.py: 96%

205 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-01-10 16:55 -0500

1 

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

3 

4u'''Fréchet distances. 

5 

6Classes L{Frechet}, L{FrechetDegrees}, L{FrechetRadians}, L{FrechetCosineLaw}, 

7L{FrechetDistanceTo}, L{FrechetEquirectangular}, L{FrechetEuclidean}, 

8L{FrechetExact}, L{FrechetFlatLocal}, L{FrechetFlatPolar}, L{FrechetHaversine}, 

9L{FrechetHubeny}, L{FrechetKarney}, L{FrechetThomas} and L{FrechetVincentys} 

10to compute I{discrete} U{Fréchet<https://WikiPedia.org/wiki/Frechet_distance>} 

11distances between two sets of C{LatLon}, C{NumPy}, C{tuples} or other types of points. 

12 

13Only L{FrechetDistanceTo} -iff used with L{ellipsoidalKarney.LatLon} 

14points- and L{FrechetKarney} requires installation of I{Charles Karney}'s 

15U{geographiclib<https://PyPI.org/project/geographiclib>}. 

16 

17Typical usage is as follows. First, create a C{Frechet} calculator from one 

18set of C{LatLon} points. 

19 

20C{f = FrechetXyz(point1s, ...)} 

21 

22Get the I{discrete} Fréchet distance to another set of C{LatLon} points 

23by 

24 

25C{t6 = f.discrete(point2s)} 

26 

27Or, use function C{frechet_} with a proper C{distance} function passed 

28as keyword arguments as follows 

29 

30C{t6 = frechet_(point1s, point2s, ..., distance=...)}. 

31 

32In both cases, the returned result C{t6} is a L{Frechet6Tuple}. 

33 

34For C{(lat, lon, ...)} points in a C{NumPy} array or plain C{tuples}, 

35wrap the points in a L{Numpy2LatLon} respectively L{Tuple2LatLon} 

36instance, more details in the documentation thereof. 

37 

38For other points, create a L{Frechet} sub-class with the appropriate 

39C{distance} method overloading L{Frechet.distance} as in this example. 

40 

41 >>> from pygeodesy import Frechet, hypot_ 

42 >>> 

43 >>> class F3D(Frechet): 

44 >>> """Custom Frechet example. 

45 >>> """ 

46 >>> def distance(self, p1, p2): 

47 >>> return hypot_(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z) 

48 >>> 

49 >>> f3D = F3D(xyz1, ..., units="...") 

50 >>> t6 = f3D.discrete(xyz2) 

51 

52Transcribed from the original U{Computing Discrete Fréchet Distance 

53<https://www.kr.TUWien.ac.AT/staff/eiter/et-archive/cdtr9464.pdf>} by 

54Eiter, T. and Mannila, H., 1994, April 25, Technical Report CD-TR 94/64, 

55Information Systems Department/Christian Doppler Laboratory for Expert 

56Systems, Technical University Vienna, Austria. 

57 

58This L{Frechet.discrete} implementation optionally generates intermediate 

59points for each point set separately. For example, using keyword argument 

60C{fraction=0.5} adds one additional point halfway between each pair of 

61points. Or using C{fraction=0.1} interpolates nine additional points 

62between each points pair. 

63 

64The L{Frechet6Tuple} attributes C{fi1} and/or C{fi2} will be I{fractional} 

65indices of type C{float} if keyword argument C{fraction} is used. Otherwise, 

66C{fi1} and/or C{fi2} are simply type C{int} indices into the respective 

67points set. 

68 

69For example, C{fractional} index value 2.5 means an intermediate point 

70halfway between points[2] and points[3]. Use function L{fractional} 

71to obtain the intermediate point for a I{fractional} index in the 

72corresponding set of points. 

73 

74The C{Fréchet} distance was introduced in 1906 by U{Maurice Fréchet 

75<https://WikiPedia.org/wiki/Maurice_Rene_Frechet>}, see U{reference 

76[6]<https://www.kr.TUWien.ac.AT/staff/eiter/et-archive/cdtr9464.pdf>}. 

77It is a measure of similarity between curves that takes into account the 

78location and ordering of the points. Therefore, it is often a better metric 

79than the well-known C{Hausdorff} distance, see the L{hausdorff} module. 

80''' 

81 

82# from pygeodesy.basics import isscalar # from .points 

83from pygeodesy.constants import EPS, EPS1, INF, NINF 

84from pygeodesy.datums import _ellipsoidal_datum, _WGS84 

85from pygeodesy.errors import PointsError, _xattr, _xcallable, _xkwds, _xkwds_get 

86import pygeodesy.formy as _formy 

87from pygeodesy.interns import NN, _DOT_, _n_, _units_ 

88# from pygeodesy.iters import points2 as _points2 # from .points 

89from pygeodesy.lazily import _ALL_LAZY, _FOR_DOCS 

90from pygeodesy.named import _name2__, _Named, _NamedTuple, _Pass 

91# from pygeodesy.namedTuples import PhiLam2Tuple # from .points 

92from pygeodesy.points import _distanceTo, _fractional, isscalar, PhiLam2Tuple, \ 

93 points2 as _points2, radians 

94from pygeodesy.props import Property, property_doc_, property_RO 

95from pygeodesy.units import FIx, Float, Number_ 

96from pygeodesy import unitsBase as _unitsBase # _Str_..., _xUnit, _xUnits 

97 

98from collections import defaultdict as _defaultdict 

99# from math import radians # from .points 

100 

101__all__ = _ALL_LAZY.frechet 

102__version__ = '24.12.31' 

103 

104 

105def _fraction(fraction, n): 

106 f = 1 # int, no fractional indices 

107 if fraction in (None, 1): 

108 pass 

109 elif not (isscalar(fraction) and EPS < fraction < EPS1 

110 and (float(n) - fraction) < n): 

111 raise FrechetError(fraction=fraction) 

112 elif fraction < EPS1: 

113 f = float(fraction) 

114 return f 

115 

116 

117class FrechetError(PointsError): 

118 '''Fréchet issue. 

119 ''' 

120 pass 

121 

122 

123class Frechet(_Named): 

124 '''Frechet base class, requires method L{Frechet.distance} to 

125 be overloaded. 

126 ''' 

127 _datum = _WGS84 

128 _func = None # formy function/property 

129 _f1 = 1 

130 _kwds = {} # func_ options 

131 _n1 = 0 

132 _ps1 = None 

133 _units = _unitsBase._Str_NN # XXX Str to _Pass and for backward compatibility 

134 

135 def __init__(self, point1s, fraction=None, units=NN, **name__kwds): 

136 '''New C{Frechet...} calculator/interpolator. 

137 

138 @arg point1s: First set of points (C{LatLon}[], L{Numpy2LatLon}[], 

139 L{Tuple2LatLon}[] or C{other}[]). 

140 @kwarg fraction: Index fraction (C{float} in L{EPS}..L{EPS1}) to 

141 interpolate intermediate B{C{point1s}} or use C{None}, 

142 C{0} or C{1} for no intermediate B{C{point1s}} and no 

143 I{fractional} indices. 

144 @kwarg units: Optional distance units (C{Unit} or C{str}). 

145 @kwarg name__kwds: Optional C{B{name}=NN} for this calculator/interpolator 

146 (C{str}) and any keyword arguments for the distance function, 

147 retrievable with property C{kwds}. 

148 

149 @raise FrechetError: Insufficient number of B{C{point1s}} or an invalid 

150 B{C{point1}}, B{C{fraction}} or B{C{units}}. 

151 ''' 

152 name, kwds = _name2__(**name__kwds) # name__=self.__class__ 

153 if name: 

154 self.name = name 

155 

156 self._n1, self._ps1 = self._points2(point1s) 

157 if fraction: 

158 self.fraction = fraction 

159 if units: # and not self.units: 

160 self.units = units 

161 if kwds: 

162 self._kwds = kwds 

163 

164 @property_RO 

165 def adjust(self): 

166 '''Get the C{adjust} setting (C{bool} or C{None}). 

167 ''' 

168 return _xkwds_get(self._kwds, adjust=None) 

169 

170 @property_RO 

171 def datum(self): 

172 '''Get the datum (L{Datum} or C{None} if not applicable). 

173 ''' 

174 return self._datum 

175 

176 def _datum_setter(self, datum): 

177 '''(INTERNAL) Set the datum. 

178 ''' 

179 d = datum or _xattr(self._ps1[0], datum=None) 

180 if d and d is not self._datum: # PYCHOK no cover 

181 self._datum = _ellipsoidal_datum(d, name=self.name) 

182 

183 def discrete(self, point2s, fraction=None): 

184 '''Compute the C{forward, discrete Fréchet} distance. 

185 

186 @arg point2s: Second set of points (C{LatLon}[], L{Numpy2LatLon}[], 

187 L{Tuple2LatLon}[] or C{other}[]). 

188 @kwarg fraction: Index fraction (C{float} in L{EPS}..L{EPS1}) to 

189 interpolate intermediate B{C{point2s}} or use 

190 C{None}, C{0} or C{1} for no intermediate 

191 B{C{point2s}} and no I{fractional} indices. 

192 

193 @return: A L{Frechet6Tuple}C{(fd, fi1, fi2, r, n, units)}. 

194 

195 @raise FrechetError: Insufficient number of B{C{point2s}} or 

196 an invalid B{C{point2}} or B{C{fraction}}. 

197 

198 @raise RecursionError: Recursion depth exceeded, see U{sys.getrecursionlimit 

199 <https://docs.Python.org/3/library/sys.html#sys.getrecursionlimit>}. 

200 ''' 

201 return self._discrete(point2s, fraction, self.distance) 

202 

203 def _discrete(self, point2s, fraction, distance): 

204 '''(INTERNAL) Detailed C{discrete} with C{disance}. 

205 ''' 

206 n2, ps2 = self._points2(point2s) 

207 

208 f2 = _fraction(fraction, n2) 

209 p2 = self.points_fraction if f2 < EPS1 else self.points_ # PYCHOK expected 

210 

211 f1 = self.fraction 

212 p1 = self.points_fraction if f1 < EPS1 else self.points_ # PYCHOK expected 

213 

214 def _dF(fi1, fi2): 

215 return distance(p1(self._ps1, fi1), p2(ps2, fi2)) 

216 

217 try: 

218 return _frechet_(self._n1, f1, n2, f2, _dF, self.units) 

219 except TypeError as x: 

220 t = _DOT_(self.classname, self.discrete.__name__) 

221 raise FrechetError(t, cause=x) 

222 

223 def distance(self, point1, point2): 

224 '''Return the distance between B{C{point1}} and B{C{point2s}}, 

225 subject to the supplied optional keyword arguments, see 

226 property C{kwds}. 

227 ''' 

228 return self._func(point1.lat, point1.lon, 

229 point2.lat, point2.lon, **self._kwds) 

230 

231 @property_doc_(''' the index fraction (C{float}).''') 

232 def fraction(self): 

233 '''Get the index fraction (C{float} or C{1}). 

234 ''' 

235 return self._f1 

236 

237 @fraction.setter # PYCHOK setter! 

238 def fraction(self, fraction): 

239 '''Set the index fraction (C{float} in C{EPS}..L{EPS1}) to interpolate 

240 intermediate B{C{point1s}} or use C{None}, C{0} or C{1} for no 

241 intermediate B{C{point1s}} and no I{fractional} indices. 

242 

243 @raise FrechetError: Invalid B{C{fraction}}. 

244 ''' 

245 self._f1 = _fraction(fraction, self._n1) 

246 

247# def _func(self, *args, **kwds): # PYCHOK no cover 

248# '''(INTERNAL) I{Must be overloaded}.''' 

249# self._notOverloaded(*args, **kwds) 

250 

251 @Property 

252 def _func(self): 

253 '''(INTERNAL) I{Must be overloaded}.''' 

254 self._notOverloaded(**self.kwds) 

255 

256 @_func.setter_ # PYCHOK setter_underscore! 

257 def _func(self, func): 

258 return _formy._Propy(func, 4, self.kwds) 

259 

260 @property_RO 

261 def kwds(self): 

262 '''Get the supplied, optional keyword arguments (C{dict}). 

263 ''' 

264 return self._kwds 

265 

266 def point(self, point): 

267 '''Convert a point for the C{.distance} method. 

268 

269 @arg point: The point to convert ((C{LatLon}, L{Numpy2LatLon}, 

270 L{Tuple2LatLon} or C{other}). 

271 

272 @return: The converted B{C{point}}. 

273 ''' 

274 return point # pass thru 

275 

276 def points_(self, points, i): 

277 '''Get and convert a point for the C{.distance} method. 

278 

279 @arg points: The orignal B{C{points}} to convert ((C{LatLon}[], 

280 L{Numpy2LatLon}[], L{Tuple2LatLon}[] or C{other}[]). 

281 @arg i: The B{C{points}} index (C{int}). 

282 

283 @return: The converted B{C{points[i]}}. 

284 ''' 

285 return self.point(points[i]) 

286 

287 def points_fraction(self, points, fi): 

288 '''Get and convert a I{fractional} point for the C{.distance} method. 

289 

290 @arg points: The orignal B{C{points}} to convert ((C{LatLon}[], 

291 L{Numpy2LatLon}[], L{Tuple2LatLon}[] or C{other}[]). 

292 @arg fi: The I{fractional} index in B{C{points}} (C{float} or C{int}). 

293 

294 @return: The interpolated, converted, intermediate B{C{points[fi]}}. 

295 ''' 

296 return self.point(_fractional(points, fi, None, wrap=None)) # was=self.wrap 

297 

298 def _points2(self, points): 

299 '''(INTERNAL) Check a set of points, overloaded in L{FrechetDistanceTo}. 

300 ''' 

301 return _points2(points, closed=False, Error=FrechetError) 

302 

303 @property_doc_(''' the distance units (C{Unit} or C{str}).''') 

304 def units(self): 

305 '''Get the distance units (C{Unit} or C{str}). 

306 ''' 

307 return self._units 

308 

309 @units.setter # PYCHOK setter! 

310 def units(self, units): 

311 '''Set the distance units (C{Unit} or C{str}). 

312 

313 @raise TypeError: Invalid B{C{units}}. 

314 ''' 

315 self._units = _unitsBase._xUnits(units, Base=Float) 

316 

317 @property_RO 

318 def wrap(self): 

319 '''Get the C{wrap} setting (C{bool} or C{None}). 

320 ''' 

321 return _xkwds_get(self._kwds, wrap=None) 

322 

323 

324class FrechetDegrees(Frechet): 

325 '''DEPRECATED, use an other C{Frechet*} class. 

326 ''' 

327 _units = _unitsBase._Str_degrees 

328 

329 if _FOR_DOCS: 

330 __init__ = Frechet.__init__ 

331 discrete = Frechet.discrete 

332 

333 def distance(self, point1, point2, *args, **kwds): # PYCHOK no cover 

334 '''I{Must be overloaded}.''' 

335 self._notOverloaded(point1, point2, *args, **kwds) 

336 

337 

338class FrechetRadians(Frechet): 

339 '''DEPRECATED, use an other C{Frechet*} class. 

340 ''' 

341 _units = _unitsBase._Str_radians 

342 

343 if _FOR_DOCS: 

344 __init__ = Frechet.__init__ 

345 discrete = Frechet.discrete 

346 

347 def distance(self, point1, point2, *args, **kwds): # PYCHOK no cover 

348 '''I{Must be overloaded}.''' 

349 self._notOverloaded(point1, point2, *args, **kwds) 

350 

351 def point(self, point): 

352 '''Return B{C{point}} as L{PhiLam2Tuple} to maintain 

353 I{backward compatibility} of L{FrechetRadians}. 

354 

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

356 ''' 

357 try: 

358 return point.philam 

359 except AttributeError: 

360 return PhiLam2Tuple(radians(point.lat), radians(point.lon)) 

361 

362 

363class _FrechetMeterRadians(Frechet): 

364 '''(INTERNAL) Returning C{meter} or C{radians} depending on 

365 the optional keyword arguments supplied at instantiation 

366 of the C{Frechet*} sub-class. 

367 ''' 

368 _units = _unitsBase._Str_meter 

369 _units_ = _unitsBase._Str_radians 

370 

371 def discrete(self, point2s, fraction=None): 

372 '''Overloaded method L{Frechet.discrete} to determine 

373 the distance function and units from the optional 

374 keyword arguments given at this instantiation, see 

375 property C{kwds}. 

376 

377 @see: L{Frechet.discrete} for other details. 

378 ''' 

379 return self._discrete(point2s, fraction, _formy._radistance(self)) 

380 

381 @Property 

382 def _func_(self): # see _formy._radistance 

383 '''(INTERNAL) I{Must be overloaded}.''' 

384 self._notOverloaded(**self.kwds) 

385 

386 @_func_.setter_ # PYCHOK setter_underscore! 

387 def _func_(self, func): 

388 return _formy._Propy(func, 3, self.kwds) 

389 

390 

391class FrechetCosineLaw(_FrechetMeterRadians): 

392 '''Compute the C{Frechet} distance with functionn L{pygeodesy.cosineLaw}. 

393 

394 @note: See note at function L{pygeodesy.vincentys_}. 

395 ''' 

396 def __init__(self, point1s, **fraction_name__corr_earth_wrap): 

397 '''New L{FrechetCosineLaw} calculator/interpolator. 

398 

399 @kwarg fraction_name__corr_earth_wrap: Optional 

400 C{B{fraction}=None} and C{B{name}=NN} and keyword 

401 arguments for function L{pygeodesy.cosineLaw}. 

402 

403 @see: L{Frechet.__init__} for details about B{C{point1s}}, 

404 B{C{fraction}}, B{C{name}} and other exceptions. 

405 ''' 

406 Frechet.__init__(self, point1s, **fraction_name__corr_earth_wrap) 

407 self._func = _formy.cosineLaw 

408 self._func_ = _formy.cosineLaw_ 

409 

410 if _FOR_DOCS: 

411 discrete = Frechet.discrete 

412 

413 

414class FrechetDistanceTo(Frechet): # FrechetMeter 

415 '''Compute the C{Frechet} distance with the point1s' C{LatLon.distanceTo} method. 

416 ''' 

417 _units = _unitsBase._Str_meter 

418 

419 def __init__(self, point1s, **fraction_name__distanceTo_kwds): 

420 '''New L{FrechetDistanceTo} calculator/interpolator. 

421 

422 @kwarg fraction_name__distanceTo_kwds: Optional C{B{fraction}=None} 

423 and C{B{name}=NN} and keyword arguments for 

424 each B{C{point1s}}' C{LatLon.distanceTo} method. 

425 

426 @see: L{Frechet.__init__} for details about B{C{point1s}}, B{C{fraction}}, 

427 B{C{name}} and other exceptions. 

428 

429 @note: All B{C{point1s}} I{must} be instances of the same ellipsoidal 

430 or spherical C{LatLon} class. 

431 ''' 

432 Frechet.__init__(self, point1s, **fraction_name__distanceTo_kwds) 

433 

434 if _FOR_DOCS: 

435 discrete = Frechet.discrete 

436 

437 def distance(self, p1, p2): 

438 '''Return the distance in C{meter}. 

439 ''' 

440 return p1.distanceTo(p2, **self._kwds) 

441 

442 def _points2(self, points): 

443 '''(INTERNAL) Check a set of points. 

444 ''' 

445 np, ps = Frechet._points2(self, points) 

446 return np, _distanceTo(FrechetError, points=ps) 

447 

448 

449class FrechetEquirectangular(Frechet): 

450 '''Compute the C{Frechet} distance with function L{pygeodesy.equirectangular}. 

451 ''' 

452 _units = _unitsBase._Str_radians2 

453 

454 def __init__(self, point1s, **fraction_name__adjust_limit_wrap): 

455 '''New L{FrechetEquirectangular} calculator/interpolator. 

456 

457 @kwarg fraction_name__adjust_limit_wrap: Optional C{B{fraction}=None} 

458 and C{B{name}=NN} and keyword arguments for 

459 function L{pygeodesy.equirectangular} I{with 

460 default} C{B{limit}=0} for I{backward compatibility}. 

461 

462 @see: L{Frechet.__init__} for details about B{C{point1s}}, B{C{fraction}}, 

463 B{C{name}} and other exceptions. 

464 ''' 

465 Frechet.__init__(self, point1s, **_xkwds(fraction_name__adjust_limit_wrap, 

466 limit=0)) 

467 self._func = _formy._equirectangular # helper 

468 

469 if _FOR_DOCS: 

470 discrete = Frechet.discrete 

471 

472 

473class FrechetEuclidean(_FrechetMeterRadians): 

474 '''Compute the C{Frechet} distance with function L{pygeodesy.euclidean}. 

475 ''' 

476 def __init__(self, point1s, **fraction_name__adjust_radius_wrap): # was=True 

477 '''New L{FrechetEuclidean} calculator/interpolator. 

478 

479 @kwarg fraction_name__adjust_radius_wrap: Optional C{B{fraction}=None} 

480 and C{B{name}=NN} and keyword arguments for 

481 function L{pygeodesy.euclidean}. 

482 

483 @see: L{Frechet.__init__} for details about B{C{point1s}}, B{C{fraction}}, 

484 B{C{name}} and other exceptions. 

485 ''' 

486 Frechet.__init__(self, point1s, **fraction_name__adjust_radius_wrap) 

487 self._func = _formy.euclidean 

488 self._func_ = _formy.euclidean_ 

489 

490 if _FOR_DOCS: 

491 discrete = Frechet.discrete 

492 

493 

494class FrechetExact(Frechet): 

495 '''Compute the C{Frechet} distance with method L{GeodesicExact}C{.Inverse}. 

496 ''' 

497 _units = _unitsBase._Str_degrees 

498 

499 def __init__(self, point1s, datum=None, **fraction_name__wrap): 

500 '''New L{FrechetExact} calculator/interpolator. 

501 

502 @kwarg datum: Datum to override the default C{Datums.WGS84} and first 

503 B{C{point1s}}' datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} 

504 or L{a_f2Tuple}). 

505 @kwarg fraction_name__wrap: Optional C{B{fraction}=None} and C{B{name}=NN} 

506 and keyword argument for method C{Inverse1} of class 

507 L{geodesicx.GeodesicExact}. 

508 

509 @raise TypeError: Invalid B{C{datum}}. 

510 

511 @see: L{Frechet.__init__} for details about B{C{point1s}}, B{C{fraction}}, 

512 B{C{name}} and other exceptions. 

513 ''' 

514 Frechet.__init__(self, point1s, **fraction_name__wrap) 

515 self._datum_setter(datum) 

516 self._func = self.datum.ellipsoid.geodesicx.Inverse1 # note -x 

517 

518 if _FOR_DOCS: 

519 discrete = Frechet.discrete 

520 

521 

522class FrechetFlatLocal(_FrechetMeterRadians): 

523 '''Compute the C{Frechet} distance with function L{pygeodesy.flatLocal_}/L{pygeodesy.hubeny}. 

524 ''' 

525 _units_ = _unitsBase._Str_radians2 # see L{flatLocal_} 

526 

527 def __init__(self, point1s, **fraction_name__datum_scaled_wrap): 

528 '''New L{FrechetFlatLocal}/L{FrechetHubeny} calculator/interpolator. 

529 

530 @kwarg fraction_name__datum_scaled_wrap: Optional C{B{fraction}=None} 

531 and C{B{name}=NN} and keyword arguments for 

532 function L{pygeodesy.flatLocal}. 

533 

534 @see: L{Frechet.__init__} for details about B{C{point1s}}, B{C{fraction}}, 

535 B{C{name}} and other exceptions. 

536 

537 @note: The distance C{units} are C{radians squared}, not C{radians}. 

538 ''' 

539 Frechet.__init__(self, point1s, **fraction_name__datum_scaled_wrap) 

540 self._func = _formy.flatLocal 

541 self._func_ = self.datum.ellipsoid._hubeny_2 

542 

543 if _FOR_DOCS: 

544 discrete = Frechet.discrete 

545 

546 

547class FrechetFlatPolar(_FrechetMeterRadians): 

548 '''Compute the C{Frechet} distance with function L{flatPolar_}. 

549 ''' 

550 def __init__(self, point1s, **fraction_name__radius_wrap): 

551 '''New L{FrechetFlatPolar} calculator/interpolator. 

552 

553 @kwarg fraction_name__radius_wrap: Optional C{B{fraction}=None} 

554 and C{B{name}=NN} and keyword arguments 

555 for function L{pygeodesy.flatPolar}. 

556 

557 @see: L{Frechet.__init__} for details about B{C{point1s}}, 

558 B{C{fraction}}, B{C{name}} and other exceptions. 

559 ''' 

560 Frechet.__init__(self, point1s, **fraction_name__radius_wrap) 

561 self._func = _formy.flatPolar 

562 self._func_ = _formy.flatPolar_ 

563 

564 if _FOR_DOCS: 

565 discrete = Frechet.discrete 

566 

567 

568class FrechetHaversine(_FrechetMeterRadians): 

569 '''Compute the C{Frechet} distance with function L{pygeodesy.haversine_}. 

570 

571 @note: See note at function L{pygeodesy.vincentys_}. 

572 ''' 

573 def __init__(self, point1s, **fraction_name__radius_wrap): 

574 '''New L{FrechetHaversine} calculator/interpolator. 

575 

576 @kwarg fraction_name__radius_wrap: Optional C{B{fraction}=None} 

577 and C{B{name}=NN} and keyword arguments 

578 for function L{pygeodesy.haversine}. 

579 

580 @see: L{Frechet.__init__} for details about B{C{point1s}}, 

581 B{C{fraction}}, B{C{name}} and other exceptions. 

582 ''' 

583 Frechet.__init__(self, point1s, **fraction_name__radius_wrap) 

584 self._func = _formy.haversine 

585 self._func_ = _formy.haversine_ 

586 

587 if _FOR_DOCS: 

588 discrete = Frechet.discrete 

589 

590 

591class FrechetHubeny(FrechetFlatLocal): # for Karl Hubeny 

592 if _FOR_DOCS: 

593 __doc__ = FrechetFlatLocal.__doc__ 

594 __init__ = FrechetFlatLocal.__init__ 

595 discrete = FrechetFlatLocal.discrete 

596 distance = FrechetFlatLocal.discrete 

597 

598 

599class FrechetKarney(Frechet): 

600 '''Compute the C{Frechet} distance with I{Karney}'s U{geographiclib 

601 <https://PyPI.org/project/geographiclib>} U{geodesic.Geodesic 

602 <https://GeographicLib.SourceForge.io/Python/doc/code.html>}C{.Inverse} 

603 method. 

604 ''' 

605 _units = _unitsBase._Str_degrees 

606 

607 def __init__(self, point1s, datum=None, **fraction_name__wrap): 

608 '''New L{FrechetKarney} calculator/interpolator. 

609 

610 @kwarg datum: Datum to override the default C{Datums.WGS84} and 

611 first B{C{knots}}' datum (L{Datum}, L{Ellipsoid}, 

612 L{Ellipsoid2} or L{a_f2Tuple}). 

613 @kwarg fraction_name__wrap: Optional C{B{fraction}=None} and 

614 C{B{name}=NN} and keyword arguments for 

615 method C{Inverse1} of class L{geodesicw.Geodesic}. 

616 

617 @raise ImportError: Package U{geographiclib 

618 <https://PyPI.org/project/geographiclib>} missing. 

619 

620 @raise TypeError: Invalid B{C{datum}}. 

621 

622 @see: L{Frechet.__init__} for details about B{C{point1s}}, 

623 B{C{fraction}}, B{C{name}} and other exceptions. 

624 ''' 

625 Frechet.__init__(self, point1s, **fraction_name__wrap) 

626 self._datum_setter(datum) 

627 self._func = self.datum.ellipsoid.geodesic.Inverse1 

628 

629 if _FOR_DOCS: 

630 discrete = Frechet.discrete 

631 

632 

633class FrechetThomas(_FrechetMeterRadians): 

634 '''Compute the C{Frechet} distance with function L{pygeodesy.thomas_}. 

635 ''' 

636 def __init__(self, point1s, **fraction_name__datum_wrap): 

637 '''New L{FrechetThomas} calculator/interpolator. 

638 

639 @kwarg fraction_name__datum_wrap: Optional C{B{fraction}=None} 

640 and C{B{name}=NN} and keyword arguments 

641 for function L{pygeodesy.thomas}. 

642 

643 @see: L{Frechet.__init__} for details about B{C{point1s}}, 

644 B{C{fraction}}, B{C{name}} and other exceptions. 

645 ''' 

646 Frechet.__init__(self, point1s, **fraction_name__datum_wrap) 

647 self._func = _formy.thomas 

648 self._func_ = _formy.thomas_ 

649 

650 if _FOR_DOCS: 

651 discrete = Frechet.discrete 

652 

653 

654class FrechetVincentys(_FrechetMeterRadians): 

655 '''Compute the C{Frechet} distance with function L{pygeodesy.vincentys_}. 

656 

657 @note: See note at function L{pygeodesy.vincentys_}. 

658 ''' 

659 def __init__(self, point1s, **fraction_name__radius_wrap): 

660 '''New L{FrechetVincentys} calculator/interpolator. 

661 

662 @kwarg fraction_name__radius_wrap: Optional C{B{fraction}=None} 

663 and C{B{name}=NN} and keyword arguments 

664 for function L{pygeodesy.vincentys}. 

665 

666 @see: L{Frechet.__init__} for details about B{C{point1s}}, 

667 B{C{fraction}}, B{C{name}} and other exceptions. 

668 ''' 

669 Frechet.__init__(self, point1s, **fraction_name__radius_wrap) 

670 self._func = _formy.vincentys 

671 self._func_ = _formy.vincentys_ 

672 

673 if _FOR_DOCS: 

674 discrete = Frechet.discrete 

675 

676 

677def _frechet_(ni, fi, nj, fj, dF, units): # MCCABE 14 

678 '''(INTERNAL) Recursive core of function L{frechet_} 

679 and method C{discrete} of C{Frechet...} classes. 

680 ''' 

681 iFs = {} 

682 

683 def iF(i): # cache index, depth ints and floats 

684 return iFs.setdefault(i, i) 

685 

686 cF = _defaultdict(dict) 

687 

688 def _rF(i, j, r): # recursive Fréchet 

689 i = iF(i) 

690 j = iF(j) 

691 try: 

692 t = cF[i][j] 

693 except KeyError: 

694 r = iF(r + 1) 

695 try: 

696 if i > 0: 

697 if j > 0: 

698 t = min(_rF(i - fi, j, r), 

699 _rF(i - fi, j - fj, r), 

700 _rF(i, j - fj, r)) 

701 elif j < 0: 

702 raise IndexError 

703 else: # j == 0 

704 t = _rF(i - fi, 0, r) 

705 elif i < 0: 

706 raise IndexError 

707 

708 elif j > 0: # i == 0 

709 t = _rF(0, j - fj, r) 

710 elif j < 0: # i == 0 

711 raise IndexError 

712 else: # i == j == 0 

713 t = (NINF, i, j, r) 

714 

715 d = dF(i, j) 

716 if d > t[0]: 

717 t = (d, i, j, r) 

718 except IndexError: 

719 t = (INF, i, j, r) 

720 cF[i][j] = t 

721 return t 

722 

723 t = _rF(ni - 1, nj - 1, 0) 

724 t += (sum(map(len, cF.values())), units) 

725# del cF, iFs 

726 return Frechet6Tuple(t) # *t 

727 

728 

729def frechet_(point1s, point2s, distance=None, units=NN): 

730 '''Compute the I{discrete} U{Fréchet<https://WikiPedia.org/wiki/Frechet_distance>} 

731 distance between two paths, each given as a set of points. 

732 

733 @arg point1s: First set of points (C{LatLon}[], L{Numpy2LatLon}[], 

734 L{Tuple2LatLon}[] or C{other}[]). 

735 @arg point2s: Second set of points (C{LatLon}[], L{Numpy2LatLon}[], 

736 L{Tuple2LatLon}[] or C{other}[]). 

737 @kwarg distance: Callable returning the distance between a B{C{point1s}} 

738 and a B{C{point2s}} point (signature C{(point1, point2)}). 

739 @kwarg units: Optional, the distance units (C{Unit} or C{str}). 

740 

741 @return: A L{Frechet6Tuple}C{(fd, fi1, fi2, r, n, units)} where C{fi1} 

742 and C{fi2} are type C{int} indices into B{C{point1s}} respectively 

743 B{C{point2s}}. 

744 

745 @raise FrechetError: Insufficient number of B{C{point1s}} or B{C{point2s}}. 

746 

747 @raise RecursionError: Recursion depth exceeded, see U{sys.getrecursionlimit() 

748 <https://docs.Python.org/3/library/sys.html#sys.getrecursionlimit>}. 

749 

750 @raise TypeError: If B{C{distance}} is not a callable. 

751 

752 @note: Function L{frechet_} does I{not} support I{fractional} indices 

753 for intermediate B{C{point1s}} and B{C{point2s}}. 

754 ''' 

755 _xcallable(distance=distance) 

756 

757 n1, ps1 = _points2(point1s, closed=False, Error=FrechetError) 

758 n2, ps2 = _points2(point2s, closed=False, Error=FrechetError) 

759 

760 def _dF(i1, i2): 

761 return distance(ps1[i1], ps2[i2]) 

762 

763 return _frechet_(n1, 1, n2, 1, _dF, units) 

764 

765 

766class Frechet6Tuple(_NamedTuple): 

767 '''6-Tuple C{(fd, fi1, fi2, r, n, units)} with the I{discrete} 

768 U{Fréchet<https://WikiPedia.org/wiki/Frechet_distance>} distance 

769 C{fd}, I{fractional} indices C{fi1} and C{fi2} as C{FIx}, the 

770 recursion depth C{r}, the number of distances computed C{n} and 

771 the L{units} class or class or name of the distance C{units}. 

772 

773 If I{fractional} indices C{fi1} and C{fi2} are C{int}, the 

774 returned C{fd} is the distance between C{point1s[fi1]} and 

775 C{point2s[fi2]}. For C{float} indices, the distance is 

776 between an intermediate point along C{point1s[int(fi1)]} and 

777 C{point1s[int(fi1) + 1]} respectively an intermediate point 

778 along C{point2s[int(fi2)]} and C{point2s[int(fi2) + 1]}. 

779 

780 Use function L{fractional} to compute the point at a 

781 I{fractional} index. 

782 ''' 

783 _Names_ = ('fd', 'fi1', 'fi2', 'r', _n_, _units_) 

784 _Units_ = (_Pass, FIx, FIx, Number_, Number_, _Pass) 

785 

786 def toUnits(self, **Error_name): # PYCHOK expected 

787 '''Overloaded C{_NamedTuple.toUnits} for C{fd} units. 

788 ''' 

789 U = _unitsBase._xUnit(self.units, Float) # PYCHOK expected 

790 return _NamedTuple.toUnits(self.reUnit(U), **Error_name) # PYCHOK self 

791 

792# def __gt__(self, other): 

793# _xinstanceof(Frechet6Tuple, other=other) 

794# return self if self.fd > other.fd else other # PYCHOK .fd=[0] 

795# 

796# def __lt__(self, other): 

797# _xinstanceof(Frechet6Tuple, other=other) 

798# return self if self.fd < other.fd else other # PYCHOK .fd=[0] 

799 

800# **) MIT License 

801# 

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

803# 

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

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

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

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

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

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

810# 

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

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

813# 

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

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

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

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

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

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

820# OTHER DEALINGS IN THE SOFTWARE.