pygeodesy 24.3.24__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PyGeodesy-24.3.24.dist-info/METADATA +272 -0
- PyGeodesy-24.3.24.dist-info/RECORD +115 -0
- PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
- PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
- pygeodesy/LICENSE +21 -0
- pygeodesy/__init__.py +615 -0
- pygeodesy/__main__.py +103 -0
- pygeodesy/albers.py +867 -0
- pygeodesy/auxilats/_CX_4.py +218 -0
- pygeodesy/auxilats/_CX_6.py +314 -0
- pygeodesy/auxilats/_CX_8.py +475 -0
- pygeodesy/auxilats/__init__.py +54 -0
- pygeodesy/auxilats/__main__.py +86 -0
- pygeodesy/auxilats/auxAngle.py +548 -0
- pygeodesy/auxilats/auxDLat.py +302 -0
- pygeodesy/auxilats/auxDST.py +296 -0
- pygeodesy/auxilats/auxLat.py +848 -0
- pygeodesy/auxilats/auxily.py +272 -0
- pygeodesy/azimuthal.py +1150 -0
- pygeodesy/basics.py +892 -0
- pygeodesy/booleans.py +2031 -0
- pygeodesy/cartesianBase.py +1062 -0
- pygeodesy/clipy.py +704 -0
- pygeodesy/constants.py +516 -0
- pygeodesy/css.py +660 -0
- pygeodesy/datums.py +752 -0
- pygeodesy/deprecated/__init__.py +61 -0
- pygeodesy/deprecated/bases.py +40 -0
- pygeodesy/deprecated/classes.py +262 -0
- pygeodesy/deprecated/consterns.py +54 -0
- pygeodesy/deprecated/datum.py +40 -0
- pygeodesy/deprecated/functions.py +375 -0
- pygeodesy/deprecated/nvector.py +48 -0
- pygeodesy/deprecated/rhumbBase.py +32 -0
- pygeodesy/deprecated/rhumbaux.py +33 -0
- pygeodesy/deprecated/rhumbsolve.py +33 -0
- pygeodesy/deprecated/rhumbx.py +33 -0
- pygeodesy/dms.py +986 -0
- pygeodesy/ecef.py +1348 -0
- pygeodesy/elevations.py +279 -0
- pygeodesy/ellipsoidalBase.py +1224 -0
- pygeodesy/ellipsoidalBaseDI.py +913 -0
- pygeodesy/ellipsoidalExact.py +343 -0
- pygeodesy/ellipsoidalGeodSolve.py +343 -0
- pygeodesy/ellipsoidalKarney.py +403 -0
- pygeodesy/ellipsoidalNvector.py +685 -0
- pygeodesy/ellipsoidalVincenty.py +590 -0
- pygeodesy/ellipsoids.py +2476 -0
- pygeodesy/elliptic.py +1198 -0
- pygeodesy/epsg.py +243 -0
- pygeodesy/errors.py +804 -0
- pygeodesy/etm.py +1190 -0
- pygeodesy/fmath.py +1013 -0
- pygeodesy/formy.py +1818 -0
- pygeodesy/frechet.py +865 -0
- pygeodesy/fstats.py +760 -0
- pygeodesy/fsums.py +1898 -0
- pygeodesy/gars.py +358 -0
- pygeodesy/geodesicw.py +581 -0
- pygeodesy/geodesicx/_C4_24.py +1699 -0
- pygeodesy/geodesicx/_C4_27.py +2395 -0
- pygeodesy/geodesicx/_C4_30.py +3301 -0
- pygeodesy/geodesicx/__init__.py +48 -0
- pygeodesy/geodesicx/__main__.py +91 -0
- pygeodesy/geodesicx/gx.py +1382 -0
- pygeodesy/geodesicx/gxarea.py +535 -0
- pygeodesy/geodesicx/gxbases.py +154 -0
- pygeodesy/geodesicx/gxline.py +669 -0
- pygeodesy/geodsolve.py +426 -0
- pygeodesy/geohash.py +914 -0
- pygeodesy/geoids.py +1884 -0
- pygeodesy/hausdorff.py +892 -0
- pygeodesy/heights.py +1155 -0
- pygeodesy/interns.py +687 -0
- pygeodesy/iters.py +545 -0
- pygeodesy/karney.py +919 -0
- pygeodesy/ktm.py +633 -0
- pygeodesy/latlonBase.py +1766 -0
- pygeodesy/lazily.py +960 -0
- pygeodesy/lcc.py +684 -0
- pygeodesy/ltp.py +1107 -0
- pygeodesy/ltpTuples.py +1563 -0
- pygeodesy/mgrs.py +721 -0
- pygeodesy/named.py +1324 -0
- pygeodesy/namedTuples.py +683 -0
- pygeodesy/nvectorBase.py +695 -0
- pygeodesy/osgr.py +781 -0
- pygeodesy/points.py +1686 -0
- pygeodesy/props.py +628 -0
- pygeodesy/resections.py +1048 -0
- pygeodesy/rhumb/__init__.py +46 -0
- pygeodesy/rhumb/aux_.py +397 -0
- pygeodesy/rhumb/bases.py +1148 -0
- pygeodesy/rhumb/ekx.py +563 -0
- pygeodesy/rhumb/solve.py +572 -0
- pygeodesy/simplify.py +647 -0
- pygeodesy/solveBase.py +472 -0
- pygeodesy/sphericalBase.py +724 -0
- pygeodesy/sphericalNvector.py +1264 -0
- pygeodesy/sphericalTrigonometry.py +1447 -0
- pygeodesy/streprs.py +627 -0
- pygeodesy/trf.py +2079 -0
- pygeodesy/triaxials.py +1484 -0
- pygeodesy/units.py +969 -0
- pygeodesy/unitsBase.py +349 -0
- pygeodesy/ups.py +538 -0
- pygeodesy/utily.py +1231 -0
- pygeodesy/utm.py +762 -0
- pygeodesy/utmups.py +318 -0
- pygeodesy/utmupsBase.py +517 -0
- pygeodesy/vector2d.py +785 -0
- pygeodesy/vector3d.py +968 -0
- pygeodesy/vector3dBase.py +1049 -0
- pygeodesy/webmercator.py +383 -0
- pygeodesy/wgrs.py +439 -0
pygeodesy/hausdorff.py
ADDED
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''Hausdorff distances.
|
|
5
|
+
|
|
6
|
+
Classes L{Hausdorff}, L{HausdorffDegrees}, L{HausdorffRadians},
|
|
7
|
+
L{HausdorffCosineAndoyerLambert}, L{HausdorffCosineForsytheAndoyerLambert},
|
|
8
|
+
L{HausdorffCosineLaw}, L{HausdorffDistanceTo}, L{HausdorffEquirectangular},
|
|
9
|
+
L{HausdorffEuclidean}, L{HausdorffFlatLocal}, L{HausdorffFlatPolar},
|
|
10
|
+
L{HausdorffHaversine}, L{HausdorffHubeny}, L{HausdorffKarney},
|
|
11
|
+
L{HausdorffThomas} and L{HausdorffVincentys} to compute U{Hausdorff
|
|
12
|
+
<https://WikiPedia.org/wiki/Hausdorff_distance>} distances between two
|
|
13
|
+
sets of C{LatLon}, C{NumPy}, C{tuples} or other types of points.
|
|
14
|
+
|
|
15
|
+
Only L{HausdorffDistanceTo} -iff used with L{ellipsoidalKarney.LatLon}
|
|
16
|
+
points- and L{HausdorffKarney} requires installation of I{Charles Karney}'s
|
|
17
|
+
U{geographiclib<https://PyPI.org/project/geographiclib>}.
|
|
18
|
+
|
|
19
|
+
Typical usage is as follows. First, create a C{Hausdorff} calculator
|
|
20
|
+
from a given set of C{LatLon} points, called the C{model} or C{template}
|
|
21
|
+
points.
|
|
22
|
+
|
|
23
|
+
C{h = HausdorffXyz(point1s, ...)}
|
|
24
|
+
|
|
25
|
+
Get the C{directed} or C{symmetric} Hausdorff distance to a second set
|
|
26
|
+
of C{LatLon} points, named the C{target} points, by using
|
|
27
|
+
|
|
28
|
+
C{t6 = h.directed(point2s)}
|
|
29
|
+
|
|
30
|
+
respectively
|
|
31
|
+
|
|
32
|
+
C{t6 = h.symmetric(point2s)}.
|
|
33
|
+
|
|
34
|
+
Or, use function C{hausdorff_} with a proper C{distance} function and
|
|
35
|
+
optionally a C{point} function passed as keyword arguments as follows
|
|
36
|
+
|
|
37
|
+
C{t6 = hausdorff_(point1s, point2s, ..., distance=..., point=...)}.
|
|
38
|
+
|
|
39
|
+
In all cases, the returned result C{t6} is a L{Hausdorff6Tuple}.
|
|
40
|
+
|
|
41
|
+
For C{(lat, lon, ...)} points in a C{NumPy} array or plain C{tuples},
|
|
42
|
+
wrap the points in a L{Numpy2LatLon} respectively L{Tuple2LatLon}
|
|
43
|
+
instance, more details in the documentation thereof.
|
|
44
|
+
|
|
45
|
+
For other points, create a L{Hausdorff} sub-class with the appropriate
|
|
46
|
+
C{distance} method overloading L{Hausdorff.distance} and optionally a
|
|
47
|
+
C{point} method overriding L{Hausdorff.point} as the next example.
|
|
48
|
+
|
|
49
|
+
>>> from pygeodesy import Hausdorff, hypot_
|
|
50
|
+
>>>
|
|
51
|
+
>>> class H3D(Hausdorff):
|
|
52
|
+
>>> """Custom Hausdorff example.
|
|
53
|
+
>>> """
|
|
54
|
+
>>> def distance(self, p1, p2):
|
|
55
|
+
>>> return hypot_(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z)
|
|
56
|
+
>>>
|
|
57
|
+
>>> h3D = H3D(xyz1, ..., units="...")
|
|
58
|
+
>>> d6 = h3D.directed(xyz2)
|
|
59
|
+
|
|
60
|
+
Transcribed from the original SciPy U{Directed Hausdorff Code
|
|
61
|
+
<https://GitHub.com/scipy/scipy/blob/master/scipy/spatial/_hausdorff.pyx>}
|
|
62
|
+
version 0.19.0, Copyright (C) Tyler Reddy, Richard Gowers, and Max Linke,
|
|
63
|
+
2016, distributed under the same BSD license as SciPy, including C{early
|
|
64
|
+
breaking} and C{random sampling} as in U{Abdel Aziz Taha, Allan Hanbury
|
|
65
|
+
"An Efficient Algorithm for Calculating the Exact Hausdorff Distance"
|
|
66
|
+
<https://Publik.TUWien.ac.AT/files/PubDat_247739.pdf>}, IEEE Trans. Pattern
|
|
67
|
+
Analysis Machine Intelligence (PAMI), vol 37, no 11, pp 2153-2163, Nov 2015.
|
|
68
|
+
'''
|
|
69
|
+
|
|
70
|
+
from pygeodesy.constants import INF, NINF, _0_0
|
|
71
|
+
from pygeodesy.datums import _ellipsoidal_datum, _WGS84
|
|
72
|
+
from pygeodesy.errors import PointsError, _xattr, _xcallable, _xkwds, _xkwds_get
|
|
73
|
+
import pygeodesy.formy as _formy
|
|
74
|
+
from pygeodesy.interns import NN, _i_, _j_, _units_
|
|
75
|
+
# from pygeodesy.iters import points2 # from .points
|
|
76
|
+
from pygeodesy.lazily import _ALL_LAZY, _FOR_DOCS
|
|
77
|
+
from pygeodesy.named import _Named, _NamedTuple, notOverloaded, _Pass
|
|
78
|
+
# from pygeodesy.namedTuples import PhiLam2Tuple # from .points
|
|
79
|
+
from pygeodesy.points import _distanceTo, points2 as _points2, PhiLam2Tuple, radians
|
|
80
|
+
from pygeodesy.props import Property_RO, property_doc_, property_RO
|
|
81
|
+
from pygeodesy.units import Float, Number_, _xUnit, _xUnits
|
|
82
|
+
from pygeodesy.unitsBase import _Str_degrees, _Str_degrees2, _Str_meter, _Str_NN, \
|
|
83
|
+
_Str_radians, _Str_radians2
|
|
84
|
+
|
|
85
|
+
# from math import radians # from .points
|
|
86
|
+
from random import Random
|
|
87
|
+
|
|
88
|
+
__all__ = _ALL_LAZY.hausdorff
|
|
89
|
+
__version__ = '24.03.24'
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class HausdorffError(PointsError):
|
|
93
|
+
'''Hausdorff issue.
|
|
94
|
+
'''
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Hausdorff(_Named):
|
|
99
|
+
'''Hausdorff base class, requires method L{Hausdorff.distance} to
|
|
100
|
+
be overloaded.
|
|
101
|
+
'''
|
|
102
|
+
_datum = _WGS84
|
|
103
|
+
_func = None # formy function
|
|
104
|
+
_kwds = {} # func_ options
|
|
105
|
+
_model = ()
|
|
106
|
+
_seed = None
|
|
107
|
+
_units = _Str_NN # XXX Str to _Pass and for backward compatibility
|
|
108
|
+
|
|
109
|
+
def __init__(self, point1s, seed=None, name=NN, units=NN, **kwds):
|
|
110
|
+
'''New C{Hausdorff...} calculator.
|
|
111
|
+
|
|
112
|
+
@arg point1s: Initial set of points, aka the C{model} or
|
|
113
|
+
C{template} (C{LatLon}[], C{Numpy2LatLon}[],
|
|
114
|
+
C{Tuple2LatLon}[] or C{other}[]).
|
|
115
|
+
@kwarg seed: Random sampling seed (C{any}) or C{None}, C{0}
|
|
116
|
+
or C{False} for no U{random sampling<https://
|
|
117
|
+
Publik.TUWien.ac.AT/files/PubDat_247739.pdf>}.
|
|
118
|
+
@kwarg name: Optional name for this interpolator (C{str}).
|
|
119
|
+
@kwarg units: Optional, the distance units (C{Unit} or C{str}).
|
|
120
|
+
@kwarg kwds: Optional keyword argument for distance function,
|
|
121
|
+
retrievable with property C{kwds}.
|
|
122
|
+
|
|
123
|
+
@raise HausdorffError: Insufficient number of B{C{point1s}}
|
|
124
|
+
or an invalid B{C{point1}}, B{C{seed}}
|
|
125
|
+
or B{C{units}}.
|
|
126
|
+
'''
|
|
127
|
+
_, self._model = self._points2(point1s)
|
|
128
|
+
if seed:
|
|
129
|
+
self.seed = seed
|
|
130
|
+
if name:
|
|
131
|
+
self.name = name
|
|
132
|
+
if units: # and not self.units:
|
|
133
|
+
self.units = units
|
|
134
|
+
if kwds:
|
|
135
|
+
self._kwds = kwds
|
|
136
|
+
|
|
137
|
+
@Property_RO
|
|
138
|
+
def adjust(self):
|
|
139
|
+
'''Get the adjust setting (C{bool} or C{None} if not applicable).
|
|
140
|
+
'''
|
|
141
|
+
return _xkwds_get(self._kwds, adjust=None)
|
|
142
|
+
|
|
143
|
+
@Property_RO
|
|
144
|
+
def datum(self):
|
|
145
|
+
'''Get the datum of this calculator (L{Datum} or C{None} if not applicable).
|
|
146
|
+
'''
|
|
147
|
+
return self._datum
|
|
148
|
+
|
|
149
|
+
def _datum_setter(self, datum):
|
|
150
|
+
'''(INTERNAL) Set the datum.
|
|
151
|
+
'''
|
|
152
|
+
d = datum or _xattr(self._model[0], datum=datum)
|
|
153
|
+
if d not in (None, self._datum): # PYCHOK no cover
|
|
154
|
+
self._datum = _ellipsoidal_datum(d, name=self.name)
|
|
155
|
+
|
|
156
|
+
def directed(self, point2s, early=True):
|
|
157
|
+
'''Compute only the C{forward Hausdorff} distance.
|
|
158
|
+
|
|
159
|
+
@arg point2s: Second set of points, aka the C{target} (C{LatLon}[],
|
|
160
|
+
C{Numpy2LatLon}[], C{Tuple2LatLon}[] or C{other}[]).
|
|
161
|
+
@kwarg early: Enable or disable U{early breaking<https://
|
|
162
|
+
Publik.TUWien.ac.AT/files/PubDat_247739.pdf>} (C{bool}).
|
|
163
|
+
|
|
164
|
+
@return: A L{Hausdorff6Tuple}C{(hd, i, j, mn, md, units)}.
|
|
165
|
+
|
|
166
|
+
@raise HausdorffError: Insufficient number of B{C{point2s}} or
|
|
167
|
+
an invalid B{C{point2}}.
|
|
168
|
+
|
|
169
|
+
@note: See B{C{point2s}} note at L{HausdorffDistanceTo}.
|
|
170
|
+
'''
|
|
171
|
+
return self._hausdorff_(point2s, False, early, self.distance)
|
|
172
|
+
|
|
173
|
+
def distance(self, point1, point2):
|
|
174
|
+
'''Return the distance between B{C{point1}} and B{C{point2s}},
|
|
175
|
+
subject to the supplied optional keyword arguments, see
|
|
176
|
+
property C{kwds}.
|
|
177
|
+
'''
|
|
178
|
+
return self._func(point1.lat, point1.lon,
|
|
179
|
+
point2.lat, point2.lon, **self._kwds)
|
|
180
|
+
|
|
181
|
+
def _hausdorff_(self, point2s, both, early, distance):
|
|
182
|
+
_, ps2 = self._points2(point2s)
|
|
183
|
+
return _hausdorff_(self._model, ps2, both, early, self.seed,
|
|
184
|
+
self.units, distance, self.point)
|
|
185
|
+
|
|
186
|
+
@property_RO
|
|
187
|
+
def kwds(self):
|
|
188
|
+
'''Get the supplied, optional keyword arguments (C{dict}).
|
|
189
|
+
'''
|
|
190
|
+
return self._kwds
|
|
191
|
+
|
|
192
|
+
def point(self, point):
|
|
193
|
+
'''Convert a C{model} or C{target} point for the C{.distance} method.
|
|
194
|
+
'''
|
|
195
|
+
return point # pass thru
|
|
196
|
+
|
|
197
|
+
def _points2(self, points):
|
|
198
|
+
'''(INTERNAL) Check a set of points.
|
|
199
|
+
'''
|
|
200
|
+
return _points2(points, closed=False, Error=HausdorffError)
|
|
201
|
+
|
|
202
|
+
@property_doc_(''' the random sampling seed (C{Random}).''')
|
|
203
|
+
def seed(self):
|
|
204
|
+
'''Get the random sampling seed (C{any} or C{None}).
|
|
205
|
+
'''
|
|
206
|
+
return self._seed
|
|
207
|
+
|
|
208
|
+
@seed.setter # PYCHOK setter!
|
|
209
|
+
def seed(self, seed):
|
|
210
|
+
'''Set the random sampling seed (C{Random(seed)}) or
|
|
211
|
+
C{None}, C{0} or C{False} for no U{random sampling
|
|
212
|
+
<https://Publik.TUWien.ac.AT/files/PubDat_247739.pdf>}.
|
|
213
|
+
|
|
214
|
+
@raise HausdorffError: Invalid B{C{seed}}.
|
|
215
|
+
'''
|
|
216
|
+
if seed:
|
|
217
|
+
try:
|
|
218
|
+
Random(seed)
|
|
219
|
+
except (TypeError, ValueError) as x:
|
|
220
|
+
raise HausdorffError(seed=seed, cause=x)
|
|
221
|
+
self._seed = seed
|
|
222
|
+
else:
|
|
223
|
+
self._seed = None
|
|
224
|
+
|
|
225
|
+
def symmetric(self, point2s, early=True):
|
|
226
|
+
'''Compute the combined C{forward and reverse Hausdorff} distance.
|
|
227
|
+
|
|
228
|
+
@arg point2s: Second set of points, aka the C{target} (C{LatLon}[],
|
|
229
|
+
C{Numpy2LatLon}[], C{Tuple2LatLon}[] or C{other}[]).
|
|
230
|
+
@kwarg early: Enable or disable U{early breaking<https://
|
|
231
|
+
Publik.TUWien.ac.AT/files/PubDat_247739.pdf>} (C{bool}).
|
|
232
|
+
|
|
233
|
+
@return: A L{Hausdorff6Tuple}C{(hd, i, j, mn, md, units)}.
|
|
234
|
+
|
|
235
|
+
@raise HausdorffError: Insufficient number of B{C{point2s}} or
|
|
236
|
+
an invalid B{C{point2}}.
|
|
237
|
+
|
|
238
|
+
@note: See B{C{point2s}} note at L{HausdorffDistanceTo}.
|
|
239
|
+
'''
|
|
240
|
+
return self._hausdorff_(point2s, True, early, self.distance)
|
|
241
|
+
|
|
242
|
+
@property_doc_(''' the distance units (C{Unit} or C{str}).''')
|
|
243
|
+
def units(self):
|
|
244
|
+
'''Get the distance units (C{Unit} or C{str}).
|
|
245
|
+
'''
|
|
246
|
+
return self._units
|
|
247
|
+
|
|
248
|
+
@units.setter # PYCHOK setter!
|
|
249
|
+
def units(self, units):
|
|
250
|
+
'''Set the distance units (C{Unit} or C{str}).
|
|
251
|
+
|
|
252
|
+
@raise TypeError: Invalid B{C{units}}.
|
|
253
|
+
'''
|
|
254
|
+
self._units = _xUnits(units, Base=Float)
|
|
255
|
+
|
|
256
|
+
@Property_RO
|
|
257
|
+
def wrap(self):
|
|
258
|
+
'''Get the wrap setting (C{bool} or C{None} if not applicable).
|
|
259
|
+
'''
|
|
260
|
+
return _xkwds_get(self._kwds, adjust=None)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class HausdorffDegrees(Hausdorff):
|
|
264
|
+
'''L{Hausdorff} base class for distances from C{LatLon}
|
|
265
|
+
points in C{degrees}.
|
|
266
|
+
'''
|
|
267
|
+
_units = _Str_degrees
|
|
268
|
+
|
|
269
|
+
if _FOR_DOCS:
|
|
270
|
+
__init__ = Hausdorff.__init__
|
|
271
|
+
directed = Hausdorff.directed
|
|
272
|
+
symmetric = Hausdorff.symmetric
|
|
273
|
+
|
|
274
|
+
def distance(self, point1, point2): # PYCHOK no cover
|
|
275
|
+
'''I{Must be overloaded}.'''
|
|
276
|
+
notOverloaded(self, point1, point2)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class HausdorffRadians(Hausdorff):
|
|
280
|
+
'''L{Hausdorff} base class for distances from C{LatLon}
|
|
281
|
+
points converted from C{degrees} to C{radians}.
|
|
282
|
+
'''
|
|
283
|
+
_units = _Str_radians
|
|
284
|
+
|
|
285
|
+
if _FOR_DOCS:
|
|
286
|
+
__init__ = Hausdorff.__init__
|
|
287
|
+
directed = Hausdorff.directed
|
|
288
|
+
symmetric = Hausdorff.symmetric
|
|
289
|
+
|
|
290
|
+
def distance(self, point1, point2): # PYCHOK no cover
|
|
291
|
+
'''Return the distance in C{radians} between B{C{point1}} and B{C{point2}}.
|
|
292
|
+
I{Must be overloaded}.'''
|
|
293
|
+
notOverloaded(self, point1, point2)
|
|
294
|
+
|
|
295
|
+
def point(self, point):
|
|
296
|
+
'''Return B{C{point}} as L{PhiLam2Tuple} to maintain
|
|
297
|
+
I{backward compatibility} of L{HausdorffRadians}.
|
|
298
|
+
|
|
299
|
+
@return: A L{PhiLam2Tuple}C{(phi, lam)}.
|
|
300
|
+
'''
|
|
301
|
+
try:
|
|
302
|
+
return point.philam
|
|
303
|
+
except AttributeError:
|
|
304
|
+
return PhiLam2Tuple(radians(point.lat), radians(point.lon))
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class _HausdorffMeterRadians(Hausdorff):
|
|
308
|
+
'''(INTERNAL) Returning C{meter} or C{radians} depending on
|
|
309
|
+
the optional keyword arguments supplied at instantiation
|
|
310
|
+
of the C{Hausdorff*} sub-class.
|
|
311
|
+
'''
|
|
312
|
+
_units = _Str_meter
|
|
313
|
+
_units_ = _Str_radians
|
|
314
|
+
|
|
315
|
+
def directed(self, point2s, early=True):
|
|
316
|
+
'''Overloaded method L{Hausdorff.directed} to determine
|
|
317
|
+
the distance function and units from the optional
|
|
318
|
+
keyword arguments given at this instantiation, see
|
|
319
|
+
property C{kwds}.
|
|
320
|
+
|
|
321
|
+
@see: L{Hausdorff.directed} for other details.
|
|
322
|
+
'''
|
|
323
|
+
return self._hausdorff_(point2s, False, early, _formy._radistance(self))
|
|
324
|
+
|
|
325
|
+
def symmetric(self, point2s, early=True):
|
|
326
|
+
'''Overloaded method L{Hausdorff.symmetric} to determine
|
|
327
|
+
the distance function and units from the optional
|
|
328
|
+
keyword arguments given at this instantiation, see
|
|
329
|
+
property C{kwds}.
|
|
330
|
+
|
|
331
|
+
@see: L{Hausdorff.symmetric} for other details.
|
|
332
|
+
'''
|
|
333
|
+
return self._hausdorff_(point2s, True, early, _formy._radistance(self))
|
|
334
|
+
|
|
335
|
+
def _func_(self, *args, **kwds): # PYCHOK no cover
|
|
336
|
+
'''(INTERNAL) I{Must be overloaded}.'''
|
|
337
|
+
notOverloaded(self, *args, **kwds)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class HausdorffCosineAndoyerLambert(_HausdorffMeterRadians):
|
|
341
|
+
'''Compute the C{Hausdorff} distance based on the I{angular} distance
|
|
342
|
+
in C{radians} from function L{pygeodesy.cosineAndoyerLambert}.
|
|
343
|
+
'''
|
|
344
|
+
def __init__(self, point1s, seed=None, name=NN, **datum_wrap):
|
|
345
|
+
'''New L{HausdorffCosineAndoyerLambert} calculator.
|
|
346
|
+
|
|
347
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
348
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
349
|
+
|
|
350
|
+
@kwarg datum_wrap: Optional keyword arguments for function
|
|
351
|
+
L{pygeodesy.cosineAndoyerLambert}.
|
|
352
|
+
'''
|
|
353
|
+
Hausdorff.__init__(self, point1s, seed=seed, name=name,
|
|
354
|
+
**datum_wrap)
|
|
355
|
+
self._func = _formy.cosineAndoyerLambert
|
|
356
|
+
self._func_ = _formy.cosineAndoyerLambert_
|
|
357
|
+
|
|
358
|
+
if _FOR_DOCS:
|
|
359
|
+
directed = Hausdorff.directed
|
|
360
|
+
symmetric = Hausdorff.symmetric
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class HausdorffCosineForsytheAndoyerLambert(_HausdorffMeterRadians):
|
|
364
|
+
'''Compute the C{Hausdorff} distance based on the I{angular} distance
|
|
365
|
+
in C{radians} from function L{pygeodesy.cosineForsytheAndoyerLambert}.
|
|
366
|
+
'''
|
|
367
|
+
def __init__(self, point1s, seed=None, name=NN, **datum_wrap):
|
|
368
|
+
'''New L{HausdorffCosineForsytheAndoyerLambert} calculator.
|
|
369
|
+
|
|
370
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
371
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
372
|
+
|
|
373
|
+
@kwarg datum_wrap: Optional keyword arguments for function
|
|
374
|
+
L{pygeodesy.cosineAndoyerLambert}.
|
|
375
|
+
'''
|
|
376
|
+
Hausdorff.__init__(self, point1s, seed=seed, name=name,
|
|
377
|
+
**datum_wrap)
|
|
378
|
+
self._func = _formy.cosineForsytheAndoyerLambert
|
|
379
|
+
self._func_ = _formy.cosineForsytheAndoyerLambert_
|
|
380
|
+
|
|
381
|
+
if _FOR_DOCS:
|
|
382
|
+
directed = Hausdorff.directed
|
|
383
|
+
symmetric = Hausdorff.symmetric
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class HausdorffCosineLaw(_HausdorffMeterRadians):
|
|
387
|
+
'''Compute the C{Hausdorff} distance based on the I{angular}
|
|
388
|
+
distance in C{radians} from function L{pygeodesy.cosineLaw_}.
|
|
389
|
+
|
|
390
|
+
@note: See note at function L{pygeodesy.vincentys_}.
|
|
391
|
+
'''
|
|
392
|
+
def __init__(self, point1s, seed=None, name=NN, **radius_wrap):
|
|
393
|
+
'''New L{HausdorffCosineLaw} calculator.
|
|
394
|
+
|
|
395
|
+
@kwarg radius_wrap: Optional keyword arguments for function
|
|
396
|
+
L{pygeodesy.cosineLaw}.
|
|
397
|
+
|
|
398
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
399
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
400
|
+
'''
|
|
401
|
+
Hausdorff.__init__(self, point1s, seed=seed, name=name,
|
|
402
|
+
**radius_wrap)
|
|
403
|
+
self._func = _formy.cosineLaw
|
|
404
|
+
self._func_ = _formy.cosineLaw_
|
|
405
|
+
|
|
406
|
+
if _FOR_DOCS:
|
|
407
|
+
directed = Hausdorff.directed
|
|
408
|
+
symmetric = Hausdorff.symmetric
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class HausdorffDistanceTo(Hausdorff):
|
|
412
|
+
'''Compute the C{Hausdorff} distance based on the distance from the
|
|
413
|
+
points' C{LatLon.distanceTo} method, conventionally in C{meter}.
|
|
414
|
+
'''
|
|
415
|
+
_units = _Str_meter
|
|
416
|
+
|
|
417
|
+
def __init__(self, point1s, seed=None, name=NN, **distanceTo_kwds):
|
|
418
|
+
'''New L{HausdorffDistanceTo} calculator.
|
|
419
|
+
|
|
420
|
+
@kwarg distanceTo_kwds: Optional keyword arguments for each
|
|
421
|
+
B{C{point1s}}' C{LatLon.distanceTo}
|
|
422
|
+
method.
|
|
423
|
+
|
|
424
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
425
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
426
|
+
|
|
427
|
+
@note: All C{model}, C{template} and C{target} B{C{points}}
|
|
428
|
+
I{must} be instances of the same ellipsoidal or
|
|
429
|
+
spherical C{LatLon} class.
|
|
430
|
+
'''
|
|
431
|
+
Hausdorff.__init__(self, point1s, seed=seed, name=name,
|
|
432
|
+
**distanceTo_kwds)
|
|
433
|
+
|
|
434
|
+
if _FOR_DOCS:
|
|
435
|
+
directed = Hausdorff.directed
|
|
436
|
+
symmetric = Hausdorff.symmetric
|
|
437
|
+
|
|
438
|
+
def distance(self, p1, p2):
|
|
439
|
+
'''Return the distance in C{meter}.
|
|
440
|
+
'''
|
|
441
|
+
return p1.distanceTo(p2, **self._kwds)
|
|
442
|
+
|
|
443
|
+
def _points2(self, points):
|
|
444
|
+
'''(INTERNAL) Check a set of points.
|
|
445
|
+
'''
|
|
446
|
+
np, ps = Hausdorff._points2(self, points)
|
|
447
|
+
return np, _distanceTo(HausdorffError, points=ps)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
class HausdorffEquirectangular(Hausdorff):
|
|
451
|
+
'''Compute the C{Hausdorff} distance based on the C{equirectangular} distance
|
|
452
|
+
in C{radians squared} like function L{pygeodesy.equirectangular_}.
|
|
453
|
+
'''
|
|
454
|
+
_units = _Str_degrees2
|
|
455
|
+
|
|
456
|
+
def __init__(self, point1s, seed=None, name=NN, **adjust_limit_wrap):
|
|
457
|
+
'''New L{HausdorffEquirectangular} calculator.
|
|
458
|
+
|
|
459
|
+
@kwarg adjust_limit_wrap: Optional keyword arguments for function
|
|
460
|
+
L{pygeodesy.equirectangular_} I{with default}
|
|
461
|
+
C{B{limit}=0} for I{backward compatibility}.
|
|
462
|
+
|
|
463
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
464
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
465
|
+
'''
|
|
466
|
+
adjust_limit_wrap = _xkwds(adjust_limit_wrap, limit=0)
|
|
467
|
+
Hausdorff.__init__(self, point1s, seed=seed, name=name,
|
|
468
|
+
**adjust_limit_wrap)
|
|
469
|
+
self._func = _formy._equirectangular # helper
|
|
470
|
+
|
|
471
|
+
if _FOR_DOCS:
|
|
472
|
+
directed = Hausdorff.directed
|
|
473
|
+
symmetric = Hausdorff.symmetric
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
class HausdorffEuclidean(_HausdorffMeterRadians):
|
|
477
|
+
'''Compute the C{Hausdorff} distance based on the C{Euclidean}
|
|
478
|
+
distance in C{radians} from function L{pygeodesy.euclidean_}.
|
|
479
|
+
'''
|
|
480
|
+
def __init__(self, point1s, seed=None, name=NN, **adjust_wrap):
|
|
481
|
+
'''New L{HausdorffEuclidean} calculator.
|
|
482
|
+
|
|
483
|
+
@kwarg adjust_radius_wrap: Optional keyword arguments for
|
|
484
|
+
function L{pygeodesy.euclidean}.
|
|
485
|
+
|
|
486
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
487
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
488
|
+
'''
|
|
489
|
+
Hausdorff.__init__(self, point1s, seed=seed, name=name,
|
|
490
|
+
**adjust_wrap)
|
|
491
|
+
self._func = _formy.euclidean
|
|
492
|
+
self._func_ = _formy.euclidean_
|
|
493
|
+
|
|
494
|
+
if _FOR_DOCS:
|
|
495
|
+
directed = Hausdorff.directed
|
|
496
|
+
symmetric = Hausdorff.symmetric
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class HausdorffExact(Hausdorff):
|
|
500
|
+
'''Compute the C{Hausdorff} distance based on the I{angular}
|
|
501
|
+
distance in C{degrees} from method L{GeodesicExact}C{.Inverse}.
|
|
502
|
+
'''
|
|
503
|
+
_units = _Str_degrees
|
|
504
|
+
|
|
505
|
+
def __init__(self, point1s, seed=None, name=NN, datum=None, **wrap):
|
|
506
|
+
'''New L{HausdorffKarney} calculator.
|
|
507
|
+
|
|
508
|
+
@kwarg datum: Datum to override the default C{Datums.WGS84} and
|
|
509
|
+
first B{C{point1s}}' datum (L{Datum}, L{Ellipsoid},
|
|
510
|
+
L{Ellipsoid2} or L{a_f2Tuple}).
|
|
511
|
+
@kwarg wrap: Optional keyword argument for method C{Inverse1}
|
|
512
|
+
of class L{geodesicx.GeodesicExact}.
|
|
513
|
+
|
|
514
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
515
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
516
|
+
|
|
517
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
518
|
+
'''
|
|
519
|
+
Hausdorff.__init__(self, point1s, seed=seed, name=name,
|
|
520
|
+
**wrap)
|
|
521
|
+
self._datum_setter(datum)
|
|
522
|
+
self._func = self.datum.ellipsoid.geodesicx.Inverse1 # note -x
|
|
523
|
+
|
|
524
|
+
if _FOR_DOCS:
|
|
525
|
+
directed = Hausdorff.directed
|
|
526
|
+
symmetric = Hausdorff.symmetric
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
class HausdorffFlatLocal(_HausdorffMeterRadians):
|
|
530
|
+
'''Compute the C{Hausdorff} distance based on the I{angular} distance in
|
|
531
|
+
C{radians squared} like function L{pygeodesy.flatLocal_}/L{pygeodesy.hubeny_}.
|
|
532
|
+
'''
|
|
533
|
+
_units = _Str_radians2
|
|
534
|
+
|
|
535
|
+
def __init__(self, point1s, seed=None, name=NN, **datum_scaled_wrap):
|
|
536
|
+
'''New L{HausdorffFlatLocal}/L{HausdorffHubeny} calculator.
|
|
537
|
+
|
|
538
|
+
@kwarg datum_scaled_wrap: Optional keyword arguments for
|
|
539
|
+
function L{pygeodesy.flatLocal}.
|
|
540
|
+
|
|
541
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
542
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
543
|
+
|
|
544
|
+
@note: The distance C{units} are C{radians squared}, not C{radians}.
|
|
545
|
+
'''
|
|
546
|
+
Hausdorff.__init__(self, point1s, seed=seed, name=name,
|
|
547
|
+
**datum_scaled_wrap)
|
|
548
|
+
self._func = _formy.flatLocal
|
|
549
|
+
self._func_ = self.datum.ellipsoid._hubeny_2
|
|
550
|
+
|
|
551
|
+
if _FOR_DOCS:
|
|
552
|
+
directed = Hausdorff.directed
|
|
553
|
+
symmetric = Hausdorff.symmetric
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
class HausdorffFlatPolar(_HausdorffMeterRadians):
|
|
557
|
+
'''Compute the C{Hausdorff} distance based on the I{angular}
|
|
558
|
+
distance in C{radians} from function L{pygeodesy.flatPolar_}.
|
|
559
|
+
'''
|
|
560
|
+
_wrap = False
|
|
561
|
+
|
|
562
|
+
def __init__(self, points, seed=None, name=NN, **radius_wrap):
|
|
563
|
+
'''New L{HausdorffFlatPolar} calculator.
|
|
564
|
+
|
|
565
|
+
@kwarg radius_wrap: Optional keyword arguments for function
|
|
566
|
+
L{pygeodesy.flatPolar}.
|
|
567
|
+
|
|
568
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
569
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
570
|
+
'''
|
|
571
|
+
Hausdorff.__init__(self, points, seed=seed, name=name,
|
|
572
|
+
**radius_wrap)
|
|
573
|
+
self._func = _formy.flatPolar
|
|
574
|
+
self._func_ = _formy.flatPolar_
|
|
575
|
+
|
|
576
|
+
if _FOR_DOCS:
|
|
577
|
+
directed = Hausdorff.directed
|
|
578
|
+
symmetric = Hausdorff.symmetric
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
class HausdorffHaversine(_HausdorffMeterRadians):
|
|
582
|
+
'''Compute the C{Hausdorff} distance based on the I{angular}
|
|
583
|
+
distance in C{radians} from function L{pygeodesy.haversine_}.
|
|
584
|
+
|
|
585
|
+
@note: See note under L{HausdorffVincentys}.
|
|
586
|
+
'''
|
|
587
|
+
_wrap = False
|
|
588
|
+
|
|
589
|
+
def __init__(self, points, seed=None, name=NN, **radius_wrap):
|
|
590
|
+
'''New L{HausdorffHaversine} calculator.
|
|
591
|
+
|
|
592
|
+
@kwarg radius_wrap: Optional keyword arguments for function
|
|
593
|
+
L{pygeodesy.haversine}.
|
|
594
|
+
|
|
595
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
596
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
597
|
+
'''
|
|
598
|
+
Hausdorff.__init__(self, points, seed=seed, name=name,
|
|
599
|
+
**radius_wrap)
|
|
600
|
+
self._func = _formy.haversine
|
|
601
|
+
self._func_ = _formy.haversine_
|
|
602
|
+
|
|
603
|
+
if _FOR_DOCS:
|
|
604
|
+
directed = Hausdorff.directed
|
|
605
|
+
symmetric = Hausdorff.symmetric
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
class HausdorffHubeny(HausdorffFlatLocal): # for Karl Hubeny
|
|
609
|
+
if _FOR_DOCS:
|
|
610
|
+
__doc__ = HausdorffFlatLocal.__doc__
|
|
611
|
+
__init__ = HausdorffFlatLocal.__init__
|
|
612
|
+
directed = HausdorffFlatLocal.directed
|
|
613
|
+
distance = HausdorffFlatLocal.distance
|
|
614
|
+
symmetric = HausdorffFlatLocal.symmetric
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
class HausdorffKarney(Hausdorff):
|
|
618
|
+
'''Compute the C{Hausdorff} distance based on the I{angular}
|
|
619
|
+
distance in C{degrees} from I{Karney}'s U{geographiclib
|
|
620
|
+
<https://PyPI.org/project/geographiclib>} U{Geodesic
|
|
621
|
+
<https://GeographicLib.SourceForge.io/Python/doc/code.html>}
|
|
622
|
+
Inverse method.
|
|
623
|
+
'''
|
|
624
|
+
_units = _Str_degrees
|
|
625
|
+
|
|
626
|
+
def __init__(self, point1s, datum=None, seed=None, name=NN, **wrap):
|
|
627
|
+
'''New L{HausdorffKarney} calculator.
|
|
628
|
+
|
|
629
|
+
@kwarg datum: Datum to override the default C{Datums.WGS84} and
|
|
630
|
+
first B{C{knots}}' datum (L{Datum}, L{Ellipsoid},
|
|
631
|
+
L{Ellipsoid2} or L{a_f2Tuple}).
|
|
632
|
+
@kwarg wrap: Optional keyword argument for method C{Inverse1}
|
|
633
|
+
of class L{geodesicw.Geodesic}.
|
|
634
|
+
|
|
635
|
+
@raise ImportError: Package U{geographiclib
|
|
636
|
+
<https://PyPI.org/project/geographiclib>} missing.
|
|
637
|
+
|
|
638
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
639
|
+
|
|
640
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
641
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
642
|
+
'''
|
|
643
|
+
Hausdorff.__init__(self, point1s, seed=seed, name=name,
|
|
644
|
+
**wrap)
|
|
645
|
+
self._datum_setter(datum)
|
|
646
|
+
self._func = self.datum.ellipsoid.geodesic.Inverse1
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
class HausdorffThomas(_HausdorffMeterRadians):
|
|
650
|
+
'''Compute the C{Hausdorff} distance based on the I{angular}
|
|
651
|
+
distance in C{radians} from function L{pygeodesy.thomas_}.
|
|
652
|
+
'''
|
|
653
|
+
def __init__(self, point1s, seed=None, name=NN, **datum_wrap):
|
|
654
|
+
'''New L{HausdorffThomas} calculator.
|
|
655
|
+
|
|
656
|
+
@kwarg datum_wrap: Optional keyword argument for function
|
|
657
|
+
L{pygeodesy.thomas}.
|
|
658
|
+
|
|
659
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
660
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
661
|
+
'''
|
|
662
|
+
Hausdorff.__init__(self, point1s, seed=seed, name=name,
|
|
663
|
+
**datum_wrap)
|
|
664
|
+
self._func = _formy.thomas
|
|
665
|
+
self._func_ = _formy.thomas_
|
|
666
|
+
|
|
667
|
+
if _FOR_DOCS:
|
|
668
|
+
directed = Hausdorff.directed
|
|
669
|
+
symmetric = Hausdorff.symmetric
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
class HausdorffVincentys(_HausdorffMeterRadians):
|
|
673
|
+
'''Compute the C{Hausdorff} distance based on the I{angular}
|
|
674
|
+
distance in C{radians} from function L{pygeodesy.vincentys_}.
|
|
675
|
+
|
|
676
|
+
@note: See note at function L{pygeodesy.vincentys_}.
|
|
677
|
+
'''
|
|
678
|
+
_wrap = False
|
|
679
|
+
|
|
680
|
+
def __init__(self, point1s, seed=None, name=NN, **radius_wrap):
|
|
681
|
+
'''New L{HausdorffVincentys} calculator.
|
|
682
|
+
|
|
683
|
+
@kwarg radius_wrap: Optional keyword arguments for function
|
|
684
|
+
L{pygeodesy.vincentys}.
|
|
685
|
+
|
|
686
|
+
@see: L{Hausdorff.__init__} for details about B{C{point1s}},
|
|
687
|
+
B{C{seed}}, B{C{name}} and other exceptions.
|
|
688
|
+
'''
|
|
689
|
+
Hausdorff.__init__(self, point1s, seed=seed, name=name,
|
|
690
|
+
**radius_wrap)
|
|
691
|
+
self._func = _formy.vincentys
|
|
692
|
+
self._func_ = _formy.vincentys_
|
|
693
|
+
|
|
694
|
+
if _FOR_DOCS:
|
|
695
|
+
directed = Hausdorff.directed
|
|
696
|
+
symmetric = Hausdorff.symmetric
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _hausdorff_(ps1, ps2, both, early, seed, units, distance, point):
|
|
700
|
+
'''(INTERNAL) Core of function L{hausdorff_} and methods C{directed}
|
|
701
|
+
and C{symmetric} of classes C{hausdorff.Hausdorff...}.
|
|
702
|
+
'''
|
|
703
|
+
# shuffling the points generally increases the
|
|
704
|
+
# chance of an early break in the inner j loop
|
|
705
|
+
rr = randomrangenerator(seed) if seed else range
|
|
706
|
+
|
|
707
|
+
hd = NINF
|
|
708
|
+
hi = hj = m = mn = 0
|
|
709
|
+
md = _0_0
|
|
710
|
+
|
|
711
|
+
# forward or forward and backward
|
|
712
|
+
for fb in range(2 if both else 1):
|
|
713
|
+
n = len(ps2)
|
|
714
|
+
for i in rr(len(ps1)):
|
|
715
|
+
p1 = point(ps1[i])
|
|
716
|
+
dh, dj = INF, 0
|
|
717
|
+
for j in rr(n):
|
|
718
|
+
p2 = point(ps2[j])
|
|
719
|
+
d = distance(p1, p2)
|
|
720
|
+
if early and d < hd:
|
|
721
|
+
break # early
|
|
722
|
+
elif d < dh:
|
|
723
|
+
dh, dj = d, j
|
|
724
|
+
else: # no early break
|
|
725
|
+
if hd < dh:
|
|
726
|
+
hd = dh
|
|
727
|
+
if fb:
|
|
728
|
+
hi, hj = dj, i
|
|
729
|
+
else:
|
|
730
|
+
hi, hj = i, dj
|
|
731
|
+
md += dh
|
|
732
|
+
mn += 1
|
|
733
|
+
m += 1
|
|
734
|
+
# swap model and target
|
|
735
|
+
ps1, ps2 = ps2, ps1
|
|
736
|
+
|
|
737
|
+
md = None if mn < m else (md / float(m))
|
|
738
|
+
return Hausdorff6Tuple(hd, hi, hj, m, md, units)
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
def _point(p):
|
|
742
|
+
'''Default B{C{point}} callable for function L{hausdorff_}.
|
|
743
|
+
|
|
744
|
+
@arg p: The original C{model} or C{target} point (C{any}).
|
|
745
|
+
|
|
746
|
+
@return: The point, suitable for the L{hausdorff_}
|
|
747
|
+
B{C{distance}} callable.
|
|
748
|
+
'''
|
|
749
|
+
return p
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def hausdorff_(model, target, both=False, early=True, seed=None, units=NN,
|
|
753
|
+
distance=None, point=_point):
|
|
754
|
+
'''Compute the C{directed} or C{symmetric} U{Hausdorff
|
|
755
|
+
<https://WikiPedia.org/wiki/Hausdorff_distance>} distance between 2 sets of points
|
|
756
|
+
with or without U{early breaking<https://Publik.TUWien.ac.AT/files/PubDat_247739.pdf>}
|
|
757
|
+
and U{random sampling<https://Publik.TUWien.ac.AT/files/PubDat_247739.pdf>}.
|
|
758
|
+
|
|
759
|
+
@arg model: First set of points (C{LatLon}[], C{Numpy2LatLon}[],
|
|
760
|
+
C{Tuple2LatLon}[] or C{other}[]).
|
|
761
|
+
@arg target: Second set of points (C{LatLon}[], C{Numpy2LatLon}[],
|
|
762
|
+
C{Tuple2LatLon}[] or C{other}[]).
|
|
763
|
+
@kwarg both: Return the C{directed} (forward only) or the C{symmetric}
|
|
764
|
+
(combined forward and reverse) C{Hausdorff} distance (C{bool}).
|
|
765
|
+
@kwarg early: Enable or disable U{early breaking<https://Publik.TUWien.ac.AT/
|
|
766
|
+
files/PubDat_247739.pdf>} (C{bool}).
|
|
767
|
+
@kwarg seed: Random sampling seed (C{any}) or C{None}, C{0} or C{False} for no
|
|
768
|
+
U{random sampling<https://Publik.TUWien.ac.AT/files/PubDat_247739.pdf>}.
|
|
769
|
+
@kwarg units: Optional, the distance units (C{Unit} or C{str}).
|
|
770
|
+
@kwarg distance: Callable returning the distance between a B{C{model}}
|
|
771
|
+
and B{C{target}} point (signature C{(point1, point2)}).
|
|
772
|
+
@kwarg point: Callable returning the B{C{model}} or B{C{target}} point
|
|
773
|
+
suitable for B{C{distance}} (signature C{(point)}).
|
|
774
|
+
|
|
775
|
+
@return: A L{Hausdorff6Tuple}C{(hd, i, j, mn, md, units)}.
|
|
776
|
+
|
|
777
|
+
@raise HausdorffError: Insufficient number of B{C{model}} or B{C{target}} points.
|
|
778
|
+
|
|
779
|
+
@raise TypeError: If B{C{distance}} or B{C{point}} is not callable.
|
|
780
|
+
'''
|
|
781
|
+
_xcallable(distance=distance, point=point)
|
|
782
|
+
|
|
783
|
+
_, ps1 = _points2(model, closed=False, Error=HausdorffError) # PYCHOK non-sequence
|
|
784
|
+
_, ps2 = _points2(target, closed=False, Error=HausdorffError) # PYCHOK non-sequence
|
|
785
|
+
return _hausdorff_(ps1, ps2, both, early, seed, units, distance, point)
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
class Hausdorff6Tuple(_NamedTuple):
|
|
789
|
+
'''6-Tuple C{(hd, i, j, mn, md, units)} with the U{Hausdorff
|
|
790
|
+
<https://WikiPedia.org/wiki/Hausdorff_distance>} distance C{hd},
|
|
791
|
+
indices C{i} and C{j}, the total count C{mn}, the C{I{mean}
|
|
792
|
+
Hausdorff} distance C{md} and the class or name of both distance
|
|
793
|
+
C{units}.
|
|
794
|
+
|
|
795
|
+
For C{directed Hausdorff} distances, count C{mn} is the number
|
|
796
|
+
of model points considered. For C{symmetric Hausdorff} distances
|
|
797
|
+
count C{mn} twice that.
|
|
798
|
+
|
|
799
|
+
Indices C{i} and C{j} are the C{model} respectively C{target}
|
|
800
|
+
point with the C{hd} distance.
|
|
801
|
+
|
|
802
|
+
Mean distance C{md} is C{None} if an C{early break} occurred and
|
|
803
|
+
U{early breaking<https://Publik.TUWien.ac.AT/files/PubDat_247739.pdf>}
|
|
804
|
+
was enabled by keyword argument C{early=True}.
|
|
805
|
+
'''
|
|
806
|
+
_Names_ = ('hd', _i_, _j_, 'mn', 'md', _units_)
|
|
807
|
+
_Units_ = (_Pass, Number_, Number_, Number_, _Pass, _Pass)
|
|
808
|
+
|
|
809
|
+
def toUnits(self, **Error): # PYCHOK expected
|
|
810
|
+
'''Overloaded C{_NamedTuple.toUnits} for C{hd} and C{md} units.
|
|
811
|
+
'''
|
|
812
|
+
U = _xUnit(self.units, Float) # PYCHOK expected
|
|
813
|
+
M = _Pass if self.md is None else U # PYCHOK expected
|
|
814
|
+
self._Units_ = (U,) + Hausdorff6Tuple._Units_[1:4] \
|
|
815
|
+
+ (M,) + Hausdorff6Tuple._Units_[5:]
|
|
816
|
+
return _NamedTuple.toUnits(self, **Error)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def randomrangenerator(seed):
|
|
820
|
+
'''Return a C{seed}ed random range function generator.
|
|
821
|
+
|
|
822
|
+
@arg seed: Initial, internal L{Random} state (C{hashable}
|
|
823
|
+
or C{None}).
|
|
824
|
+
|
|
825
|
+
@note: L{Random} with C{B{seed} is None} seeds from the
|
|
826
|
+
current time or from a platform-specific randomness
|
|
827
|
+
source, if available.
|
|
828
|
+
|
|
829
|
+
@return: A function to generate random ranges.
|
|
830
|
+
|
|
831
|
+
@example:
|
|
832
|
+
|
|
833
|
+
>>> rrange = randomrangenerator('R')
|
|
834
|
+
>>> for r in rrange(n):
|
|
835
|
+
>>> ... # r is random in 0..n-1
|
|
836
|
+
'''
|
|
837
|
+
R = Random(seed)
|
|
838
|
+
|
|
839
|
+
def _range(n, *stop_step):
|
|
840
|
+
'''Like standard L{range}C{start, stop=..., step=...)},
|
|
841
|
+
except the returned values are in random order.
|
|
842
|
+
|
|
843
|
+
@note: Especially C{range(n)} behaves like standard
|
|
844
|
+
L{Random.sample}C{(range(n), n)} but avoids
|
|
845
|
+
creating a tuple with the entire C{population}
|
|
846
|
+
and a list containing all sample values (for
|
|
847
|
+
large C{n}).
|
|
848
|
+
'''
|
|
849
|
+
if stop_step:
|
|
850
|
+
s = range(n, *stop_step)
|
|
851
|
+
|
|
852
|
+
elif n > 32:
|
|
853
|
+
r = R.randrange # Random._randbelow
|
|
854
|
+
s = set()
|
|
855
|
+
for _ in range(n - 32):
|
|
856
|
+
i = r(n)
|
|
857
|
+
while i in s:
|
|
858
|
+
i = r(n)
|
|
859
|
+
s.add(i)
|
|
860
|
+
yield i
|
|
861
|
+
s = set(range(n)) - s # [i for i in range(n) if i not in s]
|
|
862
|
+
else:
|
|
863
|
+
s = range(n)
|
|
864
|
+
|
|
865
|
+
s = list(s)
|
|
866
|
+
R.shuffle(s)
|
|
867
|
+
while s:
|
|
868
|
+
yield s.pop(0)
|
|
869
|
+
|
|
870
|
+
return _range
|
|
871
|
+
|
|
872
|
+
# **) MIT License
|
|
873
|
+
#
|
|
874
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
875
|
+
#
|
|
876
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
877
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
878
|
+
# to deal in the Software without restriction, including without limitation
|
|
879
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
880
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
881
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
882
|
+
#
|
|
883
|
+
# The above copyright notice and this permission notice shall be included
|
|
884
|
+
# in all copies or substantial portions of the Software.
|
|
885
|
+
#
|
|
886
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
887
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
888
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
889
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
890
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
891
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
892
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|