22from copy import copy
33from typing import List , Tuple
44
5- import geopy .distance
65from automatic_walk_time_tables .utils import path , point
6+ from automatic_walk_time_tables .utils .point import Point_LV03
7+ from automatic_walk_time_tables .utils .way_point import WayPoint
78
89logger = logging .getLogger (__name__ )
910
1011
11- def select_waypoints (path_ : path .Path , walk_point_limit = 21 ):
12+ def select_waypoints (path_ : path .Path_LV03 , walk_point_limit = 21 ) -> Tuple [ float , List [ WayPoint ], List [ WayPoint ]] :
1213 """
1314 Algorithm that selects suitable points for the Marschzeittabelle.
1415 Some parts are inspired by the Ramer–Douglas–Peucker algorithm. Especially
@@ -37,7 +38,7 @@ def select_waypoints(path_ : path.Path, walk_point_limit=21):
3738 return total_distance , pts_step_2 , pts_step_3
3839
3940
40- def reduce_number_of_points (pts_step_2 : List [Tuple [ float , point . Point ] ], walk_point_limit : int ):
41+ def reduce_number_of_points (pts_step_2 : List [WayPoint ], walk_point_limit : int ):
4142 """
4243 Final selection: Iteratively reduce the number of points to the maximum specified in walk_point_limit. To
4344 achieve this we iteratively increase a maximum derivation (drv_limit) value until we have dropped enough points.
@@ -53,10 +54,10 @@ def reduce_number_of_points(pts_step_2: List[Tuple[float, point.Point]], walk_po
5354 through the selected points, we increase drv_limit by 2 meters and try again.
5455 """
5556
56- pts_step_3 : List [Tuple [ float , point . Point ] ] = copy (pts_step_2 )
57+ pts_step_3 : List [WayPoint ] = copy (pts_step_2 )
5758
58- pt_A : Tuple [ float , point . Point ] = None
59- pt_B : Tuple [ float , point . Point ] = None
59+ pt_A : WayPoint = None
60+ pt_B : WayPoint = None
6061
6162 drv_limit = 0
6263
@@ -81,19 +82,22 @@ def reduce_number_of_points(pts_step_2: List[Tuple[float, point.Point]], walk_po
8182 m , b = calc_secant_line (pt_A , pt_C )
8283 secant_elev = calc_secant_elevation (m , b , pt_B )
8384
84- if abs (secant_elev - pt_B [ 1 ] .h ) < drv_limit :
85+ if abs (secant_elev - pt_B . point .h ) < drv_limit :
8586
8687 # Check if B must be replaced by a previously dropped point D
8788 pt_D = None
88- for pt in list (filter (lambda p : pt_A [0 ] < p [0 ] < pt_C [0 ], pts_dropped )):
89+ for pt in list (
90+ filter (lambda p : pt_A .accumulated_distance < p [0 ] < pt_C .accumulated_distance ,
91+ pts_dropped )):
8992 secant_elev = calc_secant_elevation (m , b , pt )
90- if abs (secant_elev - pt [ 1 ] .h ) >= drv_limit :
93+ if abs (secant_elev - pt . point .h ) >= drv_limit :
9194 pt_D = pt
9295 break
9396
9497 if pt_D is not None : # Replace B with point D
9598 pts_step_3 .remove (pt_B )
96- index = next (x for x , val in enumerate (pts_step_3 ) if val [0 ] > pt_D [0 ])
99+ index = next (x for x , val in enumerate (pts_step_3 ) if
100+ val .accumulated_distance > pt_D .accumulated_distance )
97101 pts_step_3 .insert (index , pt_D )
98102 pts_dropped .append (pt_B )
99103
@@ -113,7 +117,7 @@ def reduce_number_of_points(pts_step_2: List[Tuple[float, point.Point]], walk_po
113117 return pts_step_3
114118
115119
116- def remove_unnecessary_points (pts_step_1 : List [Tuple [ int , point . Point ] ]):
120+ def remove_unnecessary_points (pts_step_1 : List [WayPoint ]):
117121 """
118122 Now we loop through the preselected points and tighten the selection criteria.
119123 Into the list pts_step_2 we include points according to the following rules:
@@ -132,26 +136,27 @@ def remove_unnecessary_points(pts_step_1: List[Tuple[int, point.Point]]):
132136
133137 drv_limit = 20
134138
135- last_pt : Tuple [ float , point . Point ] = pts_step_1 [0 ]
136- pts_step_2 : List [Tuple [ float , point . Point ] ] = [last_pt ]
139+ last_pt : WayPoint = pts_step_1 [0 ]
140+ pts_step_2 : List [WayPoint ] = [last_pt ]
137141
138142 for pt in pts_step_1 [1 :]:
139143
140144 if last_pt is not None :
141145
142- last_coord = get_coordinates (last_pt [ 1 ]. to_WGS84 () )
143- coord = get_coordinates (pt [ 1 ]. to_WGS84 () )
146+ last_coord = get_coordinates (last_pt . point )
147+ coord = get_coordinates (pt . point )
144148
145- if (abs (last_pt [1 ].h - pt [1 ].h ) > 20
146- and geopy .distance .distance (last_coord , coord ).m > 250 ) \
147- or geopy .distance .distance (last_coord , coord ).m > 1500 : # TODO: this line seems obsole, bug?
149+ if abs (last_pt .point .h - pt .point .h ) > 20 and coord [0 ] - last_coord [0 ] > 250 :
148150
149151 m , b = calc_secant_line (last_pt , pt )
150152
151153 # add point with max derivation
152- for intermediate_point in list (filter (lambda p : last_pt [0 ] < p [0 ] < pt [0 ], pts_step_1 )):
154+ for intermediate_point in \
155+ list (filter (
156+ lambda p : last_pt .accumulated_distance < p .accumulated_distance < pt .accumulated_distance ,
157+ pts_step_1 )):
153158
154- derivation = abs (calc_secant_elevation (m , b , intermediate_point ) - intermediate_point [ 1 ] .h )
159+ derivation = abs (calc_secant_elevation (m , b , intermediate_point ) - intermediate_point . point .h )
155160 if drv_limit <= derivation :
156161 pts_step_2 .append (intermediate_point )
157162 drv_limit = derivation
@@ -173,7 +178,7 @@ def get_coordinates(pt: point.Point):
173178 return pt .lat , pt .lon
174179
175180
176- def preselection_step (path_ : path .Path ):
181+ def preselection_step (path_ : path .Path ):
177182 """
178183 Preselection: Select all points from the tracking file which satisfy one of the following criteria.
179184 This guarantees that all important points are considered. We call the set of selected points pts_step_1.
@@ -184,68 +189,66 @@ def preselection_step(path_ : path.Path):
184189 - distance to last selected point bigger than 250 meters
185190 """
186191
187- cumulated_distance = 0.0
188- pts_step_1 : List [Tuple [ float , point . Point ] ] = []
192+ accumulated_distance = 0.0
193+ way_points : List [WayPoint ] = []
189194
190- lastSlope = None
191- lastCoord = None
195+ last_slope = 0
196+ last_point = path_ . points [ 0 ]
192197
193- lastPoint = None
198+ # Insert first point of path
199+ way_points .append (WayPoint (0 , last_point ))
194200
195201 # (1) Preselection
196- for i ,pt in enumerate (path_ .points ):
197- newCoord = get_coordinates (pt .to_WGS84 ())
198-
199- if i == 0 : # First point
200- pts_step_1 .append ((0 , pt ))
201- lastCoord = get_coordinates (pt .to_WGS84 ())
202- lastSlope = 0.
203- elif i == len (path_ .points ) - 1 : # Last point
204- distDelta = geopy .distance .distance (lastCoord , newCoord ).km
205- cumulated_distance += distDelta
206- pts_step_1 .append ((cumulated_distance , pt ))
207- else : # Any other point
208- distDelta = geopy .distance .distance (lastCoord , newCoord ).km
209- cumulated_distance += distDelta
210-
211- if distDelta > 0. :
212- newSlope = (pt .h - pts_step_1 [- 1 ][1 ].h ) / distDelta
213- if abs (newSlope ) > 0.0 :
214- if abs (newSlope - lastSlope ) > 0.5 : # Slope change
215- pts_step_1 .append ((cumulated_distance , pt ))
216- lastSlope = newSlope
217- lastCoord = newCoord
218- continue
219-
220- if geopy .distance .distance (lastCoord , newCoord ).m > 250 :
221- pts_step_1 .append ((cumulated_distance , pt ))
222- lastCoord = newCoord
223- lastSlope = newSlope
224- continue
225-
226- return cumulated_distance , pts_step_1
227-
228-
229- def calc_secant_elevation (m : float , b : float , pt_B : Tuple [float , point .Point ]):
202+ for i in range (len (path_ .points ) - 2 ):
203+
204+ last_coord : Point_LV03 = path_ .points [i ].to_LV03 ()
205+ coord : Point_LV03 = path_ .points [i + 1 ].to_LV03 ()
206+
207+ delta_dist = last_coord .distance (coord )
208+ accumulated_distance += delta_dist
209+
210+ # skip duplicated points in GPX file, less than 1cm in distance
211+ if delta_dist <= 0.01 :
212+ continue
213+
214+ slope = (coord .h - way_points [- 1 ].point .h ) / delta_dist
215+ if abs (slope ) > 0. and abs (slope - last_slope ) > 0.5 : # significant slope change
216+ way_points .append (WayPoint (accumulated_distance , coord ))
217+
218+ elif accumulated_distance - way_points [- 1 ].accumulated_distance > 250 :
219+ way_points .append (WayPoint (accumulated_distance , coord ))
220+
221+ # TODO: add significant local extremum in track elevation
222+
223+ # Insert last point of path
224+ way_points .append (WayPoint (accumulated_distance , path_ .points [- 1 ]))
225+
226+ logger .info (""
227+ "Total route length = {}m" .format (accumulated_distance ))
228+
229+ return accumulated_distance , way_points
230+
231+
232+ def calc_secant_elevation (m : float , b : float , pt_B : WayPoint ):
230233 """
231234 Calculates the elevation of a point on the secant line defined by m and b. The point on the
232235 secant line is chosen such that location matches the location of pkr_B.
233236 elevation = m * loc_of_pt_B + b
234237 """
235238
236- # pt_B[0] returns the location of point B in km
237- return m * ( pt_B [ 0 ] * 1000 ) + b
239+ # pt_B[0] returns the location of point B in meter
240+ return m * pt_B . accumulated_distance + b
238241
239242
240- def calc_secant_line (pt_A : Tuple [ float , point . Point ], pt_C : Tuple [ float , point . Point ] ):
243+ def calc_secant_line (pt_A : WayPoint , pt_C : WayPoint ):
241244 """
242245 Constructs a secant line through points A and C, i.g. a linear function passing through point A and C.
243246 Returns the slope and the intersect of a linear function through A and C.
244247 """
245248
246- # the locations of pt_A and pt_C given in km .
247- x1 , y1 = pt_A [ 0 ] * 1000 , pt_A [ 1 ] .h
248- x2 , y2 = pt_C [ 0 ] * 1000 , pt_C [ 1 ] .h
249+ # the locations of pt_A and pt_C given is given in m .
250+ x1 , y1 = pt_A . accumulated_distance , pt_A . point .h
251+ x2 , y2 = pt_C . accumulated_distance , pt_C . point .h
249252
250253 # if the location of A and C is identical, the slope m is defined as 0
251254 m : float = (y1 - y2 ) / (x1 - x2 ) if (x1 - x2 ) != 0 else 0.0
@@ -256,30 +259,27 @@ def calc_secant_line(pt_A: Tuple[float, point.Point], pt_C: Tuple[float, point.P
256259 return m , b
257260
258261
259- def prepare_for_plot (path_ : path .Path ):
262+ def prepare_for_plot (path_ : path .Path_LV03 ):
260263 """
261264 Prepares a gpx file for plotting.
262265 Returns two list, one with the elevation of all points in the gpx file and one with the associated,
263266 accumulated distance form the starting point.
264267 """
265268
266- coord : Tuple [ float , float ] = None
269+ coord : Point_LV03 = None
267270 accumulated_distance : float = 0.0
268271
269272 distances : List [float ] = []
270273 heights : List [float ] = []
271274
272275 for pt in path_ .points :
273276
274- newCoord = get_coordinates (pt .to_WGS84 ())
275-
276277 if coord is not None :
277- distDelta = geopy .distance .distance (coord , newCoord ).km
278- accumulated_distance += distDelta
278+ accumulated_distance += coord .distance (pt )
279279
280280 distances .append (accumulated_distance )
281281 heights .append (pt .h )
282282
283- coord = newCoord
283+ coord = pt
284284
285285 return distances , heights
0 commit comments