Skip to content

Rust Quickstart

Server-side: tile proxy, geocoding client, downloads.

Cargo.toml

toml
[dependencies]
reqwest = { version = "0.12", features = ["json", "stream"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
hmac = "0.12"
sha2 = "0.10"
hex = "0.4"

HMAC signer

rust
use hmac::{Hmac, Mac};
use sha2::Sha256;
use std::time::{SystemTime, UNIX_EPOCH};

type HmacSha256 = Hmac<Sha256>;

pub fn sign_mfd(method: &str, path: &str, secret: &[u8]) -> (String, String) {
    let ts = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs()
        .to_string();
    let mut mac = HmacSha256::new_from_slice(secret).unwrap();
    mac.update(format!("{}\n{}\n{}", method, path, ts).as_bytes());
    let sig = hex::encode(mac.finalize().into_bytes());
    (ts, sig)
}

Tile proxy (axum)

rust
use axum::{
    extract::Path,
    http::{HeaderMap, StatusCode},
    response::Response,
    routing::get,
    Router,
};

async fn tile(Path((z, x, y)): Path<(u8, u32, u32)>) -> Result<Response, StatusCode> {
    let key = std::env::var("MFD_SRV_KEY").unwrap();
    let secret = std::env::var("MFD_SRV_SECRET").unwrap();
    let path = format!("/tiles/{}/{}/{}.pbf", z, x, y);
    let (ts, sig) = sign_mfd("GET", &path, secret.as_bytes());

    let upstream = reqwest::Client::new()
        .get(format!("https://tiles.mapsfordevs.com{}", path))
        .bearer_auth(&key)
        .header("X-MFD-Timestamp", ts)
        .header("X-MFD-Signature", sig)
        .send()
        .await
        .map_err(|_| StatusCode::BAD_GATEWAY)?;

    let bytes = upstream.bytes().await.map_err(|_| StatusCode::BAD_GATEWAY)?;
    Ok(Response::builder()
        .header("Content-Type", "application/x-protobuf")
        .header("Cache-Control", "public, max-age=86400")
        .body(bytes.into())
        .unwrap())
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/tiles/:z/:x/:y.pbf", get(tile));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Geocoding

rust
use serde::Deserialize;

#[derive(Deserialize)]
pub struct GeocodeItem {
    pub lat: f64,
    pub lng: f64,
    pub label: String,
    pub confidence: f64,
}

#[derive(Deserialize)]
struct GeocodeResp {
    ok: bool,
    items: Option<Vec<GeocodeItem>>,
    error: Option<String>,
}

pub async fn geocode(api_key: &str, query: &str) -> Result<Vec<GeocodeItem>, String> {
    let url = format!(
        "https://api.mapsfordevs.com/geocode/forward?q={}&limit=5",
        urlencoding::encode(query)
    );
    let resp: GeocodeResp = reqwest::Client::new()
        .get(&url)
        .bearer_auth(api_key)
        .send()
        .await
        .map_err(|e| e.to_string())?
        .json()
        .await
        .map_err(|e| e.to_string())?;

    if !resp.ok {
        return Err(resp.error.unwrap_or_default());
    }
    Ok(resp.items.unwrap_or_default())
}