• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Open-S2 / gis-tools / #55

27 Apr 2025 12:10AM UTC coverage: 94.176% (+0.06%) from 94.117%
#55

push

Mr Martian
to old school geojson for both TS and Rust; Adjust rust structure for convert; tests

87284 of 92682 relevant lines covered (94.18%)

1916.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.08
/rust/data_structures/tile.rs
1
use crate::{
2
    geometry::{
3
        CellId, ConvertFeature, ConvertVectorFeatureS2, ConvertVectorFeatureWM, Face,
4
        JSONCollection, Projection, SimplifyVectorGeometry, TileChildren, VectorFeature,
5
        VectorGeometry, VectorPoint, build_sq_dists, convert,
6
    },
7
    readers::FeatureReader,
8
};
9
use alloc::{
10
    collections::{BTreeMap, BTreeSet},
11
    string::{String, ToString},
12
    vec,
13
    vec::Vec,
14
};
15
use s2json::{Feature, MValue, Properties};
16
use serde::{Deserialize, Serialize};
17

18
/// If a user creates metadata for a VectorFeature, it needs to define a get_layer function
19
pub trait HasLayer {
20
    /// Get the layer from metadata if it exists
21
    fn get_layer(&self) -> Option<String>;
22
}
23
impl HasLayer for () {
24
    fn get_layer(&self) -> Option<String> {
×
25
        None
×
26
    }
×
27
}
28
impl HasLayer for MValue {
29
    fn get_layer(&self) -> Option<String> {
×
30
        let layer = self.get("layer");
×
31
        match layer {
×
32
            Some(l) => l.to_prim()?.to_string(),
×
33
            _ => None,
×
34
        }
35
    }
×
36
}
37

38
/// Tile Class to contain the tile information for splitting or simplifying
39
#[derive(Debug, Clone, PartialEq)]
40
pub struct Tile<M = (), P: Clone + Default = Properties, D: Clone + Default = MValue> {
41
    /// the tile id
42
    pub id: CellId,
43
    /// the tile's layers
44
    pub layers: BTreeMap<String, Layer<M, P, D>>,
45
    /// whether the tile feature geometry has been transformed
46
    pub transformed: bool,
47
}
48
impl<M: HasLayer + Clone, P: Clone + Default, D: Clone + Default> Tile<M, P, D> {
49
    /// Create a new Tile
50
    pub fn new(id: CellId) -> Self {
32✔
51
        Self { id, layers: BTreeMap::new(), transformed: false }
32✔
52
    }
32✔
53

54
    /// Returns the number of layers
55
    pub fn len(&self) -> usize {
5✔
56
        self.layers.len()
5✔
57
    }
5✔
58

59
    /// Returns true if the tile is empty of features
60
    pub fn is_empty(&self) -> bool {
35✔
61
        for layer in self.layers.values() {
35✔
62
            if !layer.features.is_empty() {
10✔
63
                return false;
10✔
64
            }
×
65
        }
66

67
        true
25✔
68
    }
35✔
69

70
    /// Add any reader to the tile
71
    pub fn add_reader<R>(&mut self, reader: R, layer: Option<String>)
×
72
    where
×
73
        R: FeatureReader<M, P, D>,
×
74
    {
×
75
        for feature in reader.iter() {
×
76
            self.add_feature(feature, layer.clone());
×
77
        }
×
78
    }
×
79

80
    /// Add a feature to the tile
81
    pub fn add_feature(&mut self, feature: VectorFeature<M, P, D>, layer: Option<String>) {
15✔
82
        let layer_name = feature
15✔
83
            .metadata
15✔
84
            .as_ref()
15✔
85
            .and_then(|meta| meta.get_layer()) // Get the layer from metadata if it exists
15✔
86
            .or(layer) // Fall back to the provided layer
15✔
87
            .unwrap_or_else(|| "default".to_string()); // Fall back to "default" if none found
15✔
88

89
        let layer = self.layers.entry(layer_name.clone()).or_insert(Layer::new(layer_name));
15✔
90
        layer.features.push(feature);
15✔
91
    }
15✔
92

93
    /// Simplify the geometry to have a tolerance which will be relative to the tile's zoom level.
94
    /// NOTE: This should be called after the tile has been split into children if that functionality
95
    /// is needed.
96
    pub fn transform(&mut self, tolerance: f64, maxzoom: Option<u8>) {
9✔
97
        if self.transformed || self.id.is_face() {
9✔
98
            self.transformed = true;
9✔
99
            return;
9✔
100
        }
×
101

×
102
        let (_, zoom, i, j) = self.id.to_face_ij();
×
103
        for layer in self.layers.values_mut() {
×
104
            for feature in layer.features.iter_mut() {
×
105
                feature.geometry.simplify(tolerance, zoom, maxzoom);
×
106
                feature.geometry.transform(zoom.into(), i as f64, j as f64)
×
107
            }
108
        }
109

110
        self.transformed = true;
×
111
    }
9✔
112
}
113

114
/// Layer Class to contain the layer information for splitting or simplifying
115
#[derive(Debug, Clone, PartialEq)]
116
pub struct Layer<M = (), P: Clone + Default = Properties, D: Clone + Default = MValue> {
117
    /// the layer name
118
    pub name: String,
119
    /// the layer's features
120
    pub features: Vec<VectorFeature<M, P, D>>,
121
}
122
impl<M, P: Clone + Default, D: Clone + Default> Layer<M, P, D> {
123
    /// Create a new Layer
124
    pub fn new(name: String) -> Self {
15✔
125
        Self { name, features: vec![] }
15✔
126
    }
15✔
127
}
128

129
/// Options for creating a TileStore
130
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
131
pub struct TileStoreOptions {
132
    /// manually set the projection, otherwise it defaults to whatever the data type is
133
    pub projection: Option<Projection>,
134
    /// min zoom to generate data on
135
    pub minzoom: Option<u8>,
136
    /// max zoom level to cluster the points on
137
    pub maxzoom: Option<u8>,
138
    /// tile buffer on each side in pixels
139
    pub index_maxzoom: Option<u8>,
140
    /// simplification tolerance (higher means simpler). Default is 3.
141
    /// Note: this tolerance is measured against a 4_096x4_096 unit grid when applying simplification.
142
    pub tolerance: Option<f64>,
143
    /// tile buffer on each side so lines and polygons don't get clipped
144
    pub buffer: Option<f64>,
145
}
146

147
/// TileStore Class is a tile-lookup system that splits and simplifies as needed for each tile request */
148
#[derive(Debug, Clone, PartialEq)]
149
pub struct TileStore<
150
    M: HasLayer + Clone = (),
151
    P: Clone + Default = Properties,
152
    D: Clone + Default = MValue,
153
> {
154
    minzoom: u8,                            // min zoom to preserve detail on
155
    maxzoom: u8,                            // max zoom to preserve detail on
156
    faces: BTreeSet<Face>, // store which faces are active. 0 face could be entire WM projection
157
    index_maxzoom: u8,     // max zoom in the tile index
158
    tolerance: f64,        // simplification tolerance (higher means simpler)
159
    buffer: f64,           // tile buffer for lines and polygons
160
    tiles: BTreeMap<CellId, Tile<M, P, D>>, // stores both WM and S2 tiles
161
    projection: Projection, // projection to build tiles for
162
}
163
impl<M: HasLayer + Clone, P: Clone + Default, D: Clone + Default> Default for TileStore<M, P, D> {
164
    fn default() -> Self {
2✔
165
        Self {
2✔
166
            minzoom: 0,
2✔
167
            maxzoom: 16,
2✔
168
            faces: BTreeSet::<Face>::new(),
2✔
169
            index_maxzoom: 4,
2✔
170
            tolerance: 3. / 4_096.,
2✔
171
            buffer: 0.0625,
2✔
172
            tiles: BTreeMap::<CellId, Tile<M, P, D>>::new(),
2✔
173
            projection: Projection::S2,
2✔
174
        }
2✔
175
    }
2✔
176
}
177
impl<M: HasLayer + Clone, P: Clone + Default, D: Clone + Default> TileStore<M, P, D>
178
where
179
    VectorFeature<M, P, D>: ConvertVectorFeatureWM<M, P, D> + ConvertVectorFeatureS2<M, P, D>,
180
    Feature<M, P, D>: ConvertFeature<M, P, D>,
181
{
182
    /// Create a new TileStore
183
    pub fn new(data: JSONCollection<M, P, D>, options: TileStoreOptions) -> Self {
3✔
184
        let mut tile_store = Self {
3✔
185
            minzoom: options.minzoom.unwrap_or(0),
3✔
186
            maxzoom: options.maxzoom.unwrap_or(16),
3✔
187
            faces: BTreeSet::<Face>::new(),
3✔
188
            index_maxzoom: options.index_maxzoom.unwrap_or(4),
3✔
189
            tolerance: options.tolerance.unwrap_or(3.) / 4_096.,
3✔
190
            buffer: options.buffer.unwrap_or(64.),
3✔
191
            tiles: BTreeMap::<CellId, Tile<M, P, D>>::new(),
3✔
192
            projection: options.projection.unwrap_or(Projection::S2),
3✔
193
        };
3✔
194
        // sanity check
3✔
195
        debug_assert!(
3✔
196
            tile_store.minzoom <= tile_store.maxzoom
3✔
197
                && tile_store.maxzoom > 0
3✔
198
                && tile_store.maxzoom <= 20,
3✔
199
            "maxzoom should be in the 0-20 range"
×
200
        );
201
        // convert features
202
        let features: Vec<VectorFeature<M, P, D>> =
3✔
203
            convert(tile_store.projection, &data, Some(true), Some(true));
3✔
204
        features.into_iter().for_each(|feature| tile_store.add_feature(feature));
12✔
205
        for i in 0..6 {
21✔
206
            tile_store.split_tile(CellId::from_face(i), None, None);
18✔
207
        }
18✔
208

209
        tile_store
3✔
210
    }
3✔
211

212
    /// Add a feature to the tile store
213
    fn add_feature(&mut self, mut feature: VectorFeature<M, P, D>) {
12✔
214
        let face: u8 = feature.face.into();
12✔
215
        let tile = self.tiles.entry(CellId::from_face(face)).or_insert_with(|| {
12✔
216
            self.faces.insert(feature.face);
6✔
217
            Tile::new(CellId::from_face(face))
6✔
218
        });
6✔
219
        // Prep Douglas-Peucker simplification by setting t-values.
220
        build_sq_dists(&mut feature.geometry, self.tolerance, Some(self.maxzoom));
12✔
221

12✔
222
        tile.add_feature(feature, None);
12✔
223
    }
12✔
224

225
    /// Split tiles given a range
226
    fn split_tile(&mut self, start_id: CellId, end_id: Option<CellId>, end_zoom: Option<u8>) {
21✔
227
        let TileStore { buffer, tiles, tolerance, maxzoom, index_maxzoom, .. } = self;
21✔
228
        let end_zoom = end_zoom.unwrap_or(*maxzoom);
21✔
229
        let mut stack: Vec<CellId> = vec![start_id];
21✔
230
        // avoid recursion by using a processing queue
231
        while !stack.is_empty() {
66✔
232
            // find our next tile to split
233
            let stack_id = stack.pop();
45✔
234
            if stack_id.is_none() {
45✔
235
                break;
×
236
            }
45✔
237
            let tile = tiles.get_mut(&stack_id.unwrap());
45✔
238
            // if the tile we need does not exist, is empty, or already transformed, skip it
45✔
239
            if tile.is_none() {
45✔
240
                continue;
12✔
241
            }
33✔
242
            let tile = tile.unwrap();
33✔
243
            if tile.is_empty() || tile.transformed {
33✔
244
                continue;
27✔
245
            }
6✔
246
            let tile_zoom = tile.id.level();
6✔
247
            // 1: stop tiling if we reached a defined end
6✔
248
            // 2: stop tiling if it's the first-pass tiling, and we reached max zoom for indexing
6✔
249
            // 3: stop at currently needed maxzoom OR current tile does not include child
6✔
250
            if tile_zoom >= *maxzoom || // 1
6✔
251
                (end_id.is_none() && tile_zoom >= *index_maxzoom) || // 2
6✔
252
                (end_id.is_some() && (tile_zoom > end_zoom || !tile.id.contains(end_id.unwrap())))
6✔
253
            {
254
                continue;
×
255
            }
6✔
256

6✔
257
            // split the tile
6✔
258
            let TileChildren {
6✔
259
                bottom_left: bl_id,
6✔
260
                bottom_right: br_id,
6✔
261
                top_left: tl_id,
6✔
262
                top_right: tr_id,
6✔
263
            } = tile.split(Some(*buffer));
6✔
264
            // now that the tile has been split, we can transform it
6✔
265
            tile.transform(*tolerance, Some(*maxzoom));
6✔
266
            // push the new features to the stack
6✔
267
            stack.extend(vec![bl_id.id, br_id.id, tl_id.id, tr_id.id]);
6✔
268
            // store the children
6✔
269
            tiles.insert(bl_id.id, bl_id);
6✔
270
            tiles.insert(br_id.id, br_id);
6✔
271
            tiles.insert(tl_id.id, tl_id);
6✔
272
            tiles.insert(tr_id.id, tr_id);
6✔
273
        }
274
    }
21✔
275

276
    /// Get a tile
277
    pub fn get_tile(&mut self, id: CellId) -> Option<&Tile<M, P, D>> {
3✔
278
        let zoom = id.level();
3✔
279
        let face = id.face();
3✔
280
        // If the zoom is out of bounds, return nothing
3✔
281
        if !(0..=20).contains(&zoom) || !self.faces.contains(&face.into()) {
3✔
282
            return None;
×
283
        }
3✔
284

3✔
285
        // we want to find the closest tile to the data.
3✔
286
        let mut p_id = id;
3✔
287
        while !self.tiles.contains_key(&p_id) && !p_id.is_face() {
3✔
288
            p_id = p_id.parent(None);
×
289
        }
×
290
        // split as necessary, the algorithm will know if the tile is already split
291
        self.split_tile(p_id, Some(id), Some(zoom));
3✔
292

3✔
293
        // grab the tile and split it if necessary
3✔
294
        self.tiles.get(&id)
3✔
295
    }
3✔
296
}
297

298
/// A trait for transforming a geometry from the 0->1 coordinate system to a tile coordinate system
299
pub trait TransformVectorGeometry<M: Clone + Default = MValue> {
300
    /// Transform the geometry from the 0->1 coordinate system to a tile coordinate system
301
    fn transform(&mut self, zoom: f64, ti: f64, tj: f64);
302
}
303
impl<M: Clone + Default> TransformVectorGeometry<M> for VectorGeometry<M> {
304
    /// Transform the geometry from the 0->1 coordinate system to a tile coordinate system
305
    fn transform(&mut self, zoom: f64, ti: f64, tj: f64) {
×
306
        let zoom = (1 << (zoom as u64)) as f64;
×
307
        match self {
×
308
            VectorGeometry::Point(p) => p.coordinates.transform(zoom, ti, tj),
×
309
            VectorGeometry::LineString(l) | VectorGeometry::MultiPoint(l) => {
×
310
                l.coordinates.iter_mut().for_each(|p| p.transform(zoom, ti, tj))
×
311
            }
312
            VectorGeometry::MultiLineString(l) | VectorGeometry::Polygon(l) => l
×
313
                .coordinates
×
314
                .iter_mut()
×
315
                .for_each(|l| l.iter_mut().for_each(|p| p.transform(zoom, ti, tj))),
×
316
            VectorGeometry::MultiPolygon(l) => l.coordinates.iter_mut().for_each(|p| {
×
317
                p.iter_mut().for_each(|l| l.iter_mut().for_each(|p| p.transform(zoom, ti, tj)))
×
318
            }),
×
319
        }
320
    }
×
321
}
322
impl<M: Clone + Default> TransformVectorGeometry<M> for VectorPoint<M> {
323
    /// Transform the point from the 0->1 coordinate system to a tile coordinate system
324
    fn transform(&mut self, zoom: f64, ti: f64, tj: f64) {
3✔
325
        self.x = self.x * zoom - ti;
3✔
326
        self.y = self.y * zoom - tj;
3✔
327
    }
3✔
328
}
329

330
#[cfg(test)]
331
mod tests {
332
    use super::*;
333
    use crate::geometry::S2CellId;
334
    use core::f64;
335
    use s2json::{
336
        BBox3D, Map, VectorLineStringGeometry, VectorMultiLineStringGeometry,
337
        VectorMultiPointGeometry, VectorPointGeometry,
338
    };
339

340
    const SIMPLIFY_MAXZOOM: u8 = 16;
341

342
    #[test]
343
    fn test_transform() {
1✔
344
        let mut p: VectorPoint = VectorPoint { x: 0.5, y: 0.5, z: Some(0.0), m: None, t: None };
1✔
345
        p.transform(10.0, 0.0, 0.0);
1✔
346
        assert_eq!(p.x, 5.0);
1✔
347
        assert_eq!(p.y, 5.0);
1✔
348

349
        let mut p: VectorPoint = VectorPoint { x: 0., y: 0., z: Some(0.0), m: None, t: None };
1✔
350
        p.transform(1., 0., 0.);
1✔
351
        assert_eq!(p.x, 0.);
1✔
352
        assert_eq!(p.y, 0.);
1✔
353

354
        let mut p: VectorPoint = VectorPoint { x: 0., y: 0., z: Some(0.0), m: None, t: None };
1✔
355
        p.transform(1., 1., 0.);
1✔
356
        assert_eq!(p.x, -1.);
1✔
357
        assert_eq!(p.y, -0.);
1✔
358
    }
1✔
359

360
    #[test]
361
    fn test_tile() {
1✔
362
        let mut tile: Tile = Tile::new(S2CellId::from_face(0));
1✔
363
        assert_eq!(
1✔
364
            tile,
1✔
365
            Tile { id: 1152921504606846976.into(), layers: BTreeMap::new(), transformed: false }
1✔
366
        );
367
        assert!(tile.is_empty());
1✔
368
        assert_eq!(tile.len(), 0);
1✔
369

370
        tile.add_feature(
1✔
371
            VectorFeature::new_wm(
1✔
372
                None,
1✔
373
                Map::new(),
1✔
374
                VectorGeometry::Point(VectorPointGeometry {
1✔
375
                    _type: "Point".into(),
1✔
376
                    is_3d: false,
1✔
377
                    coordinates: VectorPoint { x: 0., y: 0., z: None, m: None, t: None },
1✔
378
                    ..Default::default()
1✔
379
                }),
1✔
380
                None,
1✔
381
            ),
1✔
382
            Some("default".into()),
1✔
383
        );
1✔
384

1✔
385
        assert!(!tile.is_empty());
1✔
386
        assert_eq!(tile.len(), 1);
1✔
387

388
        tile.transform(3., Some(SIMPLIFY_MAXZOOM));
1✔
389
        // call it again (it will fail)
1✔
390
        tile.transform(3., Some(SIMPLIFY_MAXZOOM));
1✔
391

1✔
392
        // grab the feature
1✔
393
        let layer = tile.layers.get("default").unwrap();
1✔
394
        let first_feature = layer.features.first().unwrap();
1✔
395
        assert_eq!(
1✔
396
            first_feature.geometry,
1✔
397
            VectorGeometry::Point(VectorPointGeometry {
1✔
398
                _type: "Point".into(),
1✔
399
                is_3d: false,
1✔
400
                coordinates: VectorPoint { x: 0., y: 0., z: None, m: None, t: None },
1✔
401
                ..Default::default()
1✔
402
            })
1✔
403
        );
404
    }
1✔
405

406
    #[test]
407
    fn test_tile_store() {
1✔
408
        let tile_store: TileStore = TileStore::default();
1✔
409
        assert_eq!(
1✔
410
            tile_store,
1✔
411
            TileStore {
1✔
412
                minzoom: 0,
1✔
413
                maxzoom: 16,
1✔
414
                faces: BTreeSet::<Face>::new(),
1✔
415
                index_maxzoom: 4,
1✔
416
                tolerance: 0.000732421875,
1✔
417
                buffer: 0.0625,
1✔
418
                tiles: BTreeMap::new(),
1✔
419
                projection: Projection::S2,
1✔
420
            }
1✔
421
        );
422

423
        let tile_store: TileStore = Default::default();
1✔
424
        assert_eq!(
1✔
425
            tile_store,
1✔
426
            TileStore {
1✔
427
                minzoom: 0,
1✔
428
                maxzoom: 16,
1✔
429
                faces: BTreeSet::<Face>::new(),
1✔
430
                index_maxzoom: 4,
1✔
431
                tolerance: 0.000732421875,
1✔
432
                buffer: 0.0625,
1✔
433
                tiles: BTreeMap::new(),
1✔
434
                projection: Projection::S2,
1✔
435
            }
1✔
436
        );
437
    }
1✔
438

439
    #[test]
440
    fn test_tile_store_wg_points() {
1✔
441
        let json_string = r#"{
1✔
442
            "type": "FeatureCollection",
1✔
443
            "features": [
1✔
444
                {
1✔
445
                    "type": "Feature",
1✔
446
                    "properties": { "a": 1 },
1✔
447
                    "geometry": {
1✔
448
                        "type": "Point",
1✔
449
                        "coordinates": [0, 0]
1✔
450
                    }
1✔
451
                },
1✔
452
                {
1✔
453
                    "type": "Feature",
1✔
454
                    "properties": { "b": 2 },
1✔
455
                    "geometry": {
1✔
456
                        "type": "Point3D",
1✔
457
                        "coordinates": [45, 45, 1]
1✔
458
                    }
1✔
459
                },
1✔
460
                {
1✔
461
                    "type": "Feature",
1✔
462
                    "properties": { "c": 3 },
1✔
463
                    "geometry": {
1✔
464
                        "type": "MultiPoint",
1✔
465
                        "coordinates": [
1✔
466
                            [-45, -45],
1✔
467
                            [-45, 45]
1✔
468
                        ]
1✔
469
                    }
1✔
470
                },
1✔
471
                {
1✔
472
                    "type": "Feature",
1✔
473
                    "properties": { "d": 4 },
1✔
474
                    "geometry": {
1✔
475
                        "type": "MultiPoint3D",
1✔
476
                        "coordinates": [
1✔
477
                            [45, -45, 1],
1✔
478
                            [-180, 20, 2]
1✔
479
                        ]
1✔
480
                    }
1✔
481
                }
1✔
482
            ]
1✔
483
        }"#;
1✔
484
        let data: JSONCollection = serde_json::from_str(json_string).unwrap();
1✔
485
        let mut tile_store: TileStore = TileStore::<_, _, _>::new(
1✔
486
            data,
1✔
487
            TileStoreOptions { projection: Some(Projection::WG), ..Default::default() },
1✔
488
        );
1✔
489

1✔
490
        let face_0_tile = tile_store.get_tile(S2CellId::from_face(0)).unwrap();
1✔
491
        assert_eq!(face_0_tile.len(), 1);
1✔
492
        let default_layer = face_0_tile.layers.get("default").unwrap();
1✔
493
        assert_eq!(default_layer.features.len(), 4);
1✔
494

495
        assert_eq!(
1✔
496
            default_layer.features,
1✔
497
            vec![
1✔
498
                VectorFeature {
1✔
499
                    _type: "VectorFeature".into(),
1✔
500
                    id: None,
1✔
501
                    face: 0.into(),
1✔
502
                    properties: MValue::from([("a".into(), 1_u64.into())]),
1✔
503
                    geometry: VectorGeometry::Point(VectorPointGeometry {
1✔
504
                        _type: "Point".into(),
1✔
505
                        is_3d: false,
1✔
506
                        coordinates: VectorPoint { x: 0.5, y: 0.5, z: None, m: None, t: None },
1✔
507
                        offset: None,
1✔
508
                        bbox: Some(BBox3D {
1✔
509
                            left: 0.0,
1✔
510
                            bottom: 0.0,
1✔
511
                            right: 0.0,
1✔
512
                            top: 0.0,
1✔
513
                            near: 1.7976931348623157e308,
1✔
514
                            far: -1.7976931348623157e308
1✔
515
                        }),
1✔
516
                        vec_bbox: Some(BBox3D {
1✔
517
                            left: 0.5,
1✔
518
                            bottom: 0.5,
1✔
519
                            right: 0.5,
1✔
520
                            top: 0.5,
1✔
521
                            near: f64::MAX,
1✔
522
                            far: f64::MIN
1✔
523
                        }),
1✔
524
                        indices: None,
1✔
525
                        tessellation: None
1✔
526
                    }),
1✔
527
                    metadata: None
1✔
528
                },
1✔
529
                VectorFeature {
1✔
530
                    _type: "VectorFeature".into(),
1✔
531
                    id: None,
1✔
532
                    face: 0.into(),
1✔
533
                    properties: MValue::from([("b".into(), 2_u64.into())]),
1✔
534
                    geometry: VectorGeometry::Point(VectorPointGeometry {
1✔
535
                        _type: "Point".into(),
1✔
536
                        is_3d: true,
1✔
537
                        coordinates: VectorPoint {
1✔
538
                            x: 0.625,
1✔
539
                            y: 0.35972503691520497,
1✔
540
                            z: Some(1.0),
1✔
541
                            m: None,
1✔
542
                            t: None
1✔
543
                        },
1✔
544
                        offset: None,
1✔
545
                        bbox: Some(BBox3D {
1✔
546
                            left: 45.0,
1✔
547
                            bottom: 45.0,
1✔
548
                            right: 45.0,
1✔
549
                            top: 45.0,
1✔
550
                            near: 1.0,
1✔
551
                            far: 1.0
1✔
552
                        }),
1✔
553
                        vec_bbox: Some(BBox3D {
1✔
554
                            left: 0.625,
1✔
555
                            bottom: 0.35972503691520497,
1✔
556
                            right: 0.625,
1✔
557
                            top: 0.35972503691520497,
1✔
558
                            near: 1.0,
1✔
559
                            far: 1.0
1✔
560
                        }),
1✔
561
                        indices: None,
1✔
562
                        tessellation: None
1✔
563
                    }),
1✔
564
                    metadata: None
1✔
565
                },
1✔
566
                VectorFeature {
1✔
567
                    _type: "VectorFeature".into(),
1✔
568
                    id: None,
1✔
569
                    face: 0.into(),
1✔
570
                    properties: MValue::from([("c".into(), 3_u64.into())]),
1✔
571
                    geometry: VectorGeometry::MultiPoint(VectorMultiPointGeometry {
1✔
572
                        _type: "MultiPoint".into(),
1✔
573
                        is_3d: false,
1✔
574
                        coordinates: vec![
1✔
575
                            VectorPoint {
1✔
576
                                x: 0.375,
1✔
577
                                y: 0.640274963084795,
1✔
578
                                z: None,
1✔
579
                                m: None,
1✔
580
                                t: None
1✔
581
                            },
1✔
582
                            VectorPoint {
1✔
583
                                x: 0.375,
1✔
584
                                y: 0.35972503691520497,
1✔
585
                                z: None,
1✔
586
                                m: None,
1✔
587
                                t: None
1✔
588
                            }
1✔
589
                        ],
1✔
590
                        offset: None,
1✔
591
                        bbox: Some(BBox3D {
1✔
592
                            left: -45.0,
1✔
593
                            bottom: -45.0,
1✔
594
                            right: -45.0,
1✔
595
                            top: 45.0,
1✔
596
                            near: 1.7976931348623157e308,
1✔
597
                            far: -1.7976931348623157e308
1✔
598
                        }),
1✔
599
                        vec_bbox: Some(BBox3D {
1✔
600
                            left: 0.375,
1✔
601
                            bottom: 0.35972503691520497,
1✔
602
                            right: 0.375,
1✔
603
                            top: 0.640274963084795,
1✔
604
                            near: f64::MAX,
1✔
605
                            far: f64::MIN
1✔
606
                        }),
1✔
607
                        indices: None,
1✔
608
                        tessellation: None
1✔
609
                    }),
1✔
610
                    metadata: None
1✔
611
                },
1✔
612
                VectorFeature {
1✔
613
                    _type: "VectorFeature".into(),
1✔
614
                    id: None,
1✔
615
                    face: 0.into(),
1✔
616
                    properties: MValue::from([("d".into(), 4_u64.into())]),
1✔
617
                    geometry: VectorGeometry::MultiPoint(VectorMultiPointGeometry {
1✔
618
                        _type: "MultiPoint".into(),
1✔
619
                        is_3d: true,
1✔
620
                        coordinates: vec![
1✔
621
                            VectorPoint {
1✔
622
                                x: 0.625,
1✔
623
                                y: 0.640274963084795,
1✔
624
                                z: Some(1.0),
1✔
625
                                m: None,
1✔
626
                                t: None
1✔
627
                            },
1✔
628
                            VectorPoint {
1✔
629
                                x: 0.0,
1✔
630
                                y: 0.4432805993614054,
1✔
631
                                z: Some(2.0),
1✔
632
                                m: None,
1✔
633
                                t: None
1✔
634
                            }
1✔
635
                        ],
1✔
636
                        offset: None,
1✔
637
                        bbox: Some(BBox3D {
1✔
638
                            left: -180.0,
1✔
639
                            bottom: -45.0,
1✔
640
                            right: 45.0,
1✔
641
                            top: 20.0,
1✔
642
                            near: 1.0,
1✔
643
                            far: 2.0
1✔
644
                        }),
1✔
645
                        vec_bbox: Some(BBox3D {
1✔
646
                            left: 0.0,
1✔
647
                            bottom: 0.4432805993614054,
1✔
648
                            right: 0.625,
1✔
649
                            top: 0.640274963084795,
1✔
650
                            near: 1.0,
1✔
651
                            far: 2.0
1✔
652
                        }),
1✔
653
                        indices: None,
1✔
654
                        tessellation: None
1✔
655
                    }),
1✔
656
                    metadata: None
1✔
657
                }
1✔
658
            ]
659
        );
660
    }
1✔
661

662
    #[test]
663
    fn test_tile_store_s2_points() {
1✔
664
        let json_string = r#"{
1✔
665
            "type": "FeatureCollection",
1✔
666
            "features": [
1✔
667
                {
1✔
668
                    "type": "Feature",
1✔
669
                    "properties": { "a": 1 },
1✔
670
                    "geometry": {
1✔
671
                        "type": "Point",
1✔
672
                        "coordinates": [0, 0]
1✔
673
                    }
1✔
674
                },
1✔
675
                {
1✔
676
                    "type": "Feature",
1✔
677
                    "properties": { "b": 2 },
1✔
678
                    "geometry": {
1✔
679
                        "type": "Point3D",
1✔
680
                        "coordinates": [45, 45, 1]
1✔
681
                    }
1✔
682
                },
1✔
683
                {
1✔
684
                    "type": "Feature",
1✔
685
                    "properties": { "c": 3 },
1✔
686
                    "geometry": {
1✔
687
                        "type": "MultiPoint",
1✔
688
                        "coordinates": [
1✔
689
                            [-45, -45],
1✔
690
                            [-45, 45]
1✔
691
                        ]
1✔
692
                    }
1✔
693
                },
1✔
694
                {
1✔
695
                    "type": "Feature",
1✔
696
                    "properties": { "d": 4 },
1✔
697
                    "geometry": {
1✔
698
                        "type": "MultiPoint3D",
1✔
699
                        "coordinates": [
1✔
700
                            [45, -45, 1],
1✔
701
                            [-180, 20, 2]
1✔
702
                        ]
1✔
703
                    }
1✔
704
                }
1✔
705
            ]
1✔
706
        }"#;
1✔
707
        let data: JSONCollection = serde_json::from_str(json_string).unwrap();
1✔
708
        let mut tile_store: TileStore = TileStore::<_, _, _>::new(
1✔
709
            data,
1✔
710
            TileStoreOptions { projection: Some(Projection::S2), ..Default::default() },
1✔
711
        );
1✔
712

1✔
713
        let face_0_tile = tile_store.get_tile(S2CellId::from_face(0)).unwrap();
1✔
714
        assert_eq!(face_0_tile.len(), 1);
1✔
715
        let default_layer = face_0_tile.layers.get("default").unwrap();
1✔
716
        assert_eq!(default_layer.features.len(), 1);
1✔
717

718
        assert_eq!(
1✔
719
            default_layer.features,
1✔
720
            vec![VectorFeature {
1✔
721
                _type: "S2Feature".into(),
1✔
722
                id: None,
1✔
723
                face: 0.into(),
1✔
724
                properties: MValue::from([("a".into(), 1_u64.into())]),
1✔
725
                geometry: VectorGeometry::Point(VectorPointGeometry {
1✔
726
                    _type: "Point".into(),
1✔
727
                    is_3d: false,
1✔
728
                    coordinates: VectorPoint { x: 0.5, y: 0.5, z: None, m: None, t: None },
1✔
729
                    offset: None,
1✔
730
                    bbox: Some(BBox3D {
1✔
731
                        left: 0.0,
1✔
732
                        bottom: 0.0,
1✔
733
                        right: 0.0,
1✔
734
                        top: 0.0,
1✔
735
                        near: 1.7976931348623157e308,
1✔
736
                        far: -1.7976931348623157e308
1✔
737
                    }),
1✔
738
                    vec_bbox: Some(BBox3D {
1✔
739
                        left: 0.5,
1✔
740
                        bottom: 0.5,
1✔
741
                        right: 0.5,
1✔
742
                        top: 0.5,
1✔
743
                        near: f64::MAX,
1✔
744
                        far: f64::MIN
1✔
745
                    }),
1✔
746
                    indices: None,
1✔
747
                    tessellation: None
1✔
748
                }),
1✔
749
                metadata: None
1✔
750
            }]
1✔
751
        );
752
    }
1✔
753

754
    #[test]
755
    fn test_tile_store_wg_lines() {
1✔
756
        let json_string = r#"{
1✔
757
            "type": "FeatureCollection",
1✔
758
            "features": [
1✔
759
                {
1✔
760
                    "type": "Feature",
1✔
761
                    "properties": {},
1✔
762
                    "geometry": {
1✔
763
                        "type": "LineString",
1✔
764
                        "coordinates": [
1✔
765
                            [-13.292352825505162, 54.34883408204476],
1✔
766
                            [36.83102287804303, 59.56941785818924],
1✔
767
                            [50.34083898563978, 16.040052775278994],
1✔
768
                            [76.38149901912357, 35.155968522292056]
1✔
769
                        ]
1✔
770
                    }
1✔
771
                },
1✔
772
                {
1✔
773
                    "type": "Feature",
1✔
774
                    "properties": {},
1✔
775
                    "geometry": {
1✔
776
                        "type": "MultiLineString3D",
1✔
777
                        "coordinates": [
1✔
778
                            [
1✔
779
                                [138.2192704758947, 53.37525605304839, -1.0],
1✔
780
                                [138.02907780308504, 45.48182328687463, 2.0],
1✔
781
                                [166.1775933788045, 52.68902110529311, 4.0],
1✔
782
                                [161.99335457700874, 40.765696887535825, -0.5]
1✔
783
                            ], [
1✔
784
                                [139.16452129458895, -69.38636090051318, 1.0],
1✔
785
                                [143.85299782010844, -63.55049044056966, 2.0],
1✔
786
                                [128.5373078367444, -51.22800042702269, -0.5],
1✔
787
                                [134.78860987076968, -45.63638565920266, 8.0]
1✔
788
                            ]
1✔
789
                        ]
1✔
790
                    }
1✔
791
                }
1✔
792
            ]
1✔
793
        }"#;
1✔
794
        let data: JSONCollection = serde_json::from_str(json_string).unwrap();
1✔
795
        let mut tile_store: TileStore = TileStore::<_, _, _>::new(
1✔
796
            data,
1✔
797
            TileStoreOptions { projection: Some(Projection::WG), ..Default::default() },
1✔
798
        );
1✔
799

1✔
800
        let face_0_tile = tile_store.get_tile(S2CellId::from_face(0)).unwrap();
1✔
801
        assert_eq!(face_0_tile.len(), 1);
1✔
802
        let default_layer = face_0_tile.layers.get("default").unwrap();
1✔
803
        assert_eq!(default_layer.features.len(), 2);
1✔
804

805
        // [], []], offset: None, bbox: None, vec_bbox: Some(BBox3D { left: 0.8570480773242899, bottom: 0.3240121995384903, right: 0.9616044260522347, top: 0.7712879476591746, near: -1.0, far: 8.0 }), indices: None, tessellation: None }), metadata: None }]
806

807
        assert_eq!(
1✔
808
            default_layer.features,
1✔
809
            vec![
1✔
810
                VectorFeature {
1✔
811
                    _type: "VectorFeature".into(),
1✔
812
                    id: None,
1✔
813
                    face: 0.into(),
1✔
814
                    properties: Map::default(),
1✔
815
                    geometry: VectorGeometry::LineString(VectorLineStringGeometry {
1✔
816
                        _type: "LineString".into(),
1✔
817
                        is_3d: false,
1✔
818
                        coordinates: vec![
1✔
819
                            VectorPoint {
1✔
820
                                x: 0.4630767977069301,
1✔
821
                                y: 0.31942614957229354,
1✔
822
                                z: None,
1✔
823
                                m: None,
1✔
824
                                t: Some(1.),
1✔
825
                            },
1✔
826
                            VectorPoint {
1✔
827
                                x: 0.6023083968834528,
1✔
828
                                y: 0.29277635129241236,
1✔
829
                                z: None,
1✔
830
                                m: None,
1✔
831
                                t: Some(0.01120038734713082),
1✔
832
                            },
1✔
833
                            VectorPoint {
1✔
834
                                x: 0.6398356638489994,
1✔
835
                                y: 0.45485063470883236,
1✔
836
                                z: None,
1✔
837
                                m: None,
1✔
838
                                t: Some(0.00605876326361668)
1✔
839
                            },
1✔
840
                            VectorPoint {
1✔
841
                                x: 0.7121708306086766,
1✔
842
                                y: 0.3955684303719546,
1✔
843
                                z: None,
1✔
844
                                m: None,
1✔
845
                                t: Some(1.0)
1✔
846
                            }
1✔
847
                        ],
1✔
848
                        offset: None,
1✔
849
                        bbox: Some(BBox3D {
1✔
850
                            left: -13.292352825505162,
1✔
851
                            bottom: 16.040052775278994,
1✔
852
                            right: 76.38149901912357,
1✔
853
                            top: 59.56941785818924,
1✔
854
                            near: 1.7976931348623157e308,
1✔
855
                            far: -1.7976931348623157e308
1✔
856
                        }),
1✔
857
                        vec_bbox: Some(BBox3D {
1✔
858
                            left: 0.4630767977069301,
1✔
859
                            bottom: 0.29277635129241236,
1✔
860
                            right: 0.7121708306086766,
1✔
861
                            top: 0.45485063470883236,
1✔
862
                            near: f64::MAX,
1✔
863
                            far: f64::MIN
1✔
864
                        }),
1✔
865
                        indices: None,
1✔
866
                        tessellation: None
1✔
867
                    }),
1✔
868
                    metadata: None
1✔
869
                },
1✔
870
                VectorFeature {
1✔
871
                    _type: "VectorFeature".into(),
1✔
872
                    id: None,
1✔
873
                    face: 0.into(),
1✔
874
                    properties: Map::default(),
1✔
875
                    geometry: VectorGeometry::MultiLineString(VectorMultiLineStringGeometry {
1✔
876
                        _type: "MultiLineString".into(),
1✔
877
                        is_3d: true,
1✔
878
                        coordinates: vec![
1✔
879
                            vec![
1✔
880
                                VectorPoint {
1✔
881
                                    x: 0.8839424179885964,
1✔
882
                                    y: 0.3240121995384903,
1✔
883
                                    z: Some(-1.0),
1✔
884
                                    m: None,
1✔
885
                                    t: Some(1.0)
1✔
886
                                },
1✔
887
                                VectorPoint {
1✔
888
                                    x: 0.8834141050085695,
1✔
889
                                    y: 0.3578242302600759,
1✔
890
                                    z: Some(2.0),
1✔
891
                                    m: None,
1✔
892
                                    t: Some(0.0011428082308213008)
1✔
893
                                },
1✔
894
                                VectorPoint {
1✔
895
                                    x: 0.9616044260522347,
1✔
896
                                    y: 0.32718207741863975,
1✔
897
                                    z: Some(4.0),
1✔
898
                                    m: None,
1✔
899
                                    t: Some(0.0020631440003536124)
1✔
900
                                },
1✔
901
                                VectorPoint {
1✔
902
                                    x: 0.9499815404916909,
1✔
903
                                    y: 0.37578687158091856,
1✔
904
                                    z: Some(-0.5),
1✔
905
                                    m: None,
1✔
906
                                    t: Some(1.0)
1✔
907
                                }
1✔
908
                            ],
1✔
909
                            vec![
1✔
910
                                VectorPoint {
1✔
911
                                    x: 0.8865681147071915,
1✔
912
                                    y: 0.7712879476591746,
1✔
913
                                    z: Some(1.0),
1✔
914
                                    m: None,
1✔
915
                                    t: Some(1.0)
1✔
916
                                },
1✔
917
                                VectorPoint {
1✔
918
                                    x: 0.8995916606114123,
1✔
919
                                    y: 0.730480837159282,
1✔
920
                                    z: Some(2.0),
1✔
921
                                    m: None,
1✔
922
                                    t: Some(0.0005558708889643396)
1✔
923
                                },
1✔
924
                                VectorPoint {
1✔
925
                                    x: 0.8570480773242899,
1✔
926
                                    y: 0.6662313440317926,
1✔
927
                                    z: Some(-0.5),
1✔
928
                                    m: None,
1✔
929
                                    t: Some(0.0003800636747767906)
1✔
930
                                },
1✔
931
                                VectorPoint {
1✔
932
                                    x: 0.8744128051965825,
1✔
933
                                    y: 0.6427889614041957,
1✔
934
                                    z: Some(8.0),
1✔
935
                                    m: None,
1✔
936
                                    t: Some(1.0)
1✔
937
                                }
1✔
938
                            ]
1✔
939
                        ],
1✔
940
                        offset: None,
1✔
941
                        bbox: Some(BBox3D {
1✔
942
                            left: 128.5373078367444,
1✔
943
                            bottom: -69.38636090051318,
1✔
944
                            right: 166.1775933788045,
1✔
945
                            top: 53.37525605304839,
1✔
946
                            near: -1.0,
1✔
947
                            far: 8.0
1✔
948
                        }),
1✔
949
                        vec_bbox: Some(BBox3D {
1✔
950
                            left: 0.8570480773242899,
1✔
951
                            bottom: 0.3240121995384903,
1✔
952
                            right: 0.9616044260522347,
1✔
953
                            top: 0.7712879476591746,
1✔
954
                            near: -1.0,
1✔
955
                            far: 8.0
1✔
956
                        }),
1✔
957
                        indices: None,
1✔
958
                        tessellation: None
1✔
959
                    }),
1✔
960
                    metadata: None
1✔
961
                }
1✔
962
            ]
963
        );
964
    }
1✔
965
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc