Skip to content

Python Quickstart

Use server-side: tile proxying, batch geocoding, downloads, signed-URL signing.

For client-side maps in a Python web app (Streamlit, Dash, Flask templates), serve our style URL straight to MapLibre in the browser.

Install

bash
pip install requests

Tile proxy (Flask)

python
import os
import time
import hmac
import hashlib
from flask import Flask, request, Response
import requests

app = Flask(__name__)
KEY = os.environ['MFD_SRV_KEY']
SECRET = os.environ['MFD_SRV_SECRET']
TILE_BASE = 'https://tiles.mapsfordevs.com'

def sign(method: str, path: str) -> dict:
    ts = str(int(time.time()))
    sig = hmac.new(SECRET.encode(), f'{method}\n{path}\n{ts}'.encode(), hashlib.sha256).hexdigest()
    return {'Authorization': f'Bearer {KEY}', 'X-MFD-Timestamp': ts, 'X-MFD-Signature': sig}

@app.route('/tiles/<int:z>/<int:x>/<int:y>.pbf')
def tile(z, x, y):
    path = f'/tiles/{z}/{x}/{y}.pbf'
    upstream = requests.get(f'{TILE_BASE}{path}', headers=sign('GET', path), stream=True)
    return Response(
        upstream.iter_content(8192),
        status=upstream.status_code,
        content_type='application/x-protobuf',
        headers={'Cache-Control': 'public, max-age=86400'}
    )

Batch geocoding

python
import requests

KEY = 'mfd_pub_xxx'

def geocode(query: str, country: str = None, limit: int = 5):
    params = {'q': query, 'limit': limit}
    if country:
        params['country'] = country
    r = requests.get(
        'https://api.mapsfordevs.com/geocode/forward',
        headers={'Authorization': f'Bearer {KEY}'},
        params=params,
        timeout=10
    )
    body = r.json()
    if not body.get('ok'):
        raise RuntimeError(body.get('error'))
    return body['items']

results = geocode('80 Sturdee Ave Rosebank', country='za')
for hit in results:
    print(hit['lat'], hit['lng'], hit['label'])

Download a country .mfdmap

python
import requests

PUB_KEY = 'mfd_pub_xxx'

def buy_country(slug: str, plan: str = 'perpetual'):
    r = requests.post(
        'https://api.mapsfordevs.com/downloads/purchase',
        headers={'Authorization': f'Bearer {PUB_KEY}'},
        json={'countrySlug': slug, 'plan': plan},
        timeout=30
    )
    r.raise_for_status()
    return r.json()['licenseToken']

def fetch_mfdmap(token: str, dest_path: str):
    r = requests.get(f'https://api.mapsfordevs.com/downloads/{token}', timeout=30)
    url = r.json()['url']
    with requests.get(url, stream=True, timeout=600) as resp:
        resp.raise_for_status()
        with open(dest_path, 'wb') as f:
            for chunk in resp.iter_content(1024 * 1024):
                f.write(chunk)

token = buy_country('south-africa', 'perpetual')
fetch_mfdmap(token, '/data/za.mfdmap')

Retry with backoff

python
import time
import requests
from requests.exceptions import RequestException

def fetch_with_retry(url, headers=None, attempts=4):
    for i in range(attempts):
        try:
            r = requests.get(url, headers=headers, timeout=10)
            if r.status_code < 500 and r.status_code != 429:
                return r
            delay = int(r.headers.get('Retry-After', 0)) or (2 ** i)
        except RequestException:
            delay = 2 ** i
        time.sleep(delay)
    raise RuntimeError(f'failed after {attempts} attempts')