From 23a4be63e0fdf14eca177cab78afff6940bb2092 Mon Sep 17 00:00:00 2001 From: Paul Traina Date: Thu, 14 Dec 2023 16:21:30 -0800 Subject: [PATCH 1/2] get_nearest_locations returns the wrong point also trivial typing fixes elsewhere --- gpxpy/gpx.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/gpxpy/gpx.py b/gpxpy/gpx.py index 9e7b15d..6defc69 100644 --- a/gpxpy/gpx.py +++ b/gpxpy/gpx.py @@ -1218,13 +1218,13 @@ def get_location_at(self, time: mod_datetime.datetime) -> Optional[GPXTrackPoint # TODO: If between two points -- approx position! # return mod_geo.Location(point.latitude, point.longitude) return point - + return None def get_nearest_location(self, location: mod_geo.Location) -> Optional[NearestLocationData]: """ Return the (location, track_point_no) on this track segment """ return min((NearestLocationData(pt, -1, -1, pt_no) for (pt, pt_no) in self.walk()) # type: ignore - ,key=lambda x: x.location.distance_2d(location) if x is not None else mod_math.inf + ,key=lambda x: x.location.distance_2d(location) or mod_math.inf if x is not None else mod_math.inf ,default=None) def smooth(self, vertical: bool=True, horizontal: bool=False, remove_extremes: bool=False) -> None: @@ -1901,9 +1901,9 @@ def has_elevations(self) -> bool: def get_nearest_location(self, location: mod_geo.Location) -> Optional[NearestLocationData]: """ Returns (location, track_segment_no, track_point_no) for nearest location on track """ return min((NearestLocationData(pt, -1, seg, pt_no) for (pt, seg, pt_no) in self.walk()) # type: ignore - ,key=lambda x: x.location.distance_2d(location) if x is not None else mod_math.inf + ,key=lambda x: x.location.distance_2d(location) or mod_math.inf if x is not None else mod_math.inf ,default=None) - + def clone(self) -> "GPXTrack": return mod_copy.deepcopy(self) @@ -2155,7 +2155,7 @@ def get_bounds(self) -> Optional[GPXBounds]: max_longitude : float Maximum longitude of track in decimal degrees [-180, 180] """ - result: Optional[GPXBounds] = None + result: Optional[GPXBounds] = None for track in self.tracks: track_bounds = track.get_bounds() if not result: @@ -2436,8 +2436,8 @@ def get_points_data(self, distance_2d: bool=False) -> List[PointData]: Returns a list of tuples containing the actual point, its distance from the start, track_no, segment_no, and segment_point_no """ - distance_from_start = 0 - previous_point = None + distance_from_start: float = 0.0 + previous_point: Optional[GPXTrackPoint] = None # (point, distance_from_start) pairs: points = [] @@ -2451,7 +2451,7 @@ def get_points_data(self, distance_2d: bool=False) -> List[PointData]: else: distance = point.distance_3d(previous_point) - distance_from_start += distance + distance_from_start += distance or 0.0 points.append(PointData(point, distance_from_start, track_no, segment_no, point_no)) @@ -2488,6 +2488,7 @@ def get_nearest_locations(self, location: mod_geo.Location, threshold_distance: track_no_candidate: Optional[int] = None segment_no_candidate: Optional[int] = None point_no_candidate: Optional[int] = None + point_candidate: Optional[GPXTrackPoint] = None for point, distance_from_start, track_no, segment_no, point_no in points: distance = location.distance_3d(point) @@ -2498,17 +2499,19 @@ def get_nearest_locations(self, location: mod_geo.Location, threshold_distance: track_no_candidate = track_no segment_no_candidate = segment_no point_no_candidate = point_no + point_candidate = point else: - if distance_from_start_candidate is not None and point and track_no_candidate is not None and segment_no_candidate is not None and point_no_candidate is not None: - result.append(NearestLocationData(point, track_no_candidate, segment_no_candidate, point_no_candidate)) + if distance_from_start_candidate is not None and point_candidate and track_no_candidate is not None and segment_no_candidate is not None and point_no_candidate is not None: + result.append(NearestLocationData(point_candidate, track_no_candidate, segment_no_candidate, point_no_candidate)) min_distance_candidate = None distance_from_start_candidate = None track_no_candidate = None segment_no_candidate = None point_no_candidate = None + point_candidate = None - if distance_from_start_candidate is not None and point and track_no_candidate is not None and segment_no_candidate is not None and point_no_candidate is not None: - result.append(NearestLocationData(point, track_no_candidate, segment_no_candidate, point_no_candidate)) + if distance_from_start_candidate is not None and point_candidate and track_no_candidate is not None and segment_no_candidate is not None and point_no_candidate is not None: + result.append(NearestLocationData(point_candidate, track_no_candidate, segment_no_candidate, point_no_candidate)) return result @@ -2517,7 +2520,7 @@ def get_nearest_location(self, location: mod_geo.Location) -> Optional[NearestLo """ Returns (location, track_no, track_segment_no, track_point_no) for the nearest location on map """ return min((NearestLocationData(pt, tr, seg, pt_no) for (pt,tr, seg, pt_no) in self.walk()) # type:ignore - ,key=lambda x: x.location.distance_2d(location) if x is not None else mod_math.inf + ,key=lambda x: x.location.distance_2d(location) or mod_math.inf if x is not None else mod_math.inf ,default=None) def add_elevation(self, delta: float) -> None: From 6c1b702480498793a60443ab6dacafe329a3c378 Mon Sep 17 00:00:00 2001 From: Paul Traina Date: Thu, 14 Dec 2023 16:48:28 -0800 Subject: [PATCH 2/2] add get_nearest_locations for GPXTrack --- gpxpy/gpx.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/gpxpy/gpx.py b/gpxpy/gpx.py index 6defc69..5a2b9eb 100644 --- a/gpxpy/gpx.py +++ b/gpxpy/gpx.py @@ -1898,6 +1898,89 @@ def has_elevations(self) -> bool: return result + def get_points_data(self, distance_2d: bool=False) -> List[PointData]: + """ + Returns a list of tuples containing the actual point, its distance from the start, + -1, segment_no, and segment_point_no + """ + distance_from_start: float = 0.0 + previous_point: Optional[GPXTrackPoint] = None + + # (point, distance_from_start) pairs: + points = [] + + for segment_no, segment in enumerate(self.segments): + for point_no, point in enumerate(segment.points): + if previous_point and point_no > 0: + if distance_2d: + distance = point.distance_2d(previous_point) + else: + distance = point.distance_3d(previous_point) + + distance_from_start += distance or 0.0 + + points.append(PointData(point, distance_from_start, -1, segment_no, point_no)) + + previous_point = point + + return points + + def get_nearest_locations(self, location: mod_geo.Location, threshold_distance: float=0.01) -> List[NearestLocationData]: + """ + Returns a list of locations of elements like + consisting of points where the location may be on the track + + threshold_distance is the minimum distance from the track + so that the point *may* be counted as to be "on the track". + For example 0.01 means 1% of the track distance. + """ + + assert location + assert threshold_distance + + result: List[NearestLocationData] = [] + + points = self.get_points_data() + + if not points: + return result + + distance: Optional[float] = points[- 1][1] + + threshold = (distance or 0.0) * threshold_distance + + min_distance_candidate = None + distance_from_start_candidate = None + track_no_candidate: Optional[int] = None + segment_no_candidate: Optional[int] = None + point_no_candidate: Optional[int] = None + point_candidate: Optional[GPXTrackPoint] = None + + for point, distance_from_start, track_no, segment_no, point_no in points: + distance = location.distance_3d(point) + if (distance or 0.0) < threshold: + if min_distance_candidate is None or distance < min_distance_candidate: + min_distance_candidate = distance + distance_from_start_candidate = distance_from_start + track_no_candidate = track_no + segment_no_candidate = segment_no + point_no_candidate = point_no + point_candidate = point + else: + if distance_from_start_candidate is not None and point_candidate and track_no_candidate is not None and segment_no_candidate is not None and point_no_candidate is not None: + result.append(NearestLocationData(point_candidate, track_no_candidate, segment_no_candidate, point_no_candidate)) + min_distance_candidate = None + distance_from_start_candidate = None + track_no_candidate = None + segment_no_candidate = None + point_no_candidate = None + point_candidate = None + + if distance_from_start_candidate is not None and point_candidate and track_no_candidate is not None and segment_no_candidate is not None and point_no_candidate is not None: + result.append(NearestLocationData(point_candidate, track_no_candidate, segment_no_candidate, point_no_candidate)) + + return result + def get_nearest_location(self, location: mod_geo.Location) -> Optional[NearestLocationData]: """ Returns (location, track_segment_no, track_point_no) for nearest location on track """ return min((NearestLocationData(pt, -1, seg, pt_no) for (pt, seg, pt_no) in self.walk()) # type: ignore