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())
}