Routing added on maps

This commit is contained in:
hbrain 2026-07-01 05:41:05 +02:00
parent e737bcab38
commit 9a2e507c24
3 changed files with 55 additions and 11 deletions

File diff suppressed because one or more lines are too long

View file

@ -15,7 +15,7 @@ from urllib.request import Request, urlopen
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
VERSION = "2026-05-31-infographic-v5-single-location-footer-fix" VERSION = "2026-07-01-road-route-no-distance"
MAP_WIDTH = 906 MAP_WIDTH = 906
MAP_HEIGHT = 510 MAP_HEIGHT = 510
@ -26,6 +26,7 @@ CLUSTER_RADIUS_KM = 2.0
FOOTER_HEIGHT = 118 FOOTER_HEIGHT = 118
FOOTER_MARGIN = 14 FOOTER_MARGIN = 14
USER_AGENT = "gpsmap-infographic-script/1.0" USER_AGENT = "gpsmap-infographic-script/1.0"
OSRM_ROUTE_URL = "https://router.project-osrm.org/route/v1/driving"
PHOTO_EXTENSIONS = { PHOTO_EXTENSIONS = {
".jpg", ".jpeg", ".png", ".heic", ".heif", ".webp", ".tif", ".tiff" ".jpg", ".jpeg", ".png", ".heic", ".heif", ".webp", ".tif", ".tiff"
@ -168,6 +169,47 @@ def haversine_km(lat1, lon1, lat2, lon2):
return 2 * r * math.atan2(math.sqrt(a), math.sqrt(1 - a)) return 2 * r * math.atan2(math.sqrt(a), math.sqrt(1 - a))
def fetch_road_route(points):
"""Return route geometry as [(lat, lon), ...], falling back to straight points.
Uses OSRM's public OpenStreetMap-based demo router. The footer intentionally
does not show route distance; this is only for drawing a more realistic path.
"""
if len(points) < 2:
return points
try:
coords = ";".join(f"{lon:.6f},{lat:.6f}" for lat, lon in points)
params = urlencode({
"overview": "full",
"geometries": "geojson",
"steps": "false",
})
url = f"{OSRM_ROUTE_URL}/{coords}?{params}"
request = Request(url, headers={"User-Agent": USER_AGENT})
with urlopen(request, timeout=30) as response:
data = json.loads(response.read().decode("utf-8"))
if data.get("code") != "Ok":
message = data.get("message") or data.get("code") or "unknown OSRM error"
raise RuntimeError(message)
route = (data.get("routes") or [{}])[0]
coordinates = route.get("geometry", {}).get("coordinates") or []
road_points = [(float(lat), float(lon)) for lon, lat in coordinates]
if len(road_points) < 2:
raise RuntimeError("empty route geometry")
print(f"Road route: {len(road_points)} geometry points")
return road_points
except Exception as e:
print(f"Road route unavailable; using straight lines: {e}")
return points
def collect_media(media_dir): def collect_media(media_dir):
items = [] items = []
@ -604,19 +646,18 @@ def main():
clusters = make_clusters(items) clusters = make_clusters(items)
points = [(c.lat, c.lon) for c in clusters] points = [(c.lat, c.lon) for c in clusters]
route_latlon = fetch_road_route(points)
view_points = (route_latlon + points) if len(route_latlon) > 1 else points
zoom = choose_zoom(points, MAP_WIDTH, MAP_HEIGHT) zoom = choose_zoom(view_points, MAP_WIDTH, MAP_HEIGHT)
top_left_x, top_left_y = map_view(points, zoom) top_left_x, top_left_y = map_view(view_points, zoom)
label = make_map_label(items, clusters) label = make_map_label(items, clusters)
photo_count = sum(1 for i in items if i.kind == "photo") photo_count = sum(1 for i in items if i.kind == "photo")
video_count = sum(1 for i in items if i.kind == "video") video_count = sum(1 for i in items if i.kind == "video")
distance = round(route_distance_km(clusters))
stats_parts = [plural(photo_count, "photo")] stats_parts = [plural(photo_count, "photo")]
if video_count: if video_count:
stats_parts.append(plural(video_count, "video")) stats_parts.append(plural(video_count, "video"))
if len(clusters) > 1 and distance > 0:
stats_parts.append(f"{distance} km route")
stats = "".join(stats_parts) stats = "".join(stats_parts)
date_text = format_date_range(items) date_text = format_date_range(items)
print(f"Map label: {label}") print(f"Map label: {label}")
@ -627,10 +668,11 @@ def main():
image = render_tiles(top_left_x, top_left_y, zoom) image = render_tiles(top_left_x, top_left_y, zoom)
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
route_points = [screen_xy(c.lat, c.lon, zoom, top_left_x, top_left_y) for c in clusters] route_points = [screen_xy(lat, lon, zoom, top_left_x, top_left_y) for lat, lon in route_latlon]
draw_route(draw, route_points) draw_route(draw, route_points)
for cluster, (x, y) in zip(clusters, route_points): marker_points = [screen_xy(c.lat, c.lon, zoom, top_left_x, top_left_y) for c in clusters]
for cluster, (x, y) in zip(clusters, marker_points):
draw_cluster_marker(draw, x, y, cluster) draw_cluster_marker(draw, x, y, cluster)
draw_bottom_title(draw, label, stats, date_text) draw_bottom_title(draw, label, stats, date_text)

View file

@ -662,10 +662,12 @@ $siteHeaderTitle = $headerTitles[array_rand($headerTitles)];
<video controls controlsList="nodownload" oncontextmenu="return false" preload="metadata" src="<?=h($url)?>"<?= $posterUrl !== '' ? ' poster="'.h($posterUrl).'"' : '' ?>></video> <video controls controlsList="nodownload" oncontextmenu="return false" preload="metadata" src="<?=h($url)?>"<?= $posterUrl !== '' ? ' poster="'.h($posterUrl).'"' : '' ?>></video>
<?php <?php
$map = pathinfo($name, PATHINFO_FILENAME)."_map.png"; $map = pathinfo($name, PATHINFO_FILENAME)."_map.png";
$mapPath = $config['videos_dir']."/".$map;
$mapUrl = "/" . ltrim((string)$config['public_videos'], "/") . "/" . rawurlencode($map) . (is_file($mapPath) ? "?v=" . filemtime($mapPath) : "");
$hideMap = !empty($hiddenMapOutputs[$name]); $hideMap = !empty($hiddenMapOutputs[$name]);
if(!$hideMap && is_file($config['videos_dir']."/".$map)): if(!$hideMap && is_file($mapPath)):
?> ?>
<a class="map-image-link" href="<?=h("/" . ltrim((string)$config['public_videos'], "/") . "/" . rawurlencode($map))?>"><img class="map-image" src="<?=h("/" . ltrim((string)$config['public_videos'], "/") . "/" . rawurlencode($map))?>" alt="Map"></a> <a class="map-image-link" href="<?=h($mapUrl)?>"><img class="map-image" src="<?=h($mapUrl)?>" alt="Map"></a>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="video-info"> <div class="video-info">