pygeodesy 24.11.11__py2.py3-none-any.whl → 25.1.5__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.
Files changed (118) hide show
  1. {PyGeodesy-24.11.11.dist-info → PyGeodesy-25.1.5.dist-info}/METADATA +34 -35
  2. PyGeodesy-25.1.5.dist-info/RECORD +118 -0
  3. {PyGeodesy-24.11.11.dist-info → PyGeodesy-25.1.5.dist-info}/WHEEL +1 -1
  4. pygeodesy/__init__.py +19 -19
  5. pygeodesy/__main__.py +1 -1
  6. pygeodesy/albers.py +5 -5
  7. pygeodesy/auxilats/_CX_4.py +1 -1
  8. pygeodesy/auxilats/_CX_6.py +1 -1
  9. pygeodesy/auxilats/_CX_8.py +1 -1
  10. pygeodesy/auxilats/_CX_Rs.py +1 -1
  11. pygeodesy/auxilats/__init__.py +1 -1
  12. pygeodesy/auxilats/__main__.py +1 -1
  13. pygeodesy/auxilats/auxAngle.py +5 -5
  14. pygeodesy/auxilats/auxDLat.py +6 -6
  15. pygeodesy/auxilats/auxDST.py +2 -2
  16. pygeodesy/auxilats/auxLat.py +5 -5
  17. pygeodesy/auxilats/auxily.py +2 -2
  18. pygeodesy/azimuthal.py +5 -5
  19. pygeodesy/basics.py +60 -8
  20. pygeodesy/booleans.py +1 -1
  21. pygeodesy/cartesianBase.py +22 -61
  22. pygeodesy/clipy.py +1 -1
  23. pygeodesy/constants.py +5 -5
  24. pygeodesy/css.py +1 -1
  25. pygeodesy/datums.py +1 -1
  26. pygeodesy/deprecated/__init__.py +2 -2
  27. pygeodesy/deprecated/bases.py +1 -1
  28. pygeodesy/deprecated/classes.py +86 -2
  29. pygeodesy/deprecated/consterns.py +1 -1
  30. pygeodesy/deprecated/datum.py +5 -5
  31. pygeodesy/deprecated/functions.py +42 -8
  32. pygeodesy/deprecated/nvector.py +1 -1
  33. pygeodesy/deprecated/rhumbBase.py +1 -1
  34. pygeodesy/deprecated/rhumbaux.py +1 -1
  35. pygeodesy/deprecated/rhumbsolve.py +1 -1
  36. pygeodesy/deprecated/rhumbx.py +1 -1
  37. pygeodesy/dms.py +1 -1
  38. pygeodesy/ecef.py +53 -56
  39. pygeodesy/elevations.py +1 -1
  40. pygeodesy/ellipsoidalBase.py +3 -3
  41. pygeodesy/ellipsoidalBaseDI.py +1 -1
  42. pygeodesy/ellipsoidalExact.py +1 -1
  43. pygeodesy/ellipsoidalGeodSolve.py +1 -1
  44. pygeodesy/ellipsoidalKarney.py +1 -1
  45. pygeodesy/ellipsoidalNvector.py +1 -1
  46. pygeodesy/ellipsoidalVincenty.py +6 -5
  47. pygeodesy/ellipsoids.py +4 -5
  48. pygeodesy/elliptic.py +6 -6
  49. pygeodesy/epsg.py +1 -1
  50. pygeodesy/errors.py +1 -1
  51. pygeodesy/etm.py +5 -5
  52. pygeodesy/fmath.py +18 -17
  53. pygeodesy/formy.py +409 -555
  54. pygeodesy/frechet.py +29 -86
  55. pygeodesy/fstats.py +1 -1
  56. pygeodesy/fsums.py +32 -33
  57. pygeodesy/gars.py +1 -1
  58. pygeodesy/geodesici.py +7 -7
  59. pygeodesy/geodesicw.py +1 -1
  60. pygeodesy/geodesicx/_C4_24.py +2 -2
  61. pygeodesy/geodesicx/_C4_27.py +2 -2
  62. pygeodesy/geodesicx/_C4_30.py +2 -2
  63. pygeodesy/geodesicx/__init__.py +2 -2
  64. pygeodesy/geodesicx/__main__.py +4 -5
  65. pygeodesy/geodesicx/gx.py +6 -5
  66. pygeodesy/geodesicx/gxarea.py +2 -2
  67. pygeodesy/geodesicx/gxbases.py +2 -2
  68. pygeodesy/geodesicx/gxline.py +16 -12
  69. pygeodesy/geodsolve.py +1 -1
  70. pygeodesy/geohash.py +1 -1
  71. pygeodesy/geoids.py +277 -203
  72. pygeodesy/hausdorff.py +23 -81
  73. pygeodesy/heights.py +115 -150
  74. pygeodesy/internals.py +1 -1
  75. pygeodesy/interns.py +2 -3
  76. pygeodesy/iters.py +1 -1
  77. pygeodesy/karney.py +3 -3
  78. pygeodesy/ktm.py +16 -15
  79. pygeodesy/latlonBase.py +323 -409
  80. pygeodesy/lazily.py +53 -44
  81. pygeodesy/lcc.py +1 -1
  82. pygeodesy/ltp.py +46 -50
  83. pygeodesy/ltpTuples.py +147 -130
  84. pygeodesy/mgrs.py +1 -1
  85. pygeodesy/named.py +149 -3
  86. pygeodesy/namedTuples.py +58 -7
  87. pygeodesy/nvectorBase.py +122 -105
  88. pygeodesy/osgr.py +1 -1
  89. pygeodesy/points.py +1 -1
  90. pygeodesy/props.py +1 -1
  91. pygeodesy/resections.py +18 -17
  92. pygeodesy/rhumb/__init__.py +1 -1
  93. pygeodesy/rhumb/aux_.py +2 -2
  94. pygeodesy/rhumb/bases.py +2 -2
  95. pygeodesy/rhumb/ekx.py +4 -4
  96. pygeodesy/rhumb/solve.py +1 -1
  97. pygeodesy/simplify.py +289 -401
  98. pygeodesy/solveBase.py +1 -1
  99. pygeodesy/sphericalBase.py +1 -1
  100. pygeodesy/sphericalNvector.py +5 -5
  101. pygeodesy/sphericalTrigonometry.py +7 -6
  102. pygeodesy/streprs.py +10 -5
  103. pygeodesy/trf.py +1 -1
  104. pygeodesy/triaxials.py +23 -16
  105. pygeodesy/units.py +16 -16
  106. pygeodesy/unitsBase.py +1 -1
  107. pygeodesy/ups.py +4 -4
  108. pygeodesy/utily.py +341 -211
  109. pygeodesy/utm.py +5 -5
  110. pygeodesy/utmups.py +1 -1
  111. pygeodesy/utmupsBase.py +1 -1
  112. pygeodesy/vector2d.py +5 -5
  113. pygeodesy/vector3d.py +14 -3
  114. pygeodesy/vector3dBase.py +5 -5
  115. pygeodesy/webmercator.py +1 -1
  116. pygeodesy/wgrs.py +1 -1
  117. PyGeodesy-24.11.11.dist-info/RECORD +0 -118
  118. {PyGeodesy-24.11.11.dist-info → PyGeodesy-25.1.5.dist-info}/top_level.txt +0 -0
pygeodesy/simplify.py CHANGED
@@ -1,59 +1,53 @@
1
1
 
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
- u'''Simplify or linearize a path.
5
-
6
- Each of the I{simplify} functions is based on a different algorithm and
7
- produces different, simplified results in (very) different run times for
8
- the same path of C{LatLon} points.
9
-
10
- Function L{simplify1} eliminates points based on edge lengths shorter
11
- than a given tolerance.
12
-
13
- The functions L{simplifyRDP} and L{simplifyRDPm} use the original,
14
- respectively modified I{Ramer-Douglas-Peucker} (RDP) algorithm, iteratively
15
- finding the point farthest from each path edge. The difference is that
16
- function L{simplifyRDP} exhaustively searches the most distant point in
17
- each iteration, while modified L{simplifyRDPm} stops at the first point
18
- exceeding the distance tolerance.
19
-
20
- Function L{simplifyRW} use the I{Reumann-Witkam} method, sliding a "pipe"
21
- over each path edge, removing all subsequent points within, closer than
22
- the pipe radius up to the first point outside the pipe.
23
-
24
- Functions L{simplifyVW} and L{simplifyVWm} are based on the original,
25
- respectively modified I{Visvalingam-Whyatt} (VW) method using the area of
26
- the triangle formed by three neigboring points. Function L{simplifyVW}
27
- removes only a single point per iteration, while modified L{simplifyVWm}
28
- eliminates in each iteration all points with a triangular area not
29
- exceeding the tolerance.
30
-
31
- Functions L{simplifyRDP}, L{simplifyRDPm} and L{simplifyRW} provide
32
- keyword argument I{shortest} to specify of the distance between a point
33
- and a path edge. If C{True}, use the I{shortest} distance to the path
34
- edge or edge points, otherwise use the I{perpendicular} distance to
35
- the extended line through both path edge points.
4
+ u'''Simplify or linearize a path of C{LatLon} points.
5
+
6
+ Each of the 4 I{simplify} functions is based on a different algorithm and
7
+ produces different, simplified results in (very) different run times for the
8
+ same path:
9
+
10
+ - Function L{simplify1} eliminates points with edge lengths shorter than
11
+ the given tolerance.
12
+
13
+ - Function L{simplifyRDP} implements the I{Ramer-Douglas-Peucker} (RDP)
14
+ algorithm, iteratively finding the point farthest from each path edge.
15
+ Original RDP exhaustively searches the most distant point in each iteration,
16
+ I{modified} RDP stops at the first point exceeding the distance tolerance.
17
+
18
+ - Function L{simplifyRW} uses the I{Reumann-Witkam} (RW) method, sliding a
19
+ "pipe" over each path edge, removing all subsequent points within the pipe
20
+ radius, up to the first point outside the pipe.
21
+
22
+ - Function L{simplifyVW} provides the I{Visvalingam-Whyatt} (VW) method
23
+ using the area of the triangle formed by three neigboring points. Original
24
+ VW removes only a single point per iteration, I{modified} VW eliminates all
25
+ points with a triangular area not exceeding the tolerance in each iteration.
26
+
27
+ Keyword argument I{shortest} of functions L{simplifyRDP} and L{simplifyRW}
28
+ specifies of the distance between a point and a path edge. If C{True}, use
29
+ the I{shortest} distance to the path edge or edge points, otherwise use the
30
+ I{perpendicular} distance to the (extended) edge through both points.
36
31
 
37
32
  Keyword argument B{C{radius}} of all fuctions is set to the mean earth
38
- radius in C{meter}. Other units can be used, provided that the radius
39
- and tolerance are always specified in the same units.
33
+ radius in C{meter}, conventionally. Other units may be used, provided
34
+ that radius and tolerance are specified in the same units.
40
35
 
41
- Use keyword argument C{B{indices}=True} in any function to return a
42
- list of simplified point I{indices} instead of the simplified points.
43
- The first and last index are always the first and last original index.
36
+ Use keyword argument C{B{indices}=True} in any function to return a list
37
+ of I{indices} of simplified point instead of the simplified points with
38
+ the first and last index are always the first and last original index.
44
39
 
45
40
  Finally, any additional keyword arguments B{C{options}} to all functions
46
41
  are passed thru to function L{pygeodesy.equirectangular4} to specify the
47
42
  distance approximation.
48
43
 
49
- To process C{NumPy} arrays containing rows of lat-, longitude and
50
- possibly other values, use class L{Numpy2LatLon} to wrap the C{NumPy}
51
- array into I{on-the-fly-LatLon} points. Pass the L{Numpy2LatLon}
52
- instance to any I{simplify} function and the returned result will be
53
- a C{NumPy} array containing the simplified subset, a partial copy of
54
- the original C{NumPy} array. Use keyword argument C{B{indices}=True}
55
- to return a list of array row indices inlieu of the simplified array
56
- subset.
44
+ To process C{NumPy} arrays containing rows of lat-, longitude and possibly
45
+ other values, use class L{Numpy2LatLon} to wrap the C{NumPy} array into
46
+ I{on-the-fly-LatLon} points. Pass the L{Numpy2LatLon} instance to any
47
+ I{simplify} function and the returned result will be a C{NumPy} array
48
+ containing the simplified subset, a partial copy of the original C{NumPy}
49
+ array. Use keyword argument C{B{indices}=True} to return a list of array
50
+ row indices inlieu of the simplified array subset.
57
51
 
58
52
  See:
59
53
  - U{https://Bost.Ocks.org/mike/simplify}
@@ -75,7 +69,7 @@ from __future__ import division as _; del _ # PYCHOK semicolon
75
69
 
76
70
  # from pygeodesy.basics import len2 # from .fmath
77
71
  from pygeodesy.constants import EPS, R_M, _1_0
78
- from pygeodesy.errors import _AttributeError, _ValueError
72
+ from pygeodesy.errors import _AttributeError, _ValueError, _xkwds_pop2
79
73
  from pygeodesy.fmath import fdot_, len2, sqrt0
80
74
  from pygeodesy.formy import equirectangular4
81
75
  from pygeodesy.interns import _small_, _too_
@@ -86,7 +80,7 @@ from pygeodesy.units import _ALL_LAZY, _1mm, Radius_
86
80
  from math import degrees, fabs, radians
87
81
 
88
82
  __all__ = _ALL_LAZY.simplify
89
- __version__ = '24.11.07'
83
+ __version__ = '24.12.02'
90
84
 
91
85
 
92
86
  # try:
@@ -101,7 +95,7 @@ __version__ = '24.11.07'
101
95
  # self is not first method argument" which can't
102
96
  # be suppressed with command line option --stdlib
103
97
  class _T2(object):
104
- '''(INTERNAL) VW 2-tuple (index, area).
98
+ '''(INTERNAL) VW 2-tuple (index, area2).
105
99
  '''
106
100
  # __slots__ are no longer space savers, see
107
101
  # the comments at the class .points.LatLon_
@@ -115,18 +109,19 @@ class _T2(object):
115
109
  class _Sy(object):
116
110
  '''(INTERNAL) Simplify state.
117
111
  '''
118
- d2i2 = None # d2iP2 or d2iS2
119
- d2yxse5 = () # 5-tuple
120
- eps = EPS # system epsilon
121
- indices = False
122
- n = 0
123
- options = {}
124
- pts = []
125
- radius = R_M # mean earth radius
126
- r = {} # RDP indices or VW 2-tuples
127
- s2 = EPS # tolerance squared
128
- s2e = EPS # sentinel
129
- subset = None # isNumpy2 or isTuple2
112
+ d2yxse5 = () # 5-tuple
113
+ eps = EPS # system epsilon
114
+ indices = False
115
+ ixs = set() # set(indices)
116
+ n = 0
117
+ options = {}
118
+ pts = []
119
+ radius = R_M # mean earth radius
120
+ s2 = EPS # tolerance squared
121
+ s2e = EPS # VW sentinel
122
+ shortest = False # i.e. perpendicular
123
+ subset = None # isNumpy2 or isTuple2
124
+ t2s = [] # list(_T2s)
130
125
 
131
126
  def __init__(self, points, tolerance, radius, shortest,
132
127
  indices, **options):
@@ -134,123 +129,99 @@ class _Sy(object):
134
129
  '''
135
130
  n, self.pts = len2(points)
136
131
  if n > 0:
137
- self.n = n
138
- self.r = {0: True, n-1: True} # dict to avoid duplicates
132
+ self.n = n
133
+ self.ixs = set((0, n-1))
139
134
 
140
- if isNumpy2(points) or isTuple2(points): # NOT self.pts
141
- self.subset = points.subset
142
-
143
- if indices:
144
- self.indices = True
145
-
146
- if radius:
135
+ if radius is not R_M:
147
136
  self.radius = Radius_(radius, low=self.eps)
148
- elif self.radius < self.eps:
149
- raise _ValueError(radius=radius, txt=_too_(_small_))
150
-
151
- if options:
152
- self.options = options
153
-
154
137
  # tolerance converted to degrees squared
155
- self.s2 = degrees(tolerance / self.radius)**2
156
- if min(self.s2, tolerance) < self.eps:
138
+ self.s2 = s2 = degrees(tolerance / self.radius)**2
139
+ if min(s2, tolerance) < self.eps:
157
140
  raise _ValueError(tolerance=tolerance, txt=_too_(_small_))
158
- self.s2e = self.s2 + 1 # sentinel
141
+ self.s2e = s2 + _1_0 # VW sentinel
142
+ # assert self.s2e > s2
159
143
 
160
- # compute either the shortest or perpendicular distance
161
- self.d2i2 = self.d2iS2 if shortest else self.d2iP2 # PYCHOK attr
144
+ if indices:
145
+ self.indices = True
146
+ if options:
147
+ _, self.options = _xkwds_pop2(options, modified=None)
148
+ if shortest:
149
+ self.shortest = True
150
+ if isNumpy2(points) or isTuple2(points): # NOT self.pts
151
+ self.subset = points.subset
162
152
 
163
153
  def d21(self, s, e):
164
- '''Set path edge or line thru points[s] to -[e].
154
+ '''Set path edge or line thru (points[s], -[e]).
165
155
  '''
166
156
  d21, y21, x21, _ = self.d2yxu4(s, e)
167
157
  self.d2yxse5 = d21, y21, x21, s, e
168
158
  return d21 > self.eps
169
159
 
170
- def d2ih2(self, n, m, brk):
171
- '''Find the tallest distance among all points[n..m]
172
- to points[s] to -[e] exceeding the tolerance.
160
+ def d2i2(self, m, n, modified):
161
+ '''Find the tallest distance among all points[m..n]
162
+ to (points[s], -[e]) exceeding the tolerance.
173
163
  '''
174
164
  _, _, _, s, _ = self.d2yxse5
175
- eps, _d2yxu4 = self.eps, self.d2yxu4
176
165
  t2, t = self.s2, 0 # tallest
177
- for i in range(n, m):
178
- d2, _, _, _ = _d2yxu4(s, i)
166
+ for i in range(m, n):
167
+ d2, _, _, _ = self.d2yxu4(s, i)
179
168
  if d2 > t2:
180
169
  t2, t = d2, i
181
- if brk and d2 > eps:
170
+ if modified and d2 > self.eps:
182
171
  break
183
172
  return t2, t
184
173
 
185
- def d2iP2(self, n, m, brk):
186
- '''Find the tallest I{perpendicular} distance among all
187
- points[n..m] to the path edge or line thru points[s]
188
- to -[e] exceeding the tolerance.
189
- '''
190
- d21, y21, x21, s, _ = self.d2yxse5
191
- eps, _d2yxu4 = self.eps, self.d2yxu4
192
- t2, t = self.s2, 0 # tallest
193
- for i in range(n, m):
194
- d2, y01, x01, _ = _d2yxu4(s, i)
195
- if d2 > eps:
196
- # perpendicular distance squared
197
- d2 = (y01 * x21 - x01 * y21)**2 / d21
198
- if d2 > t2:
199
- t2, t = d2, i
200
- if brk:
201
- break
202
- return t2, t
203
-
204
- def d2iS2(self, n, m, brk):
205
- '''Find the tallest I{shortest} distance among all
206
- points[n..m] to the path edge or line thru points[s]
207
- to -[e] exceeding the tolerance.
174
+ def d2ix2(self, m, n, modified):
175
+ '''Find the tallest I{perpendicular B{or} shortest} distance
176
+ among all points[m..n] to the path edge or line through
177
+ (points[s], -[e]) exceeding the tolerance.
208
178
  '''
179
+ h = not self.shortest
209
180
  # point (x, y) on axis rotated by angle a ccw:
210
181
  # x' = y * sin(a) + x * cos(a)
211
182
  # y' = y * cos(a) - x * sin(a)
212
183
  #
213
- # distance (w) along and perpendicular (h) to
214
- # a line thru point (dx, dy) and the origin:
184
+ # distance along (w) and perpendicular to (h)
185
+ # a line from the origin to point (dx, dy):
215
186
  # w = (y * dy + x * dx) / hypot(dx, dy)
216
187
  # h = (y * dx - x * dy) / hypot(dx, dy)
217
-
218
188
  d21, y21, x21, s, e = self.d2yxse5
219
- eps, _d2yxu4 = self.eps, self.d2yxu4
220
189
  t2, t = self.s2, 0 # tallest
221
- for i in range(n, m):
222
- # distance points[i] to -[s]
223
- d2, y01, x01, _ = _d2yxu4(s, i)
224
- if d2 > eps:
225
- w = fdot_(y01, y21, x01, x21)
226
- if w > 0:
227
- if w < d21:
228
- # perpendicular distance squared
229
- d2 = fdot_(y01, x21, -x01, y21)**2 / d21
230
- else: # distance points[i] to -[e]
231
- d2, _, _, _ = _d2yxu4(e, i)
190
+ for i in range(m, n):
191
+ # distance points[s] to -[i], ...
192
+ d2, y01, x01, _ = self.d2yxu4(s, i)
193
+ if d2 > self.eps:
194
+ if h: # perpendicular distance
195
+ d2 = fdot_(y01, x21, -x01, y21)**2 / d21
196
+ else:
197
+ w = fdot_(y01, y21, x01, x21)
198
+ if w > 0:
199
+ if w < d21: # ... perpendicular ...
200
+ d2 = fdot_(y01, x21, -x01, y21)**2 / d21
201
+ else: # ... or points[e] to -[i]
202
+ d2, _, _, _ = self.d2yxu4(e, i)
232
203
  if d2 > t2:
233
204
  t2, t = d2, i
234
- if brk:
205
+ if modified:
235
206
  break
236
207
  return t2, t
237
208
 
238
209
  def d2yxu4(self, i, j):
239
- '''Return distance I{squared}, points[i] to -[j] deltas
240
- and the (longitudinal) unrollment.
210
+ '''Return the distance I{squared}, the deltas and the
211
+ (longitudinal) unrollment between (points[i], -[j]).
241
212
  '''
242
- p1, p2= self.pts[i], self.pts[j]
213
+ p1, p2 = self.pts[i], self.pts[j]
243
214
  return equirectangular4(p1.lat, p1.lon,
244
215
  p2.lat, p2.lon, **self.options)
245
216
 
246
- def h2t(self, i1, i0, i2):
247
- '''Compute the Visvalingam-Whyatt triangular area,
248
- points[i0] is the top and points[i1] to -[i2]
249
- form the base of the triangle.
217
+ def h2t(self, i1, i2, i3):
218
+ '''Compute (double) the triangle area, points[i2] is
219
+ the top and edge (points[i1], -[i3]) is the base
220
+ of the triangle.
250
221
  '''
251
- d21, y21, x21 , _= self.d2yxu4(i1, i2)
222
+ d21, y21, x21 , _ = self.d2yxu4(i1, i3)
252
223
  if d21 > self.eps:
253
- d01, y01, x01, _ = self.d2yxu4(i1, i0)
224
+ d01, y01, x01, _ = self.d2yxu4(i1, i2)
254
225
  if d01 > self.eps:
255
226
  h2 = fabs(fdot_(y01, x21, -x01, y21))
256
227
  # triangle height h = h2 / sqrt(d21) and
@@ -258,137 +229,171 @@ class _Sy(object):
258
229
  return h2 # double triangle area
259
230
  return 0
260
231
 
261
- def points(self, r):
262
- '''Return the list of simplified points or indices.
263
- '''
264
- r = sorted(r.keys())
265
- if self.indices:
266
- return list(r)
267
- elif self.subset:
268
- return self.subset(r)
269
- else:
270
- return [self.pts[i] for i in r]
271
-
272
232
  def rdp(self, modified):
273
233
  '''Ramer-Douglas-Peucker (RDP) simplification of a
274
234
  path of C{LatLon} points.
275
235
 
276
- @arg modified: Use modified RDP (C{bool}).
236
+ @arg modified: Use I{modified} RDP (C{bool}).
277
237
  '''
278
- n, r = self.n, self.r
238
+ r, n = self.ixs, self.n
279
239
  if n > 1:
280
- s2, _d21 = self.s2, self.d21
281
- _d2i2, _d2ih2 = self.d2i2, self.d2ih2
282
-
283
- se = [(0, n-1)]
284
- _a = se.append
285
- _p = se.pop
240
+ s2, se = self.s2, [(0, n-1)]
286
241
  while se:
287
- s, e = _p()
242
+ s, e = se.pop()
288
243
  s1 = s + 1
289
244
  if e > s1:
290
- if _d21(s, e): # points[] to edge [s, e]
291
- d2, i = _d2i2(s1, e, modified)
245
+ if self.d21(s, e): # points[] to edge [s, e]
246
+ d2, i = self.d2ix2(s1, e, modified)
292
247
  else: # points[] to point [s]
293
- d2, i = _d2ih2(s1, e, modified)
248
+ d2, i = self.d2i2( s1, e, modified)
294
249
  if d2 > s2 and i > 0:
295
- r[i] = True
296
- _a((i, e))
250
+ se.append((i, e))
297
251
  if not modified:
298
- _a((s, i))
299
- r[s] = True
300
-
301
- return self.points(r)
252
+ se.append((s, i))
253
+ r.add(i)
254
+ r.add(s)
255
+ return self.result(r)
302
256
 
303
- def rm1(self, m, tol):
304
- '''Eliminate one Visvalingam-Whyatt point and recomputes
305
- the trangular area of both neighboring points, but
306
- removes those too unless the recomputed area exceeds
307
- the tolerance.
257
+ def result(self, r):
258
+ '''Return the simplified points or indices.
308
259
  '''
309
- _h2t, r = self.h2t, self.r
310
-
311
- r.pop(m)
312
- for n in (m, m - 1):
313
- while 0 < n < (len(r) - 1):
314
- h2 = _h2t(r[n-1].ix, r[n].ix, r[n+1].ix)
315
- if h2 > tol:
316
- r[n].h2 = h2
317
- break # while
318
- else:
319
- r.pop(n)
260
+ r = sorted(r)
261
+ if self.indices:
262
+ return list(r)
263
+ elif self.subset:
264
+ return self.subset(r)
265
+ else:
266
+ return list(self.pts[i] for i in r)
320
267
 
321
- def rm2(self, tol):
322
- '''Eliminate all Visvalingam-Whyatt points with a
323
- triangular area not exceeding the tolerance.
268
+ def rw(self):
269
+ '''Reumann-Witkam simplification.
324
270
  '''
325
- r, _rm1 = self.r, self.rm1
271
+ r, n = self.ixs, self.n
272
+ if n > 1:
273
+ s, e, s2 = 0, 1, self.s2
274
+ while s < e < n:
275
+ if self.d21(s, e):
276
+ d2, i = self.d2ix2(e + 1, n, True)
277
+ r.add(s)
278
+ if d2 > s2 and i > 0:
279
+ r.add(i)
280
+ s = e = i
281
+ else:
282
+ # r.add(n - 1)
283
+ break
284
+ e += 1
285
+ return self.result(r)
326
286
 
327
- i = len(r) - 1
328
- while i > 1:
329
- i -= 1
330
- if r[i].h2 <= tol:
331
- _rm1(i, tol)
332
- i = min(i, len(r) - 1)
287
+ def sy1(self):
288
+ '''Basic simplification.
289
+ '''
290
+ r, n = self.ixs, self.n
291
+ if n > 1:
292
+ s2, i = self.s2, 0
293
+ for j in range(1, n):
294
+ d2, _, _, _ = self.d2yxu4(i, j)
295
+ if d2 > s2:
296
+ r.add(j)
297
+ i = j
298
+ return self.result(r)
333
299
 
334
300
  def vwn(self):
335
- '''Initialize Visvalingam-Whyatt as list of 2-tuples
336
- _T2(ix, h2) where ix is the points[] index and h2
337
- is the triangular area I{(times 2)} of that point.
301
+ '''Initialize VW as list of 2-tuples _T2(ix, h2) where
302
+ ix is the points[] index and h2 is the triangular
303
+ area I{(times 2)} of that point.
338
304
  '''
339
- n, _h2t, s2e, T2 = self.n, self.h2t, self.s2e, _T2
340
-
341
- self.r = r = []
305
+ self.t2s = t = []
306
+ n, T2 = self.n, _T2
342
307
  if n > 2:
343
- r[:] = [T2(i, _h2t(i-1, i, i+1)) for i in range(1, n-1)]
308
+ _h2t = self.h2t
309
+ t[:] = [T2(i, _h2t(i-1, i, i+1)) for i in range(1, n - 1)]
344
310
  if n > 1:
345
- r.append(T2(n-1, s2e))
311
+ t.append(T2(n - 1, self.s2e))
346
312
  if n > 0:
347
- r.insert(0, T2(0, s2e))
348
- return len(r)
313
+ t.insert(0, T2(0, self.s2e))
314
+ return len(t)
349
315
 
350
316
  def vwr(self, attr):
351
- '''Return the Visvalingam-Whyatt results, optionally
352
- including the triangular area (in meters) as
353
- attribute attr to each simplified point.
317
+ '''Return the VW results, optionally including the
318
+ triangular area (in C{meter}) as attribute C{attr}
319
+ to each simplified point.
354
320
  '''
355
- pts, r = self.pts, self.r
321
+ pts, t = self.pts, self.t2s
356
322
 
357
323
  # double check the minimal triangular area
358
- assert min(t2.h2 for t2 in r) > self.s2 > 0
324
+ assert min(t2.h2 for t2 in t) > self.s2 > 0
359
325
 
360
- if attr: # return the trangular area (actually
326
+ if attr: # return each trangular area (actually
361
327
  # the sqrt of double the triangular area)
362
328
  # converted back from degrees to meter
363
329
  if isNumpy2(pts):
364
330
  raise _AttributeError(attr=attr)
365
- m = radians(_1_0) * self.radius
366
- r[0].h2 = r[-1].h2 = 0 # zap sentinels
367
- for t2 in r: # convert back to meter
331
+ t[0].h2 = t[-1].h2 = 0 # zap sentinels
332
+ m = radians(_1_0) * self.radius # meter
333
+ for t2 in t: # convert back to meter
368
334
  setattr(pts[t2.ix], attr, sqrt0(t2.h2) * m)
369
335
 
370
- # double check for duplicates
371
- n = len(r)
372
- r = dict((t2.ix, True) for t2 in r)
336
+ n = len(t) # double check for duplicates
337
+ r = set(t2.ix for t2 in t)
373
338
  assert len(r) == n
374
- return self.points(r)
339
+ return self.result(r)
375
340
 
341
+ def vwrm(self):
342
+ '''Keep removing the VW point with the smallest triangular
343
+ area until that area exceeds the tolerance.
344
+ '''
345
+ s2, t = self.s2, self.t2s
346
+ while len(t) > 2:
347
+ m2, m = t[1].h2, 1
348
+ for i in range(2, len(t) - 1):
349
+ h2 = t[i].h2
350
+ if h2 < m2:
351
+ m2, m = h2, i
352
+ if m2 > s2:
353
+ break
354
+ self.vwrm1(m, 0)
376
355
 
377
- def simplify1(points, distance=_1mm, radius=R_M, indices=False, **options):
378
- '''Basic simplification of a path of C{LatLon} points.
356
+ def vwrm1(self, m, tol):
357
+ '''Eliminate VW point[m], keep recomputing the trangular
358
+ area of both neighboring points and removing those
359
+ too until the recomputed area exceeds C{tol}.
360
+ '''
361
+ t, _h2t = self.t2s, self.h2t
362
+ t.pop(m)
363
+ for n in (m, m - 1): # neighbors
364
+ while 0 < n < (len(t) - 1):
365
+ h2 = _h2t(t[n-1].ix, t[n].ix, t[n+1].ix)
366
+ if h2 > tol:
367
+ t[n].h2 = h2
368
+ break # while
369
+ t.pop(n)
370
+
371
+ def vwrm2(self, tol):
372
+ '''Eliminate all VW points with a triangular area not
373
+ exceeding C{tol}.
374
+ '''
375
+ t = self.t2s
376
+ m = len(t) - 1
377
+ while m > 1:
378
+ m -= 1
379
+ if t[m].h2 <= tol:
380
+ self.vwrm1(m, tol)
381
+ m = min(m, len(t) - 1)
379
382
 
380
- Eliminates any points closer together than the given I{distance}
381
- tolerance.
382
383
 
383
- @arg points: Path points (C{LatLon}[]).
384
+ def simplify1(points, distance=_1mm, radius=R_M, indices=False, **options):
385
+ '''Basic simplification of a path of C{LatLon} points by eliminating
386
+ any points closer together than the given I{distance} tolerance.
387
+
388
+ @arg points: Iterable with the path points (C{LatLon}[]).
384
389
  @kwarg distance: Tolerance (C{meter}, same units as B{C{radius}}).
385
- @kwarg radius: Mean earth radius (C{meter}).
386
- @kwarg indices: If C{True}, return the simplified point indices
387
- instead of the simplified points (C{bool}).
388
- @kwarg options: Optional keyword arguments passed thru to
389
- function L{pygeodesy.equirectangular4}.
390
+ @kwarg radius: Mean earth radius (C{meter}, conventionally).
391
+ @kwarg indices: If C{True}, return B{C{points}} indices instead
392
+ of the simplified points (C{bool}).
393
+ @kwarg options: Optional keyword arguments passed thru to function
394
+ L{pygeodesy.equirectangular4}.
390
395
 
391
- @return: Simplified points (C{LatLon}[]).
396
+ @return: Simplified points (C{LatLon}[]) or B{C{points}} indices.
392
397
 
393
398
  @raise LimitError: Lat- and/or longitudinal delta exceeds the B{C{limit}},
394
399
  see function L{pygeodesy.equirectangular4}.
@@ -396,109 +401,63 @@ def simplify1(points, distance=_1mm, radius=R_M, indices=False, **options):
396
401
  @raise ValueError: Tolerance B{C{distance}} or B{C{radius}} too small.
397
402
  '''
398
403
  S = _Sy(points, distance, radius, True, indices, **options)
399
-
400
- r, n = S.r, S.n
401
- if n > 1:
402
- s2, _d2yxu4 = S.s2, S.d2yxu4
403
-
404
- i = 0
405
- for j in range(1, n):
406
- d2, _, _, _= _d2yxu4(i, j)
407
- if d2 > s2:
408
- r[j] = True
409
- i = j
410
-
411
- return S.points(r)
404
+ return S.sy1()
412
405
 
413
406
 
414
407
  def simplifyRDP(points, distance=_1mm, radius=R_M, shortest=False,
415
- indices=False, **options):
416
- '''I{Ramer-Douglas-Peucker} (RDP) simplification of a path of
417
- C{LatLon} points.
418
-
419
- Eliminates any points too close together or closer to an
408
+ indices=False, modified=False, **options):
409
+ '''I{Ramer-Douglas-Peucker} (RDP) simplification of a path of C{LatLon}
410
+ points by eliminating any points too close together or closer to an
420
411
  edge than the given I{distance} tolerance.
421
412
 
422
- This C{RDP} method exhaustively searches for the point with
423
- the largest distance, resulting in worst-case complexity
424
- M{O(n**2)} where M{n} is the number of points.
425
-
426
- @arg points: Path points (C{LatLon}[]).
413
+ @arg points: Iterable with the path points (C{LatLon}[]).
427
414
  @kwarg distance: Tolerance (C{meter}, same units as B{C{radius}}).
428
- @kwarg radius: Mean earth radius (C{meter}).
415
+ @kwarg radius: Mean earth radius (C{meter}, conventionally).
429
416
  @kwarg shortest: If C{True}, use the I{shortest} otherwise the
430
417
  I{perpendicular} distance (C{bool}).
431
- @kwarg indices: If C{True}, return the simplified point indices
432
- instead of the simplified points (C{bool}).
433
- @kwarg options: Optional keyword arguments passed thru to
434
- function L{pygeodesy.equirectangular4}.
418
+ @kwarg indices: If C{True}, return B{C{points}} indices instead
419
+ of the simplified points (C{bool}).
420
+ @kwarg modified: If C{True}, use the C{modified RDP} method (C{bool}),
421
+ see the B{note}.
422
+ @kwarg options: Optional keyword arguments passed thru to function
423
+ L{pygeodesy.equirectangular4}.
435
424
 
436
- @return: Simplified points (C{LatLon}[]).
425
+ @return: Simplified points (C{LatLon}[]) or B{C{points}} indices.
437
426
 
438
427
  @raise LimitError: Lat- and/or longitudinal delta exceeds the B{C{limit}},
439
428
  see function L{pygeodesy.equirectangular4}.
440
429
 
441
430
  @raise ValueError: Tolerance B{C{distance}} or B{C{radius}} too small.
442
- '''
443
- S = _Sy(points, distance, radius, shortest, indices, **options)
444
-
445
- return S.rdp(False)
446
-
447
431
 
448
- def simplifyRDPm(points, distance=_1mm, radius=R_M, shortest=False,
449
- indices=False, **options):
450
- '''Modified I{Ramer-Douglas-Peucker} (RDPm) simplification of a
451
- path of C{LatLon} points.
452
-
453
- Eliminates any points too close together or closer to an edge
454
- than the given I{distance} tolerance.
455
-
456
- This C{RDPm} method stops at the first point farther than the
457
- given distance tolerance, significantly reducing the run time
458
- (but producing results different from the original C{RDP} method).
459
-
460
- @arg points: Path points (C{LatLon}[]).
461
- @kwarg distance: Tolerance (C{meter}, same units as B{C{radius}}).
462
- @kwarg radius: Mean earth radius (C{meter}).
463
- @kwarg shortest: If C{True}, use the I{shortest} otherwise the
464
- I{perpendicular} distance (C{bool}).
465
- @kwarg indices: If C{True}, return the simplified point indices
466
- instead of the simplified points (C{bool}).
467
- @kwarg options: Optional keyword arguments passed thru to
468
- function L{pygeodesy.equirectangular4}.
469
-
470
- @return: Simplified points (C{LatLon}[]).
471
-
472
- @raise LimitError: Lat- and/or longitudinal delta exceeds the B{C{limit}},
473
- see function L{pygeodesy.equirectangular4}.
474
-
475
- @raise ValueError: Tolerance B{C{distance}} or B{C{radius}} too small.
432
+ @note: The original C{RDP} method exhaustively searches for the point
433
+ with the largest distance (resulting in complexity M{O(n**2)}
434
+ with M{n} is the number of points). The B{C{modified}} C{RDP}
435
+ method stops at the first point farther than the B{C{distance}}
436
+ tolerance, significantly reducing the run time (but producing
437
+ results different from the original C{RDP} method).
476
438
  '''
477
439
  S = _Sy(points, distance, radius, shortest, indices, **options)
478
-
479
- return S.rdp(True)
440
+ return S.rdp(bool(modified))
480
441
 
481
442
 
482
443
  def simplifyRW(points, pipe=_1mm, radius=R_M, shortest=False,
483
444
  indices=False, **options):
484
- '''I{Reumann-Witkam} (RW) simplification of a path of C{LatLon}
485
- points.
486
-
487
- Eliminates any points too close together or within the given
445
+ '''I{Reumann-Witkam} (RW) simplification of a path of C{LatLon} points
446
+ by eliminating any points too close together or within the given
488
447
  I{pipe} tolerance along an edge.
489
448
 
490
- @arg points: Path points (C{LatLon}[]).
449
+ @arg points: Iterable with the path points (C{LatLon}[]).
491
450
  @kwarg pipe: Pipe radius, half-width (C{meter}, same units as
492
451
  B{C{radius}}).
493
- @kwarg radius: Mean earth radius (C{meter}).
452
+ @kwarg radius: Mean earth radius (C{meter}, conventionally).
494
453
  @kwarg shortest: If C{True}, use the I{shortest} otherwise the
495
454
  I{perpendicular} distance (C{bool}).
496
- @kwarg indices: If C{True}, return the simplified point indices
497
- instead of the simplified points (C{bool}).
498
- @kwarg options: Optional keyword arguments passed thru to
499
- function L{pygeodesy.equirectangular4}.
455
+ @kwarg indices: If C{True}, return B{C{points}} indices instead
456
+ of the simplified points (C{bool}).
457
+ @kwarg options: Optional keyword arguments passed thru to function
458
+ L{pygeodesy.equirectangular4}.
500
459
 
501
- @return: Simplified points (C{LatLon}[]).
460
+ @return: Simplified points (C{LatLon}[]) or B{C{points}} indices.
502
461
 
503
462
  @raise LimitError: Lat- and/or longitudinal delta exceeds the B{C{limit}},
504
463
  see function L{pygeodesy.equirectangular4}.
@@ -506,127 +465,56 @@ def simplifyRW(points, pipe=_1mm, radius=R_M, shortest=False,
506
465
  @raise ValueError: Tolerance B{C{pipe}} or B{C{radius}} too small.
507
466
  '''
508
467
  S = _Sy(points, pipe, radius, shortest, indices, **options)
468
+ return S.rw()
509
469
 
510
- n, r = S.n, S.r
511
- if n > 1:
512
- s2, _d21, _d2i2 = S.s2, S.d21, S.d2i2
513
-
514
- s, e = 0, 1
515
- while s < e < n:
516
- if _d21(s, e):
517
- d2, i = _d2i2(e + 1, n, True)
518
- if d2 > s2 and i > 0:
519
- r[s] = r[i] = True
520
- s, e = i, i + 1
521
- else:
522
- r[s] = True # r[n-1] = True
523
- break # while loop
524
- else: # drop points[e]
525
- e += 1
526
-
527
- return S.points(r)
528
-
529
-
530
- def simplifyVW(points, area=_1mm, radius=R_M, attr=None,
531
- indices=False, **options):
532
- '''I{Visvalingam-Whyatt} (VW) simplification of a path of
533
- C{LatLon} points.
534
470
 
535
- Eliminates any points too close together or with a triangular
471
+ def simplifyVW(points, area=_1mm, radius=R_M, indices=False,
472
+ attr=None, modified=False, **options):
473
+ '''I{Visvalingam-Whyatt} (VW) simplification of a path of C{LatLon}
474
+ points by eliminating any points too close or with a triangular
536
475
  area not exceeding the given I{area} tolerance I{squared}.
537
476
 
538
- This C{VW} method exhaustively searches for the single point
539
- with the smallest triangular area, resulting in worst-case
540
- complexity M{O(n**2)} where M{n} is the number of points.
541
-
542
- @arg points: Path points (C{LatLon}[]).
477
+ @arg points: Iterable with the path points (C{LatLon}[]).
543
478
  @kwarg area: Tolerance (C{meter}, same units as B{C{radius}}).
544
- @kwarg radius: Mean earth radius (C{meter}).
545
- @kwarg attr: Optional, points attribute to save the area value
546
- (C{str}).
547
- @kwarg indices: If C{True}, return the simplified point indices
548
- instead of the simplified points (C{bool}).
549
- @kwarg options: Optional keyword arguments passed thru to
550
- function L{pygeodesy.equirectangular4}.
479
+ @kwarg radius: Mean earth radius (C{meter}, conventionally).
480
+ @kwarg indices: If C{True}, return B{C{points}} indices instead
481
+ of the simplified points (C{bool}).
482
+ @kwarg attr: Optional, B{C{points}} attribute to save the area
483
+ value (C{str}).
484
+ @kwarg modified: If C{True}, use the C{modified VW} method (C{bool}),
485
+ see the B{note}.
486
+ @kwarg options: Optional keyword arguments passed thru to function
487
+ L{pygeodesy.equirectangular4}.
551
488
 
552
- @return: Simplified points (C{LatLon}[]).
489
+ @return: Simplified points (C{LatLon}[]) or B{C{points}} indices.
553
490
 
554
- @raise AttributeError: If an B{C{attr}} is specified for I{Numpy2}
555
- B{C{points}}.
491
+ @raise AttributeError: An B{C{attr}} isinvalid for I{Numpy2} B{C{points}}.
556
492
 
557
493
  @raise LimitError: Lat- and/or longitudinal delta exceeds the B{C{limit}},
558
494
  see function L{pygeodesy.equirectangular4}.
559
495
 
560
496
  @raise ValueError: Tolerance B{C{area}} or B{C{radius}} too small.
561
- '''
562
- S = _Sy(points, area, radius, False, indices, **options)
563
-
564
- if S.vwn() > 2:
565
- # remove any points too close or
566
- # with a zero triangular area
567
- S.rm2(0)
568
-
569
- r, s2, s2e = S.r, S.s2, S.s2e
570
- # keep removing the point with the smallest
571
- # area until latter exceeds the tolerance
572
- while len(r) > 2:
573
- m, m2 = 0, s2e
574
- for i in range(1, len(r) - 1):
575
- h2 = r[i].h2
576
- if h2 < m2:
577
- m, m2 = i, h2
578
- if m2 > s2:
579
- break
580
- S.rm1(m, 0)
581
-
582
- return S.vwr(attr)
583
497
 
584
-
585
- def simplifyVWm(points, area=_1mm, radius=R_M, attr=None,
586
- indices=False, **options):
587
- '''I{Modified Visvalingam-Whyatt} (VWm) simplification of a path
588
- of C{LatLon} points.
589
-
590
- Eliminates any points too close together or with a triangular
591
- area not exceeding the given area tolerance I{squared}.
592
-
593
- This C{VWm} method removes all points with a triangular area
594
- below the tolerance in each iteration, significantly reducing
595
- the run time (but producing results different from the
596
- original C{VW} method).
597
-
598
- @arg points: Path points (C{LatLon}[]).
599
- @kwarg area: Tolerance (C{meter}, same units as B{C{radius}}).
600
- @kwarg radius: Mean earth radius (C{meter}).
601
- @kwarg attr: Optional, points attribute to save the area value
602
- (C{str}).
603
- @kwarg indices: If C{True}, return the simplified point indices
604
- instead of the simplified points (C{bool}).
605
- @kwarg options: Optional keyword arguments passed thru to
606
- function L{pygeodesy.equirectangular4}.
607
-
608
- @return: Simplified points (C{LatLon}[]).
609
-
610
- @raise AttributeError: If an B{C{attr}} is specified for I{Numpy2}
611
- B{C{points}}.
612
-
613
- @raise LimitError: Lat- and/or longitudinal delta exceeds the B{C{limit}},
614
- see function L{pygeodesy.equirectangular4}.
615
-
616
- @raise ValueError: Tolerance B{C{area}} or B{C{radius}} too small.
498
+ @note: The original C{VW} method exhaustively searches for the point
499
+ with the smallest triangular I{area} (resulting in complexity
500
+ M{O(n**2)} with M{n} the number of points). The B{C{modified}}
501
+ C{VW} method removes I{all} points with a triangular I{area}
502
+ below the tolerance in each iteration, significantly reducing
503
+ the run time (but producing results different from the original
504
+ C{VW} method).
617
505
  '''
618
506
  S = _Sy(points, area, radius, False, indices, **options)
619
-
620
507
  if S.vwn() > 2:
621
- # remove all points with an area
622
- # not exceeding the tolerance
623
- S.rm2(S.s2)
624
-
508
+ if modified:
509
+ S.vwrm2(S.s2)
510
+ else:
511
+ S.vwrm2(0)
512
+ S.vwrm()
625
513
  return S.vwr(attr)
626
514
 
627
515
  # **) MIT License
628
516
  #
629
- # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
517
+ # Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved.
630
518
  #
631
519
  # Permission is hereby granted, free of charge, to any person obtaining a
632
520
  # copy of this software and associated documentation files (the "Software"),