use std::f64::consts::PI;
use ggplot_rs::aes::Aes;
use ggplot_rs::data::Value;
use ggplot_rs::geom::map_tile::{tile_bounds, TileSource};
use ggplot_rs::geom::sf::GeomSf;
use ggplot_rs::spatial::SfProjection;
use ggplot_rs::GGPlot;
fn synthetic_tile(z: u32, x: u32, y: u32) -> Option<Vec<u8>> {
let (x0, yt, x1, yb) = tile_bounds(z, x as i64, y as i64);
let size = 256u32;
let deg_per_px = (x1 - x0).to_degrees() / size as f64;
let img = image::RgbImage::from_fn(size, size, |px, py| {
let mx = x0 + (px as f64 + 0.5) / size as f64 * (x1 - x0);
let my = yt + (py as f64 + 0.5) / size as f64 * (yb - yt);
let lon = mx.to_degrees();
let lat = (2.0 * my.exp().atan() - PI / 2.0).to_degrees();
let lat_per_px = deg_per_px * lat.to_radians().cos();
let near = |v: f64, step: f64, tol: f64| {
let r = v.rem_euclid(step);
r < tol || step - r < tol
};
if near(lon, 10.0, deg_per_px) || near(lat, 10.0, lat_per_px) {
image::Rgb([150, 170, 190])
} else if near(lon, 5.0, deg_per_px * 0.6) || near(lat, 5.0, lat_per_px * 0.6) {
image::Rgb([200, 212, 224])
} else {
image::Rgb([233, 240, 246])
}
});
let mut out = Vec::new();
img.write_to(
&mut std::io::Cursor::new(&mut out),
image::ImageOutputFormat::Png,
)
.ok()?;
Some(out)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cities = [
("Berlin", 13.40, 52.52, 3.7),
("Paris", 2.35, 48.86, 2.1),
("London", -0.13, 51.51, 8.9),
("Madrid", -3.70, 40.42, 3.3),
("Rome", 12.50, 41.90, 2.8),
("Vienna", 16.37, 48.21, 1.9),
("Warsaw", 21.01, 52.23, 1.8),
("Stockholm", 18.07, 59.33, 1.0),
("Lisbon", -9.14, 38.72, 0.5),
("Athens", 23.73, 37.98, 0.7),
];
let data = vec![
(
"geometry".to_string(),
cities
.iter()
.map(|(_, lon, lat, _)| Value::Str(format!("POINT ({lon} {lat})")))
.collect::<Vec<_>>(),
),
(
"city".to_string(),
cities.iter().map(|c| Value::Str(c.0.to_string())).collect(),
),
(
"population".to_string(),
cities.iter().map(|c| Value::Float(c.3)).collect(),
),
];
type Fetch = std::sync::Arc<dyn Fn(u32, u32, u32) -> Option<Vec<u8>> + Send + Sync>;
let (source, fetch): (TileSource, Fetch) = match std::env::var("TILE_DIR") {
Ok(dir) => (
TileSource::xyz("{z}/{x}/{y}.png").attribution("Tiles: local cache"),
std::sync::Arc::new(move |z, x, y| {
std::fs::read(format!("{dir}/{z}/{x}/{y}.png")).ok()
}),
),
Err(_) => (
TileSource::xyz("synthetic").attribution("Synthetic graticule tiles"),
std::sync::Arc::new(synthetic_tile),
),
};
let mut theme = ggplot_rs::theme::presets::theme_minimal();
theme.axis_text_x.visible = false;
theme.axis_text_y.visible = false;
let plot = || {
let fetch = fetch.clone();
GGPlot::new(data.clone())
.aes(Aes::new().label("city").fill("population"))
.geom_sf_with(GeomSf::default().project(SfProjection::Mercator))
.annotation_map_tile(source.clone(), move |z, x, y| fetch(z, x, y))
.coord_sf()
.scale_fill_viridis_c()
.title("Map tiles (annotation_map_tile)")
.theme(theme.clone())
};
std::fs::write(
"map_tiles.svg",
plot().render_svg_native_with_size(640, 480)?,
)?;
println!("wrote map_tiles.svg");
#[cfg(feature = "plotters")]
{
plot().save_with_size("map_tiles.png", 640, 480)?;
println!("wrote map_tiles.png");
}
Ok(())
}