145 lines
4.6 KiB
Python
145 lines
4.6 KiB
Python
# map.py
|
||
#
|
||
# author: deng
|
||
# date: 20251127
|
||
|
||
import folium
|
||
|
||
|
||
class MapRenderer:
|
||
"""Render hiking routes on interactive maps."""
|
||
|
||
def __init__(self):
|
||
"""Initialize map renderer."""
|
||
pass
|
||
|
||
def create_route_map(self, points, start_point, end_point, waypoints=None, tile_layer='OpenStreetMap'):
|
||
"""Create an interactive map with the hiking route.
|
||
|
||
Args:
|
||
points: List of (latitude, longitude) tuples for the route
|
||
start_point: (latitude, longitude) tuple for start
|
||
end_point: (latitude, longitude) tuple for end
|
||
waypoints: List of waypoint dictionaries with 'lat', 'lon', 'name', 'elevation'
|
||
tile_layer: Map tile layer to use ('OpenStreetMap' or 'RudyMap')
|
||
|
||
Returns:
|
||
folium.Map: Interactive map object
|
||
"""
|
||
if not points or len(points) < 2:
|
||
return None
|
||
|
||
# Calculate center of the route
|
||
center_lat = sum(p[0] for p in points) / len(points)
|
||
center_lon = sum(p[1] for p in points) / len(points)
|
||
|
||
# Create map obj
|
||
route_map = folium.Map(
|
||
location=[center_lat, center_lon],
|
||
zoom_start=13,
|
||
tiles=None,
|
||
)
|
||
|
||
# Add OpenStreetMap layer
|
||
folium.TileLayer(
|
||
tiles='OpenStreetMap',
|
||
name='平面(OpenStreetMap)',
|
||
overlay=False,
|
||
control=True,
|
||
show=tile_layer == 'OpenStreetMap',
|
||
).add_to(route_map)
|
||
|
||
# Add Rudy Map (魯地圖) layer
|
||
folium.TileLayer(
|
||
tiles='https://tile.happyman.idv.tw/map/moi_osm/{z}/{x}/{y}.png',
|
||
attr='© <a href="https://rudy.basecamp.tw/">魯地圖</a>',
|
||
name='等高線(魯地圖)',
|
||
overlay=False,
|
||
control=True,
|
||
show=tile_layer == 'RudyMap',
|
||
max_zoom=18,
|
||
).add_to(route_map)
|
||
|
||
# Add layer control to switch between maps
|
||
folium.LayerControl(position='topright').add_to(route_map)
|
||
|
||
# Add the route as a red polyline
|
||
folium.PolyLine(
|
||
locations=points,
|
||
color='red',
|
||
weight=4,
|
||
opacity=0.8,
|
||
tooltip='登山路線',
|
||
).add_to(route_map)
|
||
|
||
# Add waypoints if provided
|
||
if waypoints:
|
||
for waypoint in waypoints:
|
||
popup_text = f'<b>{waypoint["name"]}</b>'
|
||
if waypoint.get('elevation'):
|
||
popup_text += f'<br>海拔: {waypoint["elevation"]} m'
|
||
|
||
folium.Marker(
|
||
location=[waypoint['lat'], waypoint['lon']],
|
||
popup=folium.Popup(popup_text, max_width=200),
|
||
tooltip=waypoint['name'],
|
||
icon=folium.Icon(color='orange', icon='info-sign'),
|
||
).add_to(route_map)
|
||
|
||
# Add start point marker (blue circle)
|
||
if start_point:
|
||
folium.CircleMarker(
|
||
location=(start_point[0], start_point[1]),
|
||
radius=8,
|
||
color='blue',
|
||
fill=True,
|
||
fill_color='blue',
|
||
fill_opacity=0.7,
|
||
popup=folium.Popup(f'<b>起點</b><br>海拔: {start_point[2]} m', max_width=200),
|
||
tooltip='起點',
|
||
).add_to(route_map)
|
||
|
||
# Add end point marker (blue circle)
|
||
if end_point:
|
||
folium.CircleMarker(
|
||
location=(end_point[0], end_point[1]),
|
||
radius=8,
|
||
color='blue',
|
||
fill=True,
|
||
fill_color='blue',
|
||
fill_opacity=0.7,
|
||
popup=folium.Popup(f'<b>終點</b><br>海拔: {end_point[2]} m', max_width=200),
|
||
tooltip='終點',
|
||
).add_to(route_map)
|
||
|
||
# Fit bounds to show entire route
|
||
route_map.fit_bounds(points)
|
||
|
||
return route_map
|
||
|
||
def add_hover_marker(self, route_map, position, label='當前位置'):
|
||
"""Add a hover position marker to the map.
|
||
|
||
Args:
|
||
route_map: Existing folium map
|
||
position: (latitude, longitude) tuple for the marker
|
||
label: Label for the marker
|
||
|
||
Returns:
|
||
folium.Map: Map with added marker
|
||
"""
|
||
if position and route_map:
|
||
folium.CircleMarker(
|
||
location=position,
|
||
radius=10,
|
||
color='orange',
|
||
fill=True,
|
||
fill_color='orange',
|
||
fill_opacity=0.9,
|
||
popup=label,
|
||
tooltip=label,
|
||
weight=3,
|
||
).add_to(route_map)
|
||
|
||
return route_map
|