earthwyrm/
tile.rs

1// tile.rs
2//
3// Copyright (c) 2019-2024  Minnesota Department of Transportation
4//
5use crate::config::{LayerGroupCfg, WyrmCfg};
6use crate::error::{Error, Result};
7use crate::geom::GeomTree;
8use crate::layer::LayerDef;
9use mvt::{Layer, MapGrid, Tile, TileId};
10use pointy::{BBox, Transform};
11use std::io::Write;
12use std::time::Instant;
13
14/// Tile configuration
15pub struct TileCfg {
16    /// Tile extent; width and height in pixels
17    tile_extent: u32,
18
19    /// Tile ID
20    tid: TileId,
21
22    /// Bounding box of tile (including edge extent)
23    bbox: BBox<f64>,
24
25    /// Transform from spatial to tile coordinates
26    transform: Transform<f64>,
27}
28
29/// Layer tree
30struct LayerTree {
31    /// Layer definition
32    layer_def: LayerDef,
33
34    /// R-Tree of geometry
35    tree: GeomTree,
36}
37
38/// Group of layers for making tiles
39struct LayerGroup {
40    /// Name of group
41    name: String,
42
43    /// Layer definitions / trees
44    layers: Vec<LayerTree>,
45}
46
47/// Wyrm tile fetcher.
48///
49/// To create:
50/// * Use `serde` to deserialize a [WyrmCfg]
51/// * `let wyrm = Wyrm::try_from(wyrm_cfg)?;`
52///
53/// [WyrmCfg]: struct.WyrmCfg.html
54pub struct Wyrm {
55    /// Map grid configuration
56    grid: MapGrid,
57
58    /// Tile extent; width and height in pixels
59    tile_extent: u32,
60
61    /// Tile layer groups
62    groups: Vec<LayerGroup>,
63}
64
65impl TileCfg {
66    /// Get the zoom level
67    pub fn zoom(&self) -> u32 {
68        self.tid.z()
69    }
70
71    /// Get the bounding box (including edge extent)
72    pub fn bbox(&self) -> BBox<f64> {
73        self.bbox
74    }
75
76    /// Get the tile transform
77    pub fn transform(&self) -> Transform<f64> {
78        self.transform
79    }
80}
81
82impl LayerGroup {
83    /// Create a new layer group
84    fn new(group: &LayerGroupCfg, wyrm: &WyrmCfg) -> Result<Self> {
85        let name = group.name.to_string();
86        let mut layers = vec![];
87        for layer_cfg in &group.layer {
88            let layer_def = LayerDef::try_from(layer_cfg)?;
89            layers.push(LayerTree::new(layer_def, wyrm)?);
90        }
91        log::info!("{} layers in {group}", layers.len());
92        Ok(LayerGroup { name, layers })
93    }
94
95    /// Get the group name
96    pub fn name(&self) -> &str {
97        &self.name
98    }
99
100    /// Fetch a tile
101    fn fetch_tile(&self, tile_cfg: &TileCfg) -> Result<Tile> {
102        let t = Instant::now();
103        let tile = self.query_tile(tile_cfg)?;
104        log::info!(
105            "{}/{}, fetched {} bytes in {:.2?}",
106            self.name(),
107            tile_cfg.tid,
108            tile.compute_size(),
109            t.elapsed()
110        );
111        Ok(tile)
112    }
113
114    /// Query one tile from trees
115    fn query_tile(&self, tile_cfg: &TileCfg) -> Result<Tile> {
116        let mut tile = Tile::new(tile_cfg.tile_extent);
117        for layer_tree in &self.layers {
118            let layer = layer_tree.query_tile(&tile, tile_cfg)?;
119            if layer.num_features() > 0 {
120                tile.add_layer(layer)?;
121            }
122        }
123        Ok(tile)
124    }
125
126    /// Write group layers to a tile
127    fn write_tile<W: Write>(
128        &self,
129        out: &mut W,
130        tile_cfg: TileCfg,
131    ) -> Result<()> {
132        let tile = self.fetch_tile(&tile_cfg)?;
133        if tile.num_layers() > 0 {
134            tile.write_to(out)?;
135            Ok(())
136        } else {
137            log::debug!("tile {} empty (no layers)", tile_cfg.tid);
138            Err(Error::TileEmpty())
139        }
140    }
141}
142
143impl TryFrom<&WyrmCfg> for Wyrm {
144    type Error = Error;
145
146    fn try_from(wyrm_cfg: &WyrmCfg) -> Result<Self> {
147        // Only Web Mercator supported for now
148        let grid = MapGrid::default();
149        let mut groups = vec![];
150        for group in &wyrm_cfg.layer_group {
151            groups.push(LayerGroup::new(group, wyrm_cfg)?);
152        }
153        Ok(Wyrm {
154            grid,
155            tile_extent: wyrm_cfg.tile_extent,
156            groups,
157        })
158    }
159}
160
161impl Wyrm {
162    /// Query features in a bounding box
163    pub fn query_features(&self, bbox: BBox<f64>) -> Result<()> {
164        for group in &self.groups {
165            log::debug!("query_features group: {:?}", group.name);
166            for layer in &group.layers {
167                layer.query_features(bbox)?;
168            }
169        }
170        Ok(())
171    }
172
173    /// Fetch one tile.
174    ///
175    /// * `out` Writer to write MVT data.
176    /// * `group_name` Name of layer group.
177    /// * `tid` Tile ID.
178    pub fn fetch_tile<W: Write>(
179        &self,
180        out: &mut W,
181        group_name: &str,
182        tid: TileId,
183    ) -> Result<()> {
184        for group in &self.groups {
185            if group_name == group.name() {
186                let tile_cfg = self.tile_config(tid);
187                return group.write_tile(out, tile_cfg);
188            }
189        }
190        log::debug!("unknown group name: {}", group_name);
191        Err(Error::UnknownGroupName())
192    }
193
194    /// Create tile config for a tile ID
195    fn tile_config(&self, tid: TileId) -> TileCfg {
196        let tile_extent = self.tile_extent;
197        let mut bbox = self.grid.tile_bbox(tid);
198        // increase bounding box by edge extent
199        let edge = zoom_edge(tid);
200        let edge_x = edge * (bbox.x_max() - bbox.x_min());
201        let edge_y = edge * (bbox.y_max() - bbox.y_min());
202        bbox.extend([
203            (bbox.x_min() - edge_x, bbox.y_min() - edge_y),
204            (bbox.x_max() + edge_x, bbox.y_max() + edge_y),
205        ]);
206        let ts = f64::from(tile_extent);
207        let transform = self.grid.tile_transform(tid).scale(ts, ts);
208        TileCfg {
209            tile_extent,
210            tid,
211            bbox,
212            transform,
213        }
214    }
215}
216
217/// Calculate edge ratio based on tile zoom
218///
219/// Edge must be larger for higher zoom levels to prevent corrupt polygons.
220fn zoom_edge(tid: TileId) -> f64 {
221    match tid.z() {
222        0..=12 => 1.0 / 32.0,
223        13 => 1.0 / 16.0,
224        14 => 1.0 / 8.0,
225        15 => 1.0 / 4.0,
226        16 => 1.0 / 2.0,
227        _ => 1.0,
228    }
229}
230
231impl LayerTree {
232    /// Create a new layer tree
233    fn new(layer_def: LayerDef, wyrm: &WyrmCfg) -> Result<Self> {
234        let loam = wyrm.loam_path(layer_def.name());
235        let tree = GeomTree::new(layer_def.geom_tp(), loam)?;
236        Ok(LayerTree { layer_def, tree })
237    }
238
239    /// Query layer features in a bounding box
240    fn query_features(&self, bbox: BBox<f64>) -> Result<()> {
241        self.tree.query_features(&self.layer_def, bbox)
242    }
243
244    /// Query tile features
245    fn query_tile(&self, tile: &Tile, tile_cfg: &TileCfg) -> Result<Layer> {
246        let layer = tile.create_layer(self.layer_def.name());
247        if self.layer_def.check_zoom(tile_cfg.zoom()) {
248            self.tree.query_tile(&self.layer_def, layer, tile_cfg)
249        } else {
250            Ok(layer)
251        }
252    }
253}