mirror of
https://github.com/ruvnet/RuView
synced 2026-07-31 18:51:42 +00:00
f49c722764
The Rust port lived two directories deep (rust-port/wifi-densepose-rs/) without any sibling under rust-port/ that warranted the extra level. Move the whole workspace up to v2/ to match v1/ (Python) at the same depth and shorten every cd / build command across the repo. git mv preserves history for all tracked files. 60 files updated for path references (CI workflows, ADRs, docs, scripts, READMEs, internal .claude-flow state). Two manual fixes for relative-cd paths in CLAUDE.md and ADR-043 that became wrong after the depth change (cd ../.. → cd ..). Validated: - cargo check --workspace --no-default-features → clean (after target/ nuke; the gitignored target/ was carried by the OS rename and had hard-coded old paths in build scripts) - cargo test --workspace --no-default-features → 1,539 passed, 0 failed, 8 ignored (same totals as pre-rename) - ESP32-S3 on COM7 → still streaming live CSI (cb #40300, RSSI -64 dBm) After-merge follow-up: contributors should `rm -rf v2/target` once and let cargo regenerate from the new path.
81 lines
2.7 KiB
Rust
81 lines
2.7 KiB
Rust
//! Satellite tile fetcher — XYZ/TMS tile download with caching.
|
|
|
|
use crate::cache::TileCache;
|
|
use crate::coord;
|
|
use crate::types::{GeoBBox, RasterTile, TileCoord};
|
|
use anyhow::Result;
|
|
|
|
/// Tile provider (all free, no API keys).
|
|
pub enum TileProvider {
|
|
/// Sentinel-2 cloudless mosaic (EOX, 10m, CC-BY-4.0)
|
|
Sentinel2Cloudless,
|
|
/// ESRI World Imagery (sub-meter, free tier)
|
|
EsriWorldImagery,
|
|
/// OpenStreetMap (map tiles, not satellite)
|
|
Osm,
|
|
}
|
|
|
|
impl TileProvider {
|
|
pub fn url(&self, coord: &TileCoord) -> String {
|
|
match self {
|
|
Self::Sentinel2Cloudless => format!(
|
|
"https://tiles.maps.eox.at/wmts/1.0.0/s2cloudless-2021_3857/default/g/{}/{}/{}.jpg",
|
|
coord.z, coord.y, coord.x
|
|
),
|
|
Self::EsriWorldImagery => format!(
|
|
"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{}/{}/{}",
|
|
coord.z, coord.y, coord.x
|
|
),
|
|
Self::Osm => format!(
|
|
"https://tile.openstreetmap.org/{}/{}/{}.png",
|
|
coord.z, coord.x, coord.y
|
|
),
|
|
}
|
|
}
|
|
|
|
pub fn name(&self) -> &str {
|
|
match self {
|
|
Self::Sentinel2Cloudless => "sentinel2",
|
|
Self::EsriWorldImagery => "esri",
|
|
Self::Osm => "osm",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Fetch a single tile with caching.
|
|
pub async fn fetch_tile(provider: &TileProvider, coord: &TileCoord, cache: &TileCache) -> Result<RasterTile> {
|
|
let cache_key = format!("tiles_{}_{}_{}.dat", coord.z, coord.x, coord.y);
|
|
|
|
if let Some(data) = cache.get(&cache_key) {
|
|
return Ok(RasterTile { coord: coord.clone(), data, bounds: coord::tile_bounds(coord) });
|
|
}
|
|
|
|
let url = provider.url(coord);
|
|
let client = reqwest::Client::builder()
|
|
.timeout(std::time::Duration::from_secs(10))
|
|
.user_agent("RuView/0.1 (https://github.com/ruvnet/RuView)")
|
|
.build()?;
|
|
|
|
let resp = client.get(&url).send().await?;
|
|
if !resp.status().is_success() {
|
|
anyhow::bail!("Tile fetch failed: {} → {}", url, resp.status());
|
|
}
|
|
let data = resp.bytes().await?.to_vec();
|
|
cache.put(&cache_key, &data)?;
|
|
|
|
Ok(RasterTile { coord: coord.clone(), data, bounds: coord::tile_bounds(coord) })
|
|
}
|
|
|
|
/// Fetch all tiles covering a bounding box.
|
|
pub async fn fetch_area(provider: &TileProvider, bbox: &GeoBBox, zoom: u8, cache: &TileCache) -> Result<Vec<RasterTile>> {
|
|
let coords = coord::tiles_for_bbox(bbox, zoom);
|
|
let mut tiles = Vec::with_capacity(coords.len());
|
|
for c in &coords {
|
|
match fetch_tile(provider, c, cache).await {
|
|
Ok(t) => tiles.push(t),
|
|
Err(e) => eprintln!(" Tile {}/{}/{} failed: {}", c.z, c.x, c.y, e),
|
|
}
|
|
}
|
|
Ok(tiles)
|
|
}
|