From c69a6000537b6deb22aea8937029c98ffcc8d070 Mon Sep 17 00:00:00 2001 From: Daniel Himmelstein Date: Mon, 14 Oct 2024 21:26:28 -0400 Subject: [PATCH] clean OpenSkiMap coordinates refs https://github.com/russellporter/openskimap.org/issues/137 and address NaN mean_bearing validation fails --- ski_bearings/bearing.py | 8 +++++++- ski_bearings/osmnx_utils.py | 29 +++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/ski_bearings/bearing.py b/ski_bearings/bearing.py index 32e4f66d18..9d33fd2e62 100644 --- a/ski_bearings/bearing.py +++ b/ski_bearings/bearing.py @@ -111,9 +111,15 @@ def get_bearing_summary_stats( # Calculate the strength/magnitude of the mean bearing mean_bearing_strength = np.linalg.norm(vector_sum) / np.sum(weights) + if np.isnan(mean_bearing_strength): + # some ski areas have no elevation variation, example 7cc74a14-fdc2-4b15-aaf9-8998433ffd86 + mean_bearing_strength = 0.0 # Convert the sum vector back to a bearing mean_bearing_rad = np.arctan2(vector_sum[1], vector_sum[0]) mean_bearing_deg = np.rad2deg(mean_bearing_rad) % 360 - return BearingSummaryStats(mean_bearing_deg, mean_bearing_strength) + return BearingSummaryStats( + mean_bearing_deg=round(mean_bearing_deg, 7), + mean_bearing_strength=round(mean_bearing_strength, 7), + ) diff --git a/ski_bearings/osmnx_utils.py b/ski_bearings/osmnx_utils.py index a054b76fa5..3578ae4da2 100644 --- a/ski_bearings/osmnx_utils.py +++ b/ski_bearings/osmnx_utils.py @@ -21,6 +21,24 @@ def suppress_user_warning( yield +def _clean_coordinates( + coordinates: list[tuple[float, float, float]], +) -> list[tuple[float, float, float]]: + """ + Sanitize coordinates to remove floating point errors and ensure downhill runs. + NOTE: longitude comes before latitude in GeoJSON and osmnx, which is different than GPS coordinates. + """ + # Round coordinates to undo floating point errors. + # https://github.com/russellporter/openskimap.org/issues/137 + coordinates = [ + (round(lon, 7), round(lat, 7), round(ele, 2)) for lon, lat, ele in coordinates + ] + if coordinates[0][2] < coordinates[-1][2]: + # Ensure the run is going downhill, such that starting elevation > ending elevation + coordinates.reverse() + return coordinates + + def create_networkx(runs: list[Any]) -> nx.MultiDiGraph: """ Convert runs to an newtorkx MultiDiGraph compatible with OSMnx. @@ -31,14 +49,13 @@ def create_networkx(runs: list[Any]) -> nx.MultiDiGraph: runs = [run for run in runs if run["geometry"]["type"] == "LineString"] graph.graph["run_count_filtered"] = len(runs) for run in runs: - # NOTE: longitude comes before latitude in GeoJSON and osmnx, which is different than GPS coordinates - for lon, lat, elevation in run["geometry"]["coordinates"]: + run["geometry"]["coordinates_clean"] = _clean_coordinates( + run["geometry"]["coordinates"] + ) + for lon, lat, elevation in run["geometry"]["coordinates_clean"]: graph.add_node((lon, lat), x=lon, y=lat, elevation=elevation) for run in runs: - coordinates = run["geometry"]["coordinates"].copy() - if coordinates[0][2] < coordinates[-1][2]: - # Ensure the run is going downhill, such that starting elevation > ending elevation - coordinates.reverse() + coordinates = run["geometry"]["coordinates_clean"].copy() lon_0, lat_0, elevation_0 = coordinates.pop(0) for lon_1, lat_1, elevation_1 in coordinates: graph.add_edge(