gistools/writers/to_tiles/mod.rs
1/// Types, Structs, and Enums for the to_tiles converter
2pub mod types;
3/// Worker
4pub mod worker;
5
6use super::OnFeature;
7#[cfg(feature = "std")]
8use crate::writers::FileTileWriter;
9use crate::{
10 data_structures::HasLayer,
11 geometry::{wm, xyz_to_bbox},
12 parsers::FeatureReader,
13 writers::{LocalTileWriter, TileWriter},
14};
15use alloc::{string::String, vec::Vec};
16use core::mem::take;
17use s2_tilejson::{LonLatBounds, MetadataBuilder, Scheme};
18use s2json::{MValueCompatible, Projection};
19pub use types::*;
20use worker::{BuiltTile, TileWorker};
21
22/// Use a Local Tile Builder via memory
23///
24/// See [`TileBuilder`] for full documentation
25pub type LocalTileBuilder = TileBuilder<LocalTileWriter>;
26/// Use the Filesystem to store all file data
27///
28/// See [`TileBuilder`] for full documentation
29#[cfg(feature = "std")]
30pub type FileTileBuilder = TileBuilder<FileTileWriter>;
31
32/// # The Tile Builder
33///
34/// ## Description
35/// The TileBuilder creates tiles for the user given any source reader that implements [`FeatureReader`]
36///
37/// Create vector tiles, raster tiles, or gridded tiles.
38///
39/// Store as Mapbox Vector Tiles or Open Vector Tiles. Utilizes the [`TileWriter`] trait to write the data to.
40///
41/// Supports storing as a folder structure or a PMTiles (with gzip compression if enabled)
42///
43/// ## Usage
44///
45/// The methods you have access to:
46/// - [`TileBuilder::new`]: Create a new TileBuilder
47/// - [`TileBuilder::tile_writer`]: Get the tile writer
48/// - [`TileBuilder::add_vector_source`]: Add a vector source to tile-ize
49/// - [`TileBuilder::add_grid_source`]: Add data that will be gridded like raster data but float precision points
50/// - [`TileBuilder::build_tiles`]: After adding all the source data, build all tiles into the tile writer
51///
52/// The Tile Writers this library supports:
53/// - [`LocalTileWriter`]
54/// - [`FileTileWriter`]
55/// - [`crate::writers::PMTilesWriter`]
56/// - [`crate::writers::S2TilesWriter`]
57///
58/// ### Writing WM projection tiles as Mapbox Vector Tiles to a PMTiles file
59///
60/// ```rust
61/// use gistools::{
62/// data_structures::TileStoreOptions,
63/// parsers::{BufferWriter, FileReader},
64/// readers::JSONReader,
65/// util::CompressionFormat,
66/// writers::{
67/// BuildGuide, PMTilesWriter, TileBuilder, LayerGuide,
68/// VectorLayerGuide, BaseLayer
69/// }
70/// };
71/// use serde::{Deserialize, Serialize};
72/// use s2_tilejson::{Metadata, MetadataBuilder, DrawType};
73/// use open_vector_tile::Extent;
74/// use s2json::{MValue, MValueCompatible, Properties, Projection};
75/// use std::{path::PathBuf, collections::BTreeMap};
76///
77/// #[derive(Debug, Default, Clone, MValueCompatible, PartialEq, Serialize, Deserialize)]
78/// #[serde(default)]
79/// struct PointProps {
80/// id: i64,
81/// }
82///
83/// #[derive(Debug, Default, Clone, MValueCompatible, PartialEq, Serialize, Deserialize)]
84/// #[serde(default)]
85/// struct LineProps {
86/// linename: String,
87/// }
88///
89/// #[derive(Debug, Default, Clone, MValueCompatible, PartialEq, Serialize, Deserialize)]
90/// #[serde(default)]
91/// struct PolyProps {
92/// poly3d: bool,
93/// }
94///
95/// // using a buffer writer for example but ideally you are using a FileWriter
96/// let tmp_buffer_writer = BufferWriter::new(vec![]);
97/// let pm_writer = PMTilesWriter::new(tmp_buffer_writer, CompressionFormat::None);
98/// let build_guide = BuildGuide {
99/// projection: Projection::WG,
100/// build_indices: true,
101/// attributions: BTreeMap::from([("Satellite Data".into(), "https://example.com".into())]),
102/// layer_guides: vec![
103/// // add points
104/// LayerGuide::Vector(VectorLayerGuide {
105/// extent: Extent::Extent4096,
106/// draw_types: vec![DrawType::Points, DrawType::Points3D],
107/// shape: Some((&MValue::from(PointProps::default())).into()),
108/// base: BaseLayer {
109/// description: Some("Points Vector Layer".into()),
110/// source_name: "all_features".into(),
111/// layer_name: "points".into(),
112/// },
113/// vector_guide: TileStoreOptions { maxzoom: Some(4), ..Default::default() },
114/// ..Default::default()
115/// }),
116/// // add lines
117/// LayerGuide::Vector(VectorLayerGuide {
118/// extent: Extent::Extent4096,
119/// draw_types: vec![DrawType::Lines, DrawType::Lines3D],
120/// shape: Some((&MValue::from(LineProps::default())).into()),
121/// base: BaseLayer {
122/// description: Some("Lines Vector Layer".into()),
123/// source_name: "all_features".into(),
124/// layer_name: "lines".into(),
125/// },
126/// vector_guide: TileStoreOptions { maxzoom: Some(4), ..Default::default() },
127/// ..Default::default()
128/// }),
129/// // add polys
130/// LayerGuide::Vector(VectorLayerGuide {
131/// extent: Extent::Extent4096,
132/// draw_types: vec![DrawType::Polygons, DrawType::Polygons3D],
133/// shape: Some((&MValue::from(PolyProps::default())).into()),
134/// base: BaseLayer {
135/// description: Some("Polygons Vector Layer".into()),
136/// source_name: "all_features".into(),
137/// layer_name: "polys".into(),
138/// },
139/// vector_guide: TileStoreOptions { maxzoom: Some(4), ..Default::default() },
140/// ..Default::default()
141/// }),
142/// ],
143/// ..Default::default()
144/// };
145/// let mut tile_builder = TileBuilder::new(pm_writer, build_guide);
146///
147/// // add json features
148/// let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
149/// path = path.join("tests/writers/fixtures/all-features.json");
150/// let reader: JSONReader<FileReader, (), Properties, Properties> =
151/// JSONReader::new(FileReader::from(path));
152/// tile_builder.add_vector_source("all_features".into(), reader, None, None);
153///
154/// // build
155/// tile_builder.build_tiles();
156/// ```
157///
158/// ## Links
159/// - <https://github.com/Open-S2/s2-pmtiles>
160/// - <https://github.com/protomaps/PMTiles>
161/// - <https://github.com/mapbox/vector-tile-spec>
162#[derive(Debug)]
163pub struct TileBuilder<W: TileWriter = LocalTileWriter> {
164 /// The data created will be stored in either a folder structure or a pmtiles file
165 /// Folder structure is either '{face}/{zoom}/{x}/{y}.pbf' or '{zoom}/{x}/{y}.pbf'.
166 /// PMTiles store all data in a single data file.
167 tile_writer: W,
168 /// Explains how to build data
169 build_guide: BuildGuide,
170 /// TileWorker collects data then builds tiles
171 worker: TileWorker,
172 /// The metadata that will be stored with the tile data
173 meta_builder: MetadataBuilder,
174}
175impl<W: TileWriter> TileBuilder<W> {
176 /// Create a new Tile builder
177 pub fn new(tile_writer: W, build_guide: BuildGuide) -> Self {
178 let meta_builder = setup_builder(&build_guide);
179 let worker = TileWorker::new(0, build_guide.clone());
180 Self { tile_writer, build_guide, worker, meta_builder }
181 }
182
183 /// Get the tile writer
184 pub fn tile_writer(&self) -> &W {
185 &self.tile_writer
186 }
187
188 /// Add a vector source to tile-ize
189 pub fn add_vector_source<
190 M: Clone + HasLayer,
191 P: MValueCompatible,
192 D: MValueCompatible,
193 T: FeatureReader<M, P, D>,
194 >(
195 &mut self,
196 source_name: String,
197 reader: T,
198 on_source_feature: Option<OnFeature<M, P, D>>,
199 on_layer_feature: Option<&Vec<LayerHandler<M, P, D>>>,
200 ) {
201 for mut feature in reader.iter() {
202 if let Some(on_feature) = on_source_feature
203 && let Some(new_feature) = (on_feature)(take(&mut feature))
204 {
205 feature = new_feature;
206 }
207 self.worker.store_feature(feature, source_name.clone(), on_layer_feature);
208 }
209 }
210
211 /// Add vector points with RGBA attributes to build raster tiles
212 #[cfg_attr(feature = "nightly", coverage(off))] // not implemented don't punish
213 pub fn add_raster_source<
214 M: Clone,
215 P: MValueCompatible,
216 D: MValueCompatible,
217 T: FeatureReader<M, P, D>,
218 >(
219 _source_name: String,
220 _reader: T,
221 _on_feature: Option<OnFeature<M, P, D>>,
222 ) {
223 unimplemented!()
224 }
225
226 /// Add data that will be gridded like raster data but float precision points
227 #[cfg_attr(feature = "nightly", coverage(off))] // not implemented don't punish
228 pub fn add_grid_source<
229 M: Clone,
230 P: MValueCompatible,
231 D: MValueCompatible,
232 T: FeatureReader<M, P, D>,
233 >(
234 _source_name: String,
235 _reader: T,
236 _on_feature: Option<OnFeature<M, P, D>>,
237 ) {
238 unimplemented!()
239 }
240
241 /// After adding all the source data, build all tiles into the tile writer
242 pub fn build_tiles(&mut self) {
243 let BuildGuide { projection, .. } = &self.build_guide;
244 // ensure all data is sorted
245 self.worker.sort();
246 // collect all tiles
247 for BuiltTile { face, zoom, x, y, data } in self.worker.build_tiles() {
248 if *projection == Projection::S2 {
249 self.tile_writer.write_tile_s2(face, zoom, x, y, data);
250 // TODO: Get correct ll-bounds
251 self.meta_builder.add_tile_s2(face, zoom, x, y, &LonLatBounds::default());
252 } else {
253 self.tile_writer.write_tile_wm(zoom, x, y, data);
254 let (w, s, e, n) =
255 xyz_to_bbox(x, y, zoom, Some(false), Some(wm::Source::WGS84), None);
256 self.meta_builder.add_tile_wm(zoom, x, y, &LonLatBounds::new(w, s, e, n));
257 }
258 }
259 // finally commit the metadata
260 self.tile_writer
261 .commit(self.meta_builder.commit(), Some((&self.build_guide.encoding).into()));
262 }
263}
264
265/// Setup a metadata builder
266fn setup_builder(build_guide: &BuildGuide) -> MetadataBuilder {
267 let mut meta_builder = MetadataBuilder::default();
268 let BuildGuide { name, description, version, projection, attributions, .. } = build_guide;
269
270 meta_builder.set_name(name.into());
271 meta_builder.set_extension(build_guide.extension.clone());
272 meta_builder.set_description(description.into());
273 meta_builder.set_version(version.into());
274 // NOTE: For now we only support xyz and fzxy
275 let scheme = if *projection == Projection::S2 { Scheme::Fzxy } else { Scheme::Xyz };
276 meta_builder.set_scheme(scheme);
277 meta_builder.set_type((&build_guide.format).into());
278
279 meta_builder.set_encoding(build_guide.encoding.clone());
280 // Add attribution
281 for (display_name, href) in attributions.iter() {
282 meta_builder.add_attribution(display_name, href);
283 }
284 // add layer guides
285 for layer in build_guide.layer_guides.iter() {
286 meta_builder.add_layer(layer.layer_name(), &layer.into());
287 }
288
289 meta_builder
290}