Fix gpsmap.py to correctly use output file path

This commit is contained in:
hbrain 2026-05-31 01:46:29 +02:00
parent 68301c6d23
commit 819a53a159

View file

@ -15,7 +15,7 @@ from urllib.request import Request, urlopen
from PIL import Image, ImageDraw, ImageFont
VERSION = "2026-05-31-infographic-v4-date-stats-centered-counts"
VERSION = "2026-05-31-infographic-v5-single-location-footer-fix"
MAP_WIDTH = 906
MAP_HEIGHT = 510
@ -23,6 +23,8 @@ TILE_SIZE = 256
MIN_ZOOM = 3
MAX_ZOOM = 13
CLUSTER_RADIUS_KM = 2.0
FOOTER_HEIGHT = 118
FOOTER_MARGIN = 14
USER_AGENT = "gpsmap-infographic-script/1.0"
PHOTO_EXTENSIONS = {
@ -244,6 +246,9 @@ def reverse_geocode_parts(lat, lon):
address.get("city")
or address.get("town")
or address.get("village")
or address.get("island")
or address.get("suburb")
or address.get("hamlet")
or address.get("municipality")
or ""
)
@ -261,10 +266,41 @@ def reverse_geocode_parts(lat, lon):
return {"city": "", "region": "", "country": ""}
def make_map_label(items):
def clean_place_name(value):
value = (value or "").strip()
# Shorter Danish names look much better in the footer.
replacements = {
"Region Syddanmark": "Syddanmark",
"Region Midtjylland": "Midtjylland",
"Region Nordjylland": "Nordjylland",
"Region Sjælland": "Sjælland",
"Region Hovedstaden": "Hovedstaden",
}
return replacements.get(value, value)
def make_map_label(items, clusters=None):
lats = [i.lat for i in items]
lons = [i.lon for i in items]
# If this is a one-location map, use the center point and keep the label short.
# Example: Rømø DANMARK instead of Tønder Kommune Region Syddanmark DANMARK.
if clusters is not None and len(clusters) == 1:
lat = sum(lats) / len(lats)
lon = sum(lons) / len(lons)
info = reverse_geocode_parts(lat, lon)
city = clean_place_name(info.get("city", ""))
region = clean_place_name(info.get("region", ""))
country = clean_place_name(info.get("country", ""))
if city and country:
return f"{city} {country}"
if region and country:
return f"{region} {country}"
return country
sample_points = [
(min(lats), min(lons)),
(max(lats), max(lons)),
@ -275,12 +311,16 @@ def make_map_label(items):
for lat, lon in sample_points:
info = reverse_geocode_parts(lat, lon)
if info["city"]:
cities.add(info["city"])
if info["region"]:
regions.add(info["region"])
if info["country"]:
countries.add(info["country"])
city = clean_place_name(info.get("city", ""))
region = clean_place_name(info.get("region", ""))
country = clean_place_name(info.get("country", ""))
if city:
cities.add(city)
if region:
regions.add(region)
if country:
countries.add(country)
if len(countries) > 1:
return "".join(sorted(countries))
@ -303,7 +343,6 @@ def make_map_label(items):
return f"{region} {country}"
return country
def format_date_range(items):
dates = [i.taken_at for i in items if i.taken_at]
if not dates:
@ -330,10 +369,9 @@ def latlon_to_pixel(lat, lon, zoom):
def choose_zoom(points, width, height):
# Leave room for bottom title/stat text.
pad_x = 80
pad_top = 55
pad_bottom = 125
pad_x = 70
pad_top = 45
pad_bottom = FOOTER_HEIGHT + FOOTER_MARGIN
usable_width = width - 2 * pad_x
usable_height = height - pad_top - pad_bottom
@ -350,22 +388,25 @@ def choose_zoom(points, width, height):
return MIN_ZOOM
def map_view(points, zoom):
pixels = [latlon_to_pixel(lat, lon, zoom) for lat, lon in points]
xs = [p[0] for p in pixels]
ys = [p[1] for p in pixels]
# Center route slightly above image center so title/stat text has room at bottom.
center_x = (min(xs) + max(xs)) / 2
center_y = (min(ys) + max(ys)) / 2 + 25
center_y = (min(ys) + max(ys)) / 2
top_left_x = center_x - MAP_WIDTH / 2
top_left_y = center_y - MAP_HEIGHT / 2
# Keep the route/markers in the upper map area and reserve the lower footer
# for date, statistics and location. This prevents single-location maps from
# having the marker collide with the footer.
usable_center_x = MAP_WIDTH / 2
usable_center_y = (MAP_HEIGHT - FOOTER_HEIGHT) / 2
top_left_x = center_x - usable_center_x
top_left_y = center_y - usable_center_y
return top_left_x, top_left_y
def download_tile(x, y, z):
url = f"https://tile.openstreetmap.org/{z}/{x}/{y}.png"
request = Request(url, headers={"User-Agent": USER_AGENT})
@ -491,10 +532,21 @@ def draw_centered_text(draw, y, text, font, fill="black"):
draw.text(((MAP_WIDTH - w) / 2, y), text, font=font, fill=fill)
def fit_font(draw, text, font_path, start_size, max_width, min_size=12):
for size in range(start_size, min_size - 1, -1):
font = load_font(font_path, size)
bbox = draw.textbbox((0, 0), text, font=font)
if bbox[2] - bbox[0] <= max_width:
return font
return load_font(font_path, min_size)
def draw_bottom_title(draw, label, stats, date_text=""):
title_font = load_font(FONT_BOLD, 38)
stats_font = load_font(FONT_REGULAR, 30)
date_font = load_font(FONT_REGULAR, 22)
max_text_width = MAP_WIDTH - 28
title_font = fit_font(draw, label, FONT_BOLD, 26, max_text_width, min_size=20)
stats_font = fit_font(draw, stats, FONT_REGULAR, 26, max_text_width, min_size=16)
date_font = fit_font(draw, date_text, FONT_REGULAR, 22, max_text_width, min_size=14) if date_text else None
title_bbox = draw.textbbox((0, 0), label, font=title_font)
stats_bbox = draw.textbbox((0, 0), stats, font=stats_font)
@ -504,8 +556,8 @@ def draw_bottom_title(draw, label, stats, date_text=""):
stats_h = stats_bbox[3] - stats_bbox[1]
date_h = date_bbox[3] - date_bbox[1] if date_text else 0
gap = 4
bottom_margin = 20
gap = 3
bottom_margin = 14
y_title = MAP_HEIGHT - title_h - bottom_margin
y_stats = y_title - stats_h - gap
@ -516,7 +568,6 @@ def draw_bottom_title(draw, label, stats, date_text=""):
draw_centered_text(draw, y_stats, stats, stats_font, fill=(30, 30, 30))
draw_centered_text(draw, y_title, label, title_font, fill="black")
def main():
print(f"gpsmap version: {VERSION}")
@ -525,19 +576,18 @@ def main():
sys.exit(1)
media_dir = Path(sys.argv[1])
output_dir = Path(sys.argv[2])
output_arg = Path(sys.argv[2])
if not media_dir.is_dir():
print(f"Media directory not found: {media_dir}")
sys.exit(1)
output_arg = output_dir
if output_arg.suffix == ".png":
output_png = output_arg
output_png.parent.mkdir(parents=True, exist_ok=True)
else:
output_dir.mkdir(parents=True, exist_ok=True)
output_png = output_dir / f"{media_dir.resolve().name}.png"
output_arg.mkdir(parents=True, exist_ok=True)
output_png = output_arg / f"{media_dir.resolve().name}.png"
items = collect_media(media_dir)
if not items:
@ -550,14 +600,15 @@ def main():
zoom = choose_zoom(points, MAP_WIDTH, MAP_HEIGHT)
top_left_x, top_left_y = map_view(points, zoom)
label = make_map_label(items)
label = make_map_label(items, clusters)
photo_count = sum(1 for i in items if i.kind == "photo")
video_count = sum(1 for i in items if i.kind == "video")
distance = round(route_distance_km(clusters))
stats_parts = [plural(photo_count, "photo")]
if video_count:
stats_parts.append(plural(video_count, "video"))
stats_parts.append(f"{distance} km route")
if len(clusters) > 1 and distance > 0:
stats_parts.append(f"{distance} km route")
stats = "".join(stats_parts)
date_text = format_date_range(items)
print(f"Map label: {label}")