gistools/writers/to_tiles/
worker.rs

1use super::{
2    BuildGuide, FormatOutput, LayerGuide, LayerHandler, MVectorFeature, ToTileMetadata,
3    VectorLayerGuide,
4};
5use crate::{
6    data_store::{MultiMap, MultiMapStore},
7    data_structures::{HasLayer, PointCluster, PointGrid, TileStore, TileStoreOptions},
8    geometry::S2CellId,
9    parsers::RGBA,
10    util::{CompressionFormat, compress_data},
11};
12use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
13use core::mem::take;
14use earclip::{earclip, tesselate};
15use libm::{floor, fmax, fmin};
16use open_vector_tile::{
17    base::{BaseVectorLayer, BaseVectorTile, s2json_to_base},
18    mapbox, write_tile,
19};
20use s2json::{
21    Face, JSONCollection, MValue, MValueCompatible, Projection, VectorFeature, VectorGeometry,
22    VectorPolygon,
23};
24
25/// A built tile that is ready to be written to the filesystem
26#[derive(Debug)]
27pub struct BuiltTile {
28    /// Face of the tile
29    pub face: Face,
30    /// zoom of the tile
31    pub zoom: u8,
32    /// x tile coordinate
33    pub x: u32,
34    /// y tile coordinate
35    pub y: u32,
36    /// compressed data
37    pub data: Vec<u8>,
38}
39
40/// Convert a vector feature to a collection of tiles and store each tile feature
41#[derive(Debug)]
42pub struct TileWorker {
43    /// Id of the worker (useful for threads)
44    pub id: usize,
45    /// total minzoom
46    pub minzoom: u8,
47    /// total maxzoom
48    pub maxzoom: u8,
49    /// Description of how to build data
50    build_guide: BuildGuide,
51    /// Store Vector Features whose key are Tile IDs.
52    pub vector_store: MultiMap<S2CellId, MVectorFeature>,
53    /// Unique store for each layer that describes itself as a cluster source
54    pub cluster_stores: BTreeMap<String, PointCluster<MValue>>, /* { [layerName: string]: PointCluster } = {}; */
55    /// Unique store for each layer that describes itself as a raster source
56    pub raster_stores: BTreeMap<String, PointGrid<RGBA>>, /* { [layerName: string]: PointGrid<RGBA> } = {}; */
57    /// Unique store for each layer that describes itself as a grid source
58    pub grid_stores: BTreeMap<String, PointGrid<f64>>, // { [layerName: string]: PointGrid } = {};
59}
60impl TileWorker {
61    /// Create a new TileWorker
62    pub fn new(id: usize, build_guide: BuildGuide) -> Self {
63        let (minzoom, maxzoom) = get_zooms(&build_guide.layer_guides);
64        Self {
65            id,
66            minzoom,
67            maxzoom,
68            build_guide,
69            vector_store: MultiMap::<S2CellId, MVectorFeature>::new(None),
70            cluster_stores: BTreeMap::new(),
71            raster_stores: BTreeMap::new(),
72            grid_stores: BTreeMap::new(),
73        }
74    }
75    /// Iterate through all the stores and sort/cluster as needed
76    pub fn sort(&mut self) {
77        for cluster in self.cluster_stores.values_mut() {
78            cluster.build_clusters(None);
79        }
80        for raster in self.raster_stores.values_mut() {
81            raster.build_clusters();
82        }
83        for grid in self.grid_stores.values_mut() {
84            grid.build_clusters();
85        }
86    }
87
88    /// Store a feature to the appropriate store
89    ///
90    /// ## Parameters
91    /// - `feature`: The feature to store
92    /// - `source_name`: The name of the source to store the feature in
93    /// - `on_layer_feature`: An optional handler to update or filter the feature
94    pub fn store_feature<M: Clone + HasLayer, P: MValueCompatible, D: MValueCompatible>(
95        &mut self,
96        mut feature: VectorFeature<M, P, D>,
97        source_name: String,
98        on_layer_feature: Option<&Vec<LayerHandler<M, P, D>>>,
99    ) {
100        // First find all build guide layers that use this source
101        let layer_guides = self
102            .build_guide
103            .layer_guides
104            .iter()
105            .filter(|lg| lg.has_source(&source_name))
106            .cloned()
107            .collect::<Vec<_>>();
108        // iterate through each layer and store the feature
109        for layer_guide in layer_guides {
110            // find the layer handler that matches layer_guide's layer_name and mutate if needed
111            if let Some(on_layer_feature) = &on_layer_feature
112                && let Some(layer_handler) =
113                    on_layer_feature.iter().find(|lh| lh.layer_name == layer_guide.layer_name())
114                && let Some(new_feature) = (layer_handler.on_feature)(take(&mut feature))
115            {
116                feature = new_feature;
117            }
118            match layer_guide {
119                LayerGuide::Vector(vlg) => {
120                    self.store_vector_feature(
121                        feature.to_m_vector_feature(|_| {
122                            Some(ToTileMetadata::new(vlg.base.layer_name.clone()))
123                        }),
124                        vlg,
125                    );
126                }
127                // LayerGuide::Cluster(clg) => {
128                //     self.store_cluster_feature(
129                //         feature.to_m_vector_feature(|_| {
130                //             Some(ToTileMetadata::new(clg.base.layer_name.clone()))
131                //         }),
132                //         clg,
133                //     );
134                // }
135                _ => {
136                    unimplemented!()
137                }
138            }
139        }
140    }
141
142    /// Store a vector feature across all appropriate zooms
143    ///
144    /// ## Parameters
145    /// - `feature`: The vector feature to store
146    /// - `layer`: The layer guide on how to shape for storage
147    pub fn store_vector_feature(&mut self, feature: MVectorFeature, layer: VectorLayerGuide) {
148        let BuildGuide { projection, .. } = &self.build_guide;
149        // skip features who are not using the layer guide's
150        if !layer.draw_types.contains(&(&feature).into()) {
151            return;
152        }
153        let minzoom = layer.vector_guide.minzoom.unwrap_or(0);
154        let maxzoom = layer.vector_guide.maxzoom.unwrap_or(self.maxzoom);
155        // Setup a tile_cache and dive down. Store the 4 children if data is found while storing data as we go
156        let mut tile_store = TileStore::new(
157            JSONCollection::VectorFeature(feature),
158            TileStoreOptions { projection: Some(*projection), ..layer.vector_guide },
159        );
160        let mut tile_cache = vec![S2CellId::from_face(0)];
161        if *projection == Projection::S2 {
162            tile_cache.extend([
163                S2CellId::from_face(1),
164                S2CellId::from_face(2),
165                S2CellId::from_face(3),
166                S2CellId::from_face(4),
167                S2CellId::from_face(5),
168            ]);
169        }
170        while let Some(id) = tile_cache.pop() {
171            let zoom = id.level();
172            if zoom > maxzoom {
173                continue;
174            }
175            let tile = tile_store.get_tile(id);
176            if zoom < minzoom {
177                // if we haven't reached the data yet, we store children
178                tile_cache.extend(id.children(None));
179            } else if let Some(tile) = tile
180                && !tile.is_empty()
181            {
182                // store feature with the associated layername
183                for layer in tile.layers.values() {
184                    for feature in &layer.features {
185                        self.vector_store.set(id, feature.clone());
186                    }
187                }
188                // store 4 children tiles to ask for
189                tile_cache.extend(id.children(None));
190            }
191        }
192    }
193
194    /// Get vector/cluster features for a tile
195    ///
196    /// ## Parameters
197    /// - `id`: tile id
198    ///
199    /// ## Returns
200    /// The shaped vector tile for storage if applicable
201    fn get_vector_tile(&mut self, id: S2CellId) -> Option<BaseVectorTile> {
202        let BuildGuide { layer_guides, format, build_indices, .. } = &self.build_guide;
203        if *format == FormatOutput::Raster {
204            return None;
205        }
206        let mut res = BaseVectorTile::default();
207        let zoom = id.level();
208        // store vector features
209        if let Some(vector_features) = self.vector_store.get(id).cloned() {
210            for mut feature in vector_features {
211                let layer_name = feature.metadata.as_ref().unwrap().layer_name.clone();
212                let layer = layer_guides
213                    .iter()
214                    .find(|lg| lg.layer_name() == layer_name)
215                    .unwrap()
216                    .to_vector()
217                    .unwrap();
218                if *build_indices {
219                    // pre-earclip, since we are still working with floats, the extent of 1 is fine! :D
220                    earclip_polygons(&mut feature, zoom);
221                }
222                let layer = res.layers.entry(layer_name.clone()).or_insert(BaseVectorLayer::new(
223                    layer_name,
224                    layer.extent,
225                    vec![],
226                    layer.shape,
227                    layer.m_shape,
228                ));
229                layer.add_feature(s2json_to_base(&feature, layer.extent));
230            }
231        }
232        // // store all cluster features
233        // for (const [layerName, cluster] of Object.entries(this.cluster_stores)) {
234        //   const layerClusterFeatures = await cluster.getTile(id);
235        //   if (layerClusterFeatures === undefined) continue;
236        //   for (const layer of Object.values(layerClusterFeatures.layers)) {
237        //     for (const feature of layer.features) tile.addFeature(feature, layerName);
238        //   }
239        // }
240
241        if res.layers.is_empty() { None } else { Some(res) }
242    }
243
244    /// Iterate through all the stores and sort/cluster as needed and build tiles
245    pub fn build_tiles(&mut self) -> TileWorkerTileBuilder<'_> {
246        let mut tile_stack = vec![S2CellId::from_face(0)];
247        if self.build_guide.projection == Projection::S2 {
248            tile_stack.extend([
249                S2CellId::from_face(1),
250                S2CellId::from_face(2),
251                S2CellId::from_face(3),
252                S2CellId::from_face(4),
253                S2CellId::from_face(5),
254            ]);
255        }
256        TileWorkerTileBuilder { worker: self, tile_stack }
257    }
258}
259/// Iterate through the stores and build tiles, compressing as we go if required
260#[derive(Debug)]
261pub struct TileWorkerTileBuilder<'a> {
262    worker: &'a mut TileWorker,
263    tile_stack: Vec<S2CellId>,
264}
265impl Iterator for TileWorkerTileBuilder<'_> {
266    type Item = BuiltTile;
267    fn next(&mut self) -> Option<Self::Item> {
268        let build_guide = &self.worker.build_guide;
269        let format = build_guide.format.clone();
270        let encoding: CompressionFormat = (&build_guide.encoding).into();
271        while let Some(id) = self.tile_stack.pop() {
272            // if the current id is less than our target zoom, we add children to the stack and continue
273            let (face, zoom, x, y) = id.to_face_ij();
274            if zoom > self.worker.maxzoom {
275                continue;
276            }
277            if zoom < self.worker.minzoom {
278                self.tile_stack.extend(id.children(None));
279                continue;
280            }
281            let mut vector_tile = self.worker.get_vector_tile(id);
282            // otherwise, we build the tile
283            if format != FormatOutput::Raster {
284                let mut data = if format == FormatOutput::OpenS2 {
285                    write_tile(vector_tile.as_mut(), None, None)
286                } else if let Some(mut vector_tile) = vector_tile {
287                    mapbox::vector_tile::write_tile(
288                        &mut vector_tile,
289                        format == FormatOutput::Mapbox,
290                    )
291                } else {
292                    vec![]
293                };
294                if data.is_empty() {
295                    continue;
296                } else {
297                    data = compress_data(data, encoding).unwrap();
298                    self.tile_stack.extend(id.children(None));
299                    return Some(BuiltTile { face: face.into(), zoom, x, y, data });
300                }
301            }
302        }
303        None
304    }
305}
306
307//   /**
308//    * Iterate through the stores and build tiles, compressing as we go if required
309//    * @yields - a built tile
310//    */
311//   async *buildTiles(): AsyncGenerator<BuiltTile> {
312//     const { format, layer_guides, projection, encoding } = this;
313//     const minzoom = getMinzoom(layer_guides);
314
315//     // three directions we can build data
316//     const tile_cache = [idFromFace(0)];
317//     if (projection === 'S2')
318//       tile_cache.push(idFromFace(1), idFromFace(2), idFromFace(3), idFromFace(4), idFromFace(5));
319//     while (tile_cache.length > 0) {
320//       const id = tile_cache.pop()!;
321//       const tile = new Tile(id);
322//       const { face, zoom, i: x, j: y } = tile;
323
324//       const vectorTile = await this.#get_vector_tile(id, tile);
325//       const rasterData = await this.#getRasterTile(id);
326//       const gridData = await this.#getGridTile(id);
327//       if (format === 'raster') {
328//         // RASTER CASE
329//         if (rasterData !== undefined) {
330//           const data = new Uint8Array(rasterData[0].image);
331//           yield { face, zoom, x, y, data };
332//           // store 4 children tiles to ask for children features
333//           tile_cache.push(...idChildrenIJ(face, zoom, x, y));
334//         } else {
335//           // if we haven't reached the data yet, we store children
336//           if (minzoom > tile.zoom) tile_cache.push(...idChildrenIJ(face, zoom, x, y));
337//         }
338//       } else {
339//         // VECTOR CASE
340//         if (vectorTile === undefined && rasterData === undefined && gridData === undefined) {
341//           // if we haven't reached the data yet, we store children
342//           if (minzoom > tile.zoom) tile_cache.push(...idChildrenIJ(face, zoom, x, y));
343//         } else {
344//           // write to a buffer using the open-vector-tile spec
345//           let data =
346//             format === 'open-s2'
347//               ? writeOVTile(vectorTile, rasterData, gridData)
348//               : writeMVTile(vectorTile!, format === 'mapbox');
349//           // gzip if necessary
350//           if (encoding === 'gz') data = await compressStream(data, 'gzip');
351//           // yield the buffer
352//           yield { face, zoom, x, y, data };
353//           // store 4 children tiles to ask for children features
354//           tile_cache.push(...idChildrenIJ(face, zoom, x, y));
355//         }
356//       }
357//     }
358//   }
359
360//   /**
361//    * Get raster data for a tile
362//    *
363//    * ## Parameters
364//    * - `id`: the tile id
365//    *
366//    * ## Returns
367//    * A collection of GridInputs
368//    */
369//   async #getRasterTile(id: S2CellId): Promise<ImageDataInput[] | undefined> {
370//     const res: ImageDataInput[] = [];
371//     // store all cluster features
372//     for (const raster of Object.values(this.raster_stores)) {
373//       const layerGrid = await raster.getTile(id);
374//       if (layerGrid === undefined) continue;
375//       const { name, size, data } = layerGrid;
376//       const image = (data as RGBA[]).flatMap(({ r, g, b, a }) => [r, g, b, a]);
377//       res.push({
378//         name,
379//         type: 'raw',
380//         width: size,
381//         height: size,
382//         image: new Uint8Array(image),
383//       });
384//     }
385
386//     if (res.length > 0) return res;
387//   }
388
389//   /**
390//    * Get gridded data for a tile
391//    *
392//    * ## Parameters
393//    * - `id`: the tile id
394//    *
395//    * ## Returns
396//    * A collection of ImageDataInputs
397//    */
398//   async #getGridTile(id: S2CellId): Promise<GridInput[] | undefined> {
399//     const res: GridInput[] = [];
400//     // store all cluster features
401//     for (const [layerName, grid] of Object.entries(this.grid_stores)) {
402//       const { extent } = this.layer_guides.filter(
403//         (guide) => guide.layerName === layerName,
404//       )[0] as GridLayer;
405//       const layerGrid = await grid.getTile(id);
406//       if (layerGrid === undefined) continue;
407//       const { name, size, data } = layerGrid;
408//       res.push({
409//         name,
410//         size,
411//         data: data as number[],
412//         extent,
413//       });
414//     }
415
416//     if (res.length > 0) return res;
417//   }
418
419/// Get the absolute maxzoom from the layer guides
420///
421/// ## Parameters
422/// - `layer_guides`: user defined guide on building/shaping specific layer data
423///
424/// ## Returns
425/// Tthe absolute maxzoom
426fn get_zooms(layer_guides: &[LayerGuide]) -> (u8, u8) {
427    let mut min: u8 = 30;
428    let mut max = 0;
429
430    for layer_guide in layer_guides {
431        let (l_min, l_max) = layer_guide.zooms();
432        if l_min < min {
433            min = l_min;
434        }
435        if l_max > max {
436            max = l_max;
437        }
438    }
439
440    (min, max)
441}
442
443/// Pre-earclip polygons for faster processing of the tile client side
444///
445/// ## Parameters
446/// - `feature`: The feature to earclip. Only `Polygon` and `MultiPolygon` types are earclipped
447/// - `zoom`: The current zoom of the tile to ensure the tessellation is good for S2 projection
448pub fn earclip_polygons<M: Clone, P: MValueCompatible, D: MValueCompatible>(
449    feature: &mut VectorFeature<M, P, D>,
450    zoom: u8,
451) {
452    let mut polys: Vec<&VectorPolygon<D>> = vec![];
453    match &feature.geometry {
454        VectorGeometry::Polygon(poly) => {
455            polys.push(&poly.coordinates);
456        }
457        VectorGeometry::MultiPolygon(multipoly) => {
458            for poly in &multipoly.coordinates {
459                polys.push(poly);
460            }
461        }
462        _ => {}
463    }
464
465    let mut offset = 0;
466    let mut verts = vec![];
467    let mut indices = vec![];
468    for poly in polys {
469        // create triangle mesh
470        let (vertices, ind) = earclip(poly, None, Some(offset / 2));
471        // update vertex position
472        offset += vertices.len();
473        verts.extend(vertices);
474        // store indices
475        indices.extend(ind);
476    }
477    let tess_pos = verts.len();
478
479    // TODO: we can actually skip tesselate if our projection is not S2
480    let level = 1 << fmax(fmin(floor(zoom as f64 / 2.), 4.), 0.) as i32;
481    let division = (16 / level) as f64;
482    if division > 1. {
483        tesselate(&mut verts, &mut indices, 1. / division, 2);
484    }
485
486    let tess_points = &verts[tess_pos..];
487
488    // store
489    feature.geometry.set_indices(indices.into_iter().map(|i| i as u32).collect());
490    feature.geometry.set_tess(tess_points.to_vec());
491}