"""Build a static store map (no third-party iframe, no cookie) from OpenStreetMap tiles. Usage: python make_map.py LAT LON OUT.jpg [ZOOM] """ import io import math import sys import time import urllib.request from PIL import Image, ImageDraw, ImageFont lat, lon, out = float(sys.argv[1]), float(sys.argv[2]), sys.argv[3] zoom = int(sys.argv[4]) if len(sys.argv) > 4 else 16 W, H = 1200, 700 # output size, 2x for retina UA = 'footfr-store-page-test/1.0 (one-off static map, contact shop@foot.fr)' def to_px(lat, lon, z): n = 2 ** z x = (lon + 180.0) / 360.0 * n * 256 y = (1.0 - math.asinh(math.tan(math.radians(lat))) / math.pi) / 2.0 * n * 256 return x, y cx, cy = to_px(lat, lon, zoom) x0, y0 = int(cx - W / 2), int(cy - H / 2) canvas = Image.new('RGB', (W, H), 'white') for tx in range(x0 // 256, (x0 + W) // 256 + 1): for ty in range(y0 // 256, (y0 + H) // 256 + 1): url = f'https://tile.openstreetmap.org/{zoom}/{tx}/{ty}.png' req = urllib.request.Request(url, headers={'User-Agent': UA}) with urllib.request.urlopen(req) as r: tile = Image.open(io.BytesIO(r.read())).convert('RGB') canvas.paste(tile, (tx * 256 - x0, ty * 256 - y0)) time.sleep(0.2) # Soften the base map so the marker stands out, like a muted brand map. canvas = Image.blend(canvas, Image.new('RGB', (W, H), (245, 245, 245)), 0.25) d = ImageDraw.Draw(canvas) mx, my = W // 2, H // 2 # Pin: black circle with white ring, brand blue dot. d.ellipse([mx - 26, my - 26, mx + 26, my + 26], fill=(29, 29, 29), outline='white', width=6) d.ellipse([mx - 9, my - 9, mx + 9, my + 9], fill=(29, 187, 255)) try: font = ImageFont.truetype('arial.ttf', 22) except OSError: font = ImageFont.load_default() label = '© les contributeurs OpenStreetMap' tw = d.textlength(label, font=font) d.rectangle([W - tw - 20, H - 36, W, H], fill=(255, 255, 255)) d.text((W - tw - 10, H - 32), label, fill=(60, 60, 60), font=font) canvas.save(out, 'JPEG', quality=82, optimize=True, progressive=True) print('saved', out, canvas.size)