Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions pretty_gpx/rendering_modes/city/data/bridges.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
BRIDGES_RELATIONS_ARRAY_NAME = "bridges_relations"
BRIDGES_WAYS_ARRAY_NAME = "bridges_ways"


@dataclass
class Bridge:
"""Bridge."""
Expand All @@ -46,6 +47,7 @@ class Bridge:
center: Point
direction: tuple[float, float] | None


class BridgeApproximation:
"""Bridge rectangle approximation."""
MAXIMUM_BRIDGE_ASPECT_RATIO = 0.75
Expand Down Expand Up @@ -73,7 +75,7 @@ def get_minimum_rectangle(polygon: ShapelyPolygon,
if not hasattr(min_rot_rect, "exterior"):
raise ValueError("The minimum rotated rectangle does not have an exterior.")

rectangle = ShapelyPolygon(np.array(min_rot_rect.exterior.coords)) # type: ignore
rectangle = ShapelyPolygon(np.array(min_rot_rect.exterior.coords)) # type: ignore
coords = list(rectangle.exterior.coords[:-1])
sides = [(np.linalg.norm(np.array(coords[i]) - np.array(coords[(i + 1) % 4])),
LineString([coords[i], coords[(i + 1) % 4]])) for i in range(4)]
Expand Down Expand Up @@ -101,8 +103,8 @@ def create_bridge(cls,
bridge_coords = get_way_coordinates(way_or_relation)
elif isinstance(way_or_relation, Relation) and way_or_relation.members:
outer_members = [member.geometry for member in way_or_relation.members
if isinstance(member, RelationWay) and member.geometry
and member.role == "outer"]
if isinstance(member, RelationWay) and member.geometry
and member.role == "outer"]
merged_ways = merge_ways(outer_members)
if len(merged_ways) > 1:
logger.error("Multiple geometries found")
Expand All @@ -126,11 +128,12 @@ def create_bridge(cls,
return None

return Bridge(name=bridge_name, polygon=bridge_simplified, length=bridge_length,
aspect_ratio=aspect_ratio, center=bridge_polygon.centroid, direction=bridge_dir)
aspect_ratio=aspect_ratio, center=bridge_polygon.centroid, direction=bridge_dir)
except Exception as e:
logger.error(f"Error processing bridge: {e}")
return None


class BridgeCrossingAnalyzer:
"""Bridge and track intersection."""
INTERSECTION_THRESHOLD = 0.75
Expand All @@ -147,8 +150,8 @@ def _calculate_intersection_length(intersection: BaseGeometry) -> float:
def _extract_intersection_coordinates(intersection: BaseGeometry) -> tuple[list[float], list[float]] | None:
"""Extract x,y coordinates from intersection geometry."""
if isinstance(intersection, GeometryCollection | MultiLineString):
coords = [(x, y) for geom in intersection.geoms
if isinstance(geom, LineString) for x, y in geom.coords]
coords = [(x, y) for geom in intersection.geoms
if isinstance(geom, LineString) for x, y in geom.coords]
if not coords:
return None
x_coords, y_coords = zip(*coords)
Expand Down Expand Up @@ -192,7 +195,7 @@ def analyze_track_bridge_crossing(cls, track: GpxTrack, bridges: list[Bridge]) -

intersection_direction = get_average_straight_line(coords[0], coords[1])[1]
angle = cls._calculate_crossing_angle(intersection_direction, bridge.direction)

if angle < cls.ANGLE_THRESHOLD:
logger.debug(f"{bridge.name} crossed, angle : {angle}")
crossed_bridges.append(bridge)
Expand All @@ -201,6 +204,7 @@ def analyze_track_bridge_crossing(cls, track: GpxTrack, bridges: list[Bridge]) -

return crossed_bridges


@profile
def prepare_download_city_bridges(query: OverpassQuery, track: GpxTrack) -> None:
"""Add the queries for city bridges inside the global OverpassQuery."""
Expand All @@ -223,6 +227,7 @@ def prepare_download_city_bridges(query: OverpassQuery, track: GpxTrack) -> None
relations=True,
radius_m=40)


@profile
def process_city_bridges(query: OverpassQuery, track: GpxTrack) -> list[ScatterPoint]:
"""Process the overpass API result to get the bridges of a city."""
Expand All @@ -233,7 +238,7 @@ def process_city_bridges(query: OverpassQuery, track: GpxTrack) -> list[ScatterP
bridges_direction: dict[str, tuple[float, LineString]] = {}
bridges_stats = {}
bridges_to_process = []

for way in query.get_query_result(BRIDGES_WAYS_ARRAY_NAME).ways:
if way.tags.get("bridge") and "man_made" not in way.tags:
line = LineString(get_way_coordinates(way))
Expand All @@ -252,12 +257,12 @@ def process_city_bridges(query: OverpassQuery, track: GpxTrack) -> list[ScatterP
bridges = [BridgeApproximation.create_bridge(way, bridges_stats) for way in bridges_to_process]
bridges.extend(BridgeApproximation.create_bridge(rel, bridges_stats)
for rel in query.get_query_result(BRIDGES_RELATIONS_ARRAY_NAME).relations)

crossed_bridges = BridgeCrossingAnalyzer.analyze_track_bridge_crossing(track, [b for b in bridges if b])
result = [ScatterPoint(name=b.name, lat=b.center.y, lon=b.center.x, category=ScatterPointCategory.CITY_BRIDGE)
for b in crossed_bridges]

logger.info(f"Found {len(result)} bridge(s)")
write_pickle(BRIDGES_CACHE.get_path(track), result)
query.add_cached_result(BRIDGES_CACHE.name, cache_file=BRIDGES_CACHE.get_path(track))
return result
return result
4 changes: 3 additions & 1 deletion pretty_gpx/test/test_bridges.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def test_new_york_bridges() -> None:
"Pulaski Bridge",
"Willis Avenue Bridge",
"Queensboro Bridge",
"Verrazzano-Narrows Bridge"})
"Verrazzano Bridge"})


def test_berlin_bridges() -> None:
"""Test Berlin Bridges."""
Expand All @@ -63,6 +64,7 @@ def test_berlin_bridges() -> None:
"Kottbusser Brücke",
"Potsdamer Brücke"})


def test_london_bridges() -> None:
"""Test London Bridges."""
__core_test_bridges(os.path.join(RUNNING_DIR, "marathon_london.gpx"),
Expand Down