use reqwest::blocking::get;
use rusqlite::{params, Connection};
use sha2::{Digest, Sha256};
use std::fs;
use std::io::Read;
use std::path::PathBuf;
pub fn dem5a_tile_url(lat: f64, lon: f64, zoom: u8) -> String {
let (x, y, z) = crate::util::tile_xyz_from_latlon(lat, lon, zoom);
format!("https://cyberjapandata.gsi.go.jp/xyz/dem5a_png/{}/{}/{}.png", z, x, y)
}
pub fn dem1a_tile_url(lat: f64, lon: f64, zoom: u8) -> String {
let (x, y, z) = crate::util::tile_xyz_from_latlon(lat, lon, zoom);
format!("https://cyberjapandata.gsi.go.jp/xyz/dem1a_png/{}/{}/{}.png", z, x, y)
}
pub fn fetch_and_cache_tile(url: &str, cache_db: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let cache_dir = "tile_cache";
fs::create_dir_all(cache_dir)?;
let conn = Connection::open(cache_db)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS tile_cache (
url TEXT PRIMARY KEY,
path TEXT,
updated_at INTEGER
)",
[],
)?;
if let Ok(mut stmt) = conn.prepare("SELECT path FROM tile_cache WHERE url = ?1") {
let mut rows = stmt.query(params![url])?;
if let Some(row) = rows.next()? {
let path: String = row.get(0)?;
let mut file = fs::File::open(&path)?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
return Ok(data);
}
}
let resp = get(url)?;
if !resp.status().is_success() {
return Err(format!("HTTP error: {}", resp.status()).into());
}
let bytes = resp.bytes()?;
let data = bytes.to_vec();
if data.len() < 8 || &data[0..8] != b"\x89PNG\r\n\x1a\n" {
return Err("取得データがPNGではありません".into());
}
let mut hasher = Sha256::new();
hasher.update(url.as_bytes());
let hash = format!("{:x}", hasher.finalize());
let mut path = PathBuf::from(cache_dir);
path.push(format!("{}.png", hash));
fs::write(&path, &data)?;
conn.execute(
"INSERT OR REPLACE INTO tile_cache (url, path, updated_at) VALUES (?1, ?2, strftime('%s','now'))",
params![url, path.to_string_lossy()],
)?;
Ok(data)
}
pub fn select_dem_tile_url(lat: f64, lon: f64, zoom: u8) -> (String, String) {
let dem1a_url = dem1a_tile_url(lat, lon, zoom);
let dem5a_url = dem5a_tile_url(lat, lon, zoom);
(dem1a_url, dem5a_url)
}
pub fn fetch_dem_tile_auto(lat: f64, lon: f64, zoom: u8, cache_db: &str) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> {
let (dem1a_url, dem5a_url) = select_dem_tile_url(lat, lon, zoom);
match fetch_and_cache_tile(&dem1a_url, cache_db) {
Ok(data) => {
if !data.is_empty() && !is_invalid_dem_png(&data) {
return Ok(Some(data));
}
}
Err(_) => {}
}
match fetch_and_cache_tile(&dem5a_url, cache_db) {
Ok(data) => {
if !data.is_empty() && !is_invalid_dem_png(&data) {
return Ok(Some(data));
}
}
Err(_) => {}
}
Ok(None)
}
fn is_invalid_dem_png(data: &[u8]) -> bool {
if let Ok(img) = image::load_from_memory(data) {
let rgb = img.to_rgb8();
let px = rgb.get_pixel(0, 0);
px[0] == 128 && px[1] == 0 && px[2] == 0
} else {
true
}
}