//! The online street map under the Places map: vector tiles from OpenFreeMap, //! fetched and drawn on a worker thread, and handed to the map as finished //! images. //! //! It is off unless turned on in Preferences, since every tile fetched tells //! OpenFreeMap which part of the world is being looked at. OpenFreeMap needs //! no account or key, and its tiles are OpenStreetMap data in the //! OpenMapTiles schema; both have to be credited wherever the map shows. //! //! Tiles are kept on disk under the cache directory, per build of the tile //! set. A build never changes, so a cached tile is good until OpenFreeMap //! publishes the next one, and then the old build's directory goes. Town and //! country names are not baked into the images: they come back as [`Label`]s //! for the map to set on top, where it can keep them upright, sharp and apart //! from each other. use std::collections::HashSet; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::time::Duration; use gtk4::cairo; use super::mvt::{self, Kind, Layer}; /// Where OpenFreeMap describes its current tile set. const TILEJSON_URL: &str = "https://tiles.openfreemap.org/planet"; /// The deepest zoom OpenFreeMap has tiles for. Closer in, a tile is drawn /// from part of its ancestor at this zoom. pub(crate) const DATA_MAX_ZOOM: u8 = 14; /// A tile's edge in logical px at the zoom it was made for. pub(crate) const TILE_EDGE: f64 = 256.0; /// How many tiles are fetched at once. const PARALLEL_FETCHES: usize = 6; /// How long one fetch may take. const FETCH_TIMEOUT: Duration = Duration::from_secs(20); /// Past this size the tile cache is emptied rather than left to grow. const DISK_BUDGET: u64 = 256 * 1024 * 1024; /// One tile of the Web Mercator grid: at zoom `z` the world is `2^z` tiles /// wide and tall, counted from the north-west. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub(crate) struct TileKey { pub(crate) z: u8, pub(crate) x: u32, pub(crate) y: u32, } impl TileKey { pub(crate) fn parent(self) -> Option { (self.z > 0).then(|| Self { z: self.z - 1, x: self.x / 2, y: self.y / 2, }) } /// The tile whose data this one is drawn from. fn data(self) -> Self { let mut key = self; while key.z > DATA_MAX_ZOOM { key = key.parent().unwrap_or(key); } key } } /// The colours of the map, for a light or a dark window. pub(crate) struct Palette { pub(crate) land: u32, pub(crate) water: u32, wood: u32, grass: u32, ice: u32, sand: u32, residential: u32, industrial: u32, building: u32, motorway: u32, primary: u32, road: u32, casing: u32, rail: u32, path: u32, pub(crate) border: u32, state_border: u32, pub(crate) coast: u32, pub(crate) text: u32, pub(crate) halo: u32, pub(crate) water_text: u32, } pub(crate) const LIGHT: Palette = Palette { land: 0xf4f2ee, water: 0xb3cde3, wood: 0xd3e5c5, grass: 0xdcebcf, ice: 0xf2f6f9, sand: 0xefe7d2, residential: 0xebe7e2, industrial: 0xebe4e8, building: 0xdcd5ce, motorway: 0xf2c98f, primary: 0xfbe4ad, road: 0xffffff, casing: 0xd2cbc1, rail: 0xb5afa8, path: 0xbfae9c, border: 0x9d94b5, state_border: 0xc2bbd2, coast: 0x9fbbd4, text: 0x34313d, halo: 0xffffff, water_text: 0x4f7398, }; pub(crate) const DARK: Palette = Palette { land: 0x24222d, water: 0x141c2b, wood: 0x212c26, grass: 0x232d27, ice: 0x2c313a, sand: 0x2d2b27, residential: 0x292733, industrial: 0x2b2833, building: 0x322f3c, motorway: 0x7a6647, primary: 0x625a4b, road: 0x46434f, casing: 0x1a1822, rail: 0x4e4a5a, path: 0x3f3b49, border: 0x857ca0, state_border: 0x5c556f, coast: 0x2c3b52, text: 0xe7e4ef, halo: 0x16141c, water_text: 0x86a6cb, }; pub(crate) fn palette(dark: bool) -> &'static Palette { if dark { &DARK } else { &LIGHT } } pub(crate) fn set_rgb(cr: &cairo::Context, color: u32, alpha: f64) { cr.set_source_rgba( f64::from((color >> 16) & 0xff) / 255.0, f64::from((color >> 8) & 0xff) / 255.0, f64::from(color & 0xff) / 255.0, alpha, ); } /// What kind of place a name stands for, which sets how it is written and /// which names win when they would overlap. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub(crate) enum LabelClass { Country, State, City, Town, Village, Suburb, Hamlet, Water, } impl LabelClass { fn from_place(class: &str) -> Option { Some(match class { "country" => Self::Country, "state" | "province" => Self::State, "city" => Self::City, "town" => Self::Town, "village" => Self::Village, "suburb" | "quarter" => Self::Suburb, "hamlet" | "neighbourhood" | "isolated_dwelling" => Self::Hamlet, _ => return None, }) } /// The zoom from which such names are shown at all. pub(crate) fn min_zoom(self) -> u8 { match self { Self::Country | Self::Water | Self::City => 0, Self::State => 4, Self::Town => 8, Self::Village => 11, Self::Suburb => 12, Self::Hamlet => 14, } } /// The zoom past which such names are left out again: a country's name /// is no help once its towns are. pub(crate) fn max_zoom(self) -> u8 { match self { Self::Country => 7, Self::State => 9, _ => u8::MAX, } } } /// A name to set on the map, at its place in world space (0..1 both ways). #[derive(Clone, Debug)] pub(crate) struct Label { pub(crate) x: f64, pub(crate) y: f64, pub(crate) text: String, pub(crate) class: LabelClass, /// Lower is more important, within a class. pub(crate) rank: f64, } /// A finished tile: premultiplied ARGB pixels, as cairo keeps them. pub(crate) struct Raster { pub(crate) key: TileKey, pub(crate) dark: bool, pub(crate) edge: i32, pub(crate) pixels: Vec, pub(crate) labels: Vec