Getting a static OS Explorer (1:25,000) / OS Landranger (1:50,000) style map image out of streetmap.co.uk programmatically, so John can run a script on his phone (Python Codepad app), get back a base64-encoded image, paste it into a Claude chat, and have Claude decode/view it — working around the fact that Claude's own web_fetch tool cannot reach streetmap.co.uk reliably (see Background below).
Claude's web_fetch tool served a stale cached response for streetmap.co.uk/map?... URLs regardless of the query parameters passed — every fetch after the first returned the same BS36 1LU content. Fetching the newer streetmap.co.uk/grid/... path format failed outright with a "URL not in any prior search or fetch result" permission error. So the plan is: John's phone does the real HTTP fetching (no such restriction there), and ships the result back as text (base64) for Claude to decode locally and view as an image.
Documented at their own How to link to us page:
https://streetmap.co.uk/grid/<easting>_<northing>_<zoom>
zoom codes:
106 Full street scale
110 Street scale
115 Town 25k scale <- OS Explorer-equivalent
120 Town 50k scale <- OS Landranger-equivalent
126 Full road scale
130 Road scale
140 County scale
150 Region scale
Confirmed working by eye (screenshot from John's phone, 2026-08-02): https://streetmap.co.uk/grid/537667_180071_120 rendered a correct 1:50,000-style map centred on Work (Canary Wharf/Isle of Dogs), with the location marker in the right place.
Grid references (OSGB36 National Grid, EPSG:27700) were computed from the notes-store gazetteer's WGS84 lat/lon via pyproj (Transformer.from_crs('EPSG:4326', 'EPSG:27700')), cross-checked against the known BS36 1LU grid ref (365032, 180850) to within a few metres:
| Place | Lat/Lon (gazetteer) | OSGB36 grid ref |
|---|---|---|
| Home | 51.525196, -2.505474 | 365029, 180819 |
| Work | 51.502813, -0.017929 | 537667, 180071 |
The page's og:image meta tag looks like the obvious source for a static raster of the map (e.g. https://www.streetmap.co.uk/emm/TQ3766780071_z_25k_76.gif for Work at 25k) but fetching it directly returns a genuine 404 from streetmap's own server (Kestrel/ASP.NET Core backend, their own styled 404 HTML page) — not an anti-hotlinking block. So the filename in og:image is stale or templated rather than a real generated file. Setting Referer to the page URL didn't help, which supports "file genuinely doesn't exist" over "blocked".
Current approach (untested as of this note): instead of trusting og:image alone, scan the whole page for every .gif/.png/.jpg URL referenced anywhere (meta tags, <img>, inline JS, data- attributes) and try each in turn, keeping whichever one actually returns image bytes rather than another HTML page. Also saves the full page HTML to page.html for manual inspection if none of the candidates work.
Likely next step if that still fails: the on-screen map John showed in his screenshot is presumably rendered by JS calling a tile or print/export endpoint that isn't a simple <img src> on the static page — would need to inspect page.html's <script> blocks for the actual endpoint pattern (something parameterised by grid ref/zoom), or look for a "print this map" feature (there's a visible print icon in the UI) which may hit a dedicated static-image-generating endpoint.
Current version (iterating — update this block when the script changes materially rather than only noting it verbally). Runs on John's phone via Python Codepad (needs requests); not run in Claude's own sandbox since streetmap.co.uk is not on Claude's bash-tool network allowlist.
#!/usr/bin/env python3
"""
Fetch a streetmap.co.uk map page, pull out its map image, then
gzip + base64 encode the image bytes so the result can be pasted
as plain text into a chat message.
Usage:
python3 fetch_streetmap.py
python3 fetch_streetmap.py "https://streetmap.co.uk/grid/537667_180071_115"
Prints the base64 text to stdout, and also writes it to
streetmap_b64.txt in the current directory (handy if it's too long
to comfortably copy from the terminal).
"""
import sys
import gzip
import base64
import re
import requests
DEFAULT_URL = "https://streetmap.co.uk/grid/537667_180071_115" # Work, 25k
def make_session() -> requests.Session:
ua = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
)
sess = requests.Session()
sess.headers.update({"User-Agent": ua})
return sess
def find_candidate_image_urls(html: str, page_url: str) -> list:
"""Every .gif/.png/.jpg URL mentioned anywhere on the page —
in meta tags, <img> tags, inline JS, data- attributes, etc.
The og:image one turned out to be a stale/wrong filename, so
cast a wide net instead of trusting a single tag."""
from urllib.parse import urljoin
found = re.findall(r'["\'](https?://[^"\']+?\.(?:gif|png|jpg|jpeg))["\']', html, re.IGNORECASE)
found += re.findall(r'["\'](/[^"\']+?\.(?:gif|png|jpg|jpeg))["\']', html, re.IGNORECASE)
seen = set()
urls = []
for u in found:
full = urljoin(page_url, u)
if full not in seen:
seen.add(full)
urls.append(full)
return urls
def fetch_map_image_bytes(page_url: str) -> bytes:
sess = make_session()
page = sess.get(page_url, timeout=20)
page.raise_for_status()
with open("page.html", "w") as f:
f.write(page.text)
print(f"Saved full page HTML to page.html ({len(page.text)} chars)", file=sys.stderr)
candidates = find_candidate_image_urls(page.text, page_url)
print(f"Found {len(candidates)} candidate image URL(s):", file=sys.stderr)
for u in candidates:
print(f" {u}", file=sys.stderr)
if not candidates:
raise RuntimeError("No image URLs found on the page — check page.html")
for image_url in candidates:
img = sess.get(image_url, headers={"Referer": page_url}, timeout=20)
if img.ok and img.content[:3] not in (b"<!D", b"<ht"):
print(f"Using: {image_url} ({len(img.content)} bytes)", file=sys.stderr)
return img.content
print(f" skip {image_url}: {img.status_code}", file=sys.stderr)
raise RuntimeError("None of the candidate image URLs loaded — check page.html by hand")
def main():
page_url = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_URL
raw_bytes = fetch_map_image_bytes(page_url)
print(f"Fetched image: {len(raw_bytes)} bytes", file=sys.stderr)
gzipped = gzip.compress(raw_bytes)
print(f"Gzipped: {len(gzipped)} bytes", file=sys.stderr)
b64 = base64.b64encode(gzipped).decode("ascii")
print(f"Base64: {len(b64)} chars", file=sys.stderr)
with open("streetmap_b64.txt", "w") as f:
f.write(b64)
print("Wrote streetmap_b64.txt", file=sys.stderr)
print(b64)
if __name__ == "__main__":
main()
• Not yet resolved: run the current "scan every image URL" version and report back which (if any) candidate URL actually loads a real image.
• If none load, inspect page.html's <script> blocks for the real tile/print endpoint the interactive map uses.
• Once an image is successfully retrieved end-to-end (base64 pasted into chat, Claude decodes/gunzips/views it), record the working approach here and simplify the script back down.
• gazetteer — source of Home/Work coordinates used here
• using-gps-data — broader GPS/location conventions