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

geo-engine / geoengine / 14596447307

22 Apr 2025 01:48PM UTC coverage: 89.898%. First build
14596447307

Pull #1048

github

web-flow
Merge da49d4fb4 into 1076a6163
Pull Request #1048: 1.86

81 of 140 new or added lines in 40 files covered. (57.86%)

126567 of 140789 relevant lines covered (89.9%)

57170.79 hits per line

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

79.86
/datatypes/src/raster/raster_tile.rs
1
use super::masked_grid::MaskedGrid;
2
use super::{
3
    GeoTransform, GeoTransformAccess, GridBounds, GridIdx2D, GridIndexAccess, GridShape,
4
    GridShape2D, GridShape3D, GridShapeAccess, GridSize, Raster, TileInformation,
5
    grid_or_empty::GridOrEmpty,
6
};
7
use super::{GridIndexAccessMut, RasterProperties};
8
use crate::primitives::CacheHint;
9
use crate::primitives::{
10
    SpatialBounded, SpatialPartition2D, SpatialPartitioned, SpatialResolution, TemporalBounded,
11
    TimeInterval,
12
};
13
use crate::raster::Pixel;
14
use crate::util::{ByteSize, Result};
15
use serde::{Deserialize, Serialize};
16
use std::fmt::Write;
17

18
/// A `RasterTile` is a `BaseTile` of raster data where the data is represented by `GridOrEmpty`.
19
pub type RasterTile<D, T> = BaseTile<GridOrEmpty<D, T>>;
20
/// A `RasterTile2D` is a `BaseTile` of 2-dimensional raster data where the data is represented by `GridOrEmpty`.
21
pub type RasterTile2D<T> = RasterTile<GridShape2D, T>;
22
/// A `RasterTile3D` is a `BaseTile` of 3-dimensional raster data where the data is represented by `GridOrEmpty`.
23
pub type RasterTile3D<T> = RasterTile<GridShape3D, T>;
24

25
/// A `MaterializedRasterTile` is a `BaseTile` of raster data where the data is represented by `Grid`. It implements mutable access to pixels.
26
pub type MaterializedRasterTile<D, T> = BaseTile<MaskedGrid<D, T>>;
27
/// A `MaterializedRasterTile2D` is a 2-dimensional `BaseTile` of raster data where the data is represented by `Grid`. It implements mutable access to pixels.
28
pub type MaterializedRasterTile2D<T> = MaterializedRasterTile<GridShape2D, T>;
29
/// A `MaterializedRasterTile3D` is a 3-dimensional `BaseTile` of raster data where the data is represented by `Grid`. It implements mutable access to pixels.
30
pub type MaterializedRasterTile3D<T> = MaterializedRasterTile<GridShape3D, T>;
31

32
/// A `BaseTile` is the main type used to iterate over tiles of raster data
33
/// The data of the `RasterTile` is stored as `Grid` or `NoDataGrid`. The enum `GridOrEmpty` allows a combination of both.
34
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
35
#[serde(rename_all = "camelCase")]
36
pub struct BaseTile<G> {
37
    /// The `TimeInterval` where this tile is valid.
38
    pub time: TimeInterval,
39
    /// The tile position is the position of the tile in the gird of tiles with origin at the origin of the `global_geo_transform`.
40
    /// This is NOT a pixel position inside the tile.
41
    pub tile_position: GridIdx2D,
42
    // the band of the tile, relevant for multi-band raster
43
    pub band: u32,
44
    /// The global geotransform to transform pixels into geographic coordinates
45
    pub global_geo_transform: GeoTransform,
46
    /// The pixels of the tile are stored as `Grid` or, in case they are all no-data as `NoDataGrid`.
47
    /// The enum `GridOrEmpty` allows a combination of both.
48
    pub grid_array: G,
49
    /// Metadata for the `BaseTile`
50
    pub properties: RasterProperties,
51
    /// Indicate how long the tile may be cached, if `None` the tile may be cached indefinitely.
52
    pub cache_hint: CacheHint,
53
}
54

55
impl<G> BaseTile<G>
56
where
57
    G: GridSize,
58
{
59
    pub fn tile_offset(&self) -> GridIdx2D {
×
60
        self.tile_position
×
61
    }
×
62

63
    pub fn tile_information(&self) -> TileInformation {
3,847✔
64
        TileInformation::new(
3,847✔
65
            self.tile_position,
3,847✔
66
            [self.grid_array.axis_size_y(), self.grid_array.axis_size_x()].into(),
3,847✔
67
            self.global_geo_transform,
3,847✔
68
        )
3,847✔
69
    }
3,847✔
70

71
    /// Use this geo transform to transform `Coordinate2D` into local grid indices and vice versa.
72
    #[inline]
73
    pub fn tile_geo_transform(&self) -> GeoTransform {
3,349,013✔
74
        let global_upper_left_idx = self.tile_position
3,349,013✔
75
            * [
3,349,013✔
76
                self.grid_array.axis_size_y() as isize,
3,349,013✔
77
                self.grid_array.axis_size_x() as isize,
3,349,013✔
78
            ];
3,349,013✔
79

3,349,013✔
80
        let tile_upper_left_coord = self
3,349,013✔
81
            .global_geo_transform
3,349,013✔
82
            .grid_idx_to_pixel_upper_left_coordinate_2d(global_upper_left_idx);
3,349,013✔
83

3,349,013✔
84
        GeoTransform::new(
3,349,013✔
85
            tile_upper_left_coord,
3,349,013✔
86
            self.global_geo_transform.x_pixel_size(),
3,349,013✔
87
            self.global_geo_transform.y_pixel_size(),
3,349,013✔
88
        )
3,349,013✔
89
    }
3,349,013✔
90

91
    pub fn spatial_resolution(&self) -> SpatialResolution {
2✔
92
        self.global_geo_transform.spatial_resolution()
2✔
93
    }
2✔
94
}
95

96
impl<G> ByteSize for BaseTile<G>
97
where
98
    G: ByteSize,
99
{
100
    fn heap_byte_size(&self) -> usize {
23✔
101
        self.grid_array.heap_byte_size() + self.properties.heap_byte_size()
23✔
102
    }
23✔
103
}
104

105
/// A way to compare two `BaseTile` ignoring the `CacheHint` and only considering the actual data.
106
pub trait TilesEqualIgnoringCacheHint<G: PartialEq> {
107
    fn tiles_equal_ignoring_cache_hint(&self, other: &dyn IterableBaseTile<G>) -> bool;
108
}
109

110
/// Allow comparing Iterables of `BaseTile` ignoring the `CacheHint` and only considering the actual data.
111
pub trait IterableBaseTile<G> {
112
    fn iter_tiles(&self) -> Box<dyn Iterator<Item = &BaseTile<G>> + '_>;
113
}
114

115
struct SingleBaseTileIter<'a, G> {
116
    tile: Option<&'a BaseTile<G>>,
117
}
118

119
impl<'a, G> Iterator for SingleBaseTileIter<'a, G> {
120
    type Item = &'a BaseTile<G>;
121

122
    fn next(&mut self) -> Option<Self::Item> {
200✔
123
        self.tile.take()
200✔
124
    }
200✔
125
}
126

127
impl<G: PartialEq> IterableBaseTile<G> for BaseTile<G> {
128
    fn iter_tiles(&self) -> Box<dyn Iterator<Item = &BaseTile<G>> + '_> {
100✔
129
        Box::new(SingleBaseTileIter { tile: Some(self) })
100✔
130
    }
100✔
131
}
132

133
impl<G: PartialEq> IterableBaseTile<G> for Vec<BaseTile<G>> {
134
    fn iter_tiles(&self) -> Box<dyn Iterator<Item = &BaseTile<G>> + '_> {
46✔
135
        Box::new(self.iter())
46✔
136
    }
46✔
137
}
138

139
impl<G: PartialEq, const N: usize> IterableBaseTile<G> for [BaseTile<G>; N] {
140
    fn iter_tiles(&self) -> Box<dyn Iterator<Item = &BaseTile<G>> + '_> {
4✔
141
        Box::new(self.iter())
4✔
142
    }
4✔
143
}
144

145
impl<G: PartialEq, I: IterableBaseTile<G>> TilesEqualIgnoringCacheHint<G> for I {
146
    fn tiles_equal_ignoring_cache_hint(&self, other: &dyn IterableBaseTile<G>) -> bool {
75✔
147
        let mut iter_self = self.iter_tiles();
75✔
148
        let mut iter_other = other.iter_tiles();
75✔
149

150
        loop {
151
            match (iter_self.next(), iter_other.next()) {
339✔
152
                (Some(a), Some(b)) => {
264✔
153
                    if a.time != b.time
264✔
154
                        || a.tile_position != b.tile_position
264✔
155
                        || a.band != b.band
264✔
156
                        || a.global_geo_transform != b.global_geo_transform
264✔
157
                        || a.grid_array != b.grid_array
264✔
158
                        || a.properties != b.properties
264✔
159
                    {
160
                        return false;
×
161
                    }
264✔
162
                }
163
                // both iterators are exhausted
164
                (None, None) => return true,
75✔
165
                // one iterator is exhausted, the other is not, so they are not equal
166
                _ => return false,
×
167
            }
168
        }
169
    }
75✔
170
}
171

172
impl<D, T> BaseTile<GridOrEmpty<D, T>>
173
where
174
    T: Pixel,
175
    D: GridSize + Clone + PartialEq,
176
{
177
    /// create a new `RasterTile`
178
    pub fn new_with_tile_info(
481✔
179
        time: TimeInterval,
481✔
180
        tile_info: TileInformation,
481✔
181
        band: u32,
481✔
182
        data: GridOrEmpty<D, T>,
481✔
183
        cache_hint: CacheHint,
481✔
184
    ) -> Self
481✔
185
    where
481✔
186
        D: GridSize,
481✔
187
    {
481✔
188
        debug_assert_eq!(
481✔
189
            tile_info.tile_size_in_pixels.axis_size_x(),
481✔
190
            data.shape_ref().axis_size_x()
481✔
191
        );
192

193
        debug_assert_eq!(
481✔
194
            tile_info.tile_size_in_pixels.axis_size_y(),
481✔
195
            data.shape_ref().axis_size_y()
481✔
196
        );
197

198
        debug_assert_eq!(
481✔
199
            tile_info.tile_size_in_pixels.number_of_elements(),
481✔
200
            data.shape_ref().number_of_elements()
481✔
201
        );
202

203
        Self {
481✔
204
            time,
481✔
205
            tile_position: tile_info.global_tile_position,
481✔
206
            band,
481✔
207
            global_geo_transform: tile_info.global_geo_transform,
481✔
208
            grid_array: data,
481✔
209
            properties: Default::default(),
481✔
210
            cache_hint,
481✔
211
        }
481✔
212
    }
481✔
213

214
    /// create a new `RasterTile`
215
    pub fn new_with_tile_info_and_properties(
962✔
216
        time: TimeInterval,
962✔
217
        tile_info: TileInformation,
962✔
218
        band: u32,
962✔
219
        data: GridOrEmpty<D, T>,
962✔
220
        properties: RasterProperties,
962✔
221
        cache_hint: CacheHint,
962✔
222
    ) -> Self {
962✔
223
        debug_assert_eq!(
962✔
224
            tile_info.tile_size_in_pixels.axis_size_x(),
962✔
225
            data.shape_ref().axis_size_x()
962✔
226
        );
227

228
        debug_assert_eq!(
962✔
229
            tile_info.tile_size_in_pixels.axis_size_y(),
962✔
230
            data.shape_ref().axis_size_y()
962✔
231
        );
232

233
        debug_assert_eq!(
962✔
234
            tile_info.tile_size_in_pixels.number_of_elements(),
962✔
235
            data.shape_ref().number_of_elements()
962✔
236
        );
237

238
        Self {
962✔
239
            time,
962✔
240
            tile_position: tile_info.global_tile_position,
962✔
241
            band,
962✔
242
            global_geo_transform: tile_info.global_geo_transform,
962✔
243
            grid_array: data,
962✔
244
            properties,
962✔
245
            cache_hint,
962✔
246
        }
962✔
247
    }
962✔
248

249
    /// create a new `RasterTile`
250
    pub fn new(
213,349✔
251
        time: TimeInterval,
213,349✔
252
        tile_position: GridIdx2D,
213,349✔
253
        band: u32,
213,349✔
254
        global_geo_transform: GeoTransform,
213,349✔
255
        data: GridOrEmpty<D, T>,
213,349✔
256
        cache_hint: CacheHint,
213,349✔
257
    ) -> Self {
213,349✔
258
        Self {
213,349✔
259
            time,
213,349✔
260
            tile_position,
213,349✔
261
            band,
213,349✔
262
            global_geo_transform,
213,349✔
263
            grid_array: data,
213,349✔
264
            properties: RasterProperties::default(),
213,349✔
265
            cache_hint,
213,349✔
266
        }
213,349✔
267
    }
213,349✔
268

269
    /// create a new `RasterTile`
270
    pub fn new_with_properties(
×
271
        time: TimeInterval,
×
272
        tile_position: GridIdx2D,
×
273
        band: u32,
×
274
        global_geo_transform: GeoTransform,
×
275
        data: GridOrEmpty<D, T>,
×
276
        properties: RasterProperties,
×
277
        cache_hint: CacheHint,
×
278
    ) -> Self {
×
279
        Self {
×
280
            time,
×
281
            tile_position,
×
282
            band,
×
283
            global_geo_transform,
×
284
            grid_array: data,
×
285
            properties,
×
286
            cache_hint,
×
287
        }
×
288
    }
×
289

290
    /// create a new `RasterTile`
291
    pub fn new_without_offset<G>(
34✔
292
        time: TimeInterval,
34✔
293
        global_geo_transform: GeoTransform,
34✔
294
        data: G,
34✔
295
        cache_hint: CacheHint,
34✔
296
    ) -> Self
34✔
297
    where
34✔
298
        G: Into<GridOrEmpty<D, T>>,
34✔
299
    {
34✔
300
        Self {
34✔
301
            time,
34✔
302
            tile_position: [0, 0].into(),
34✔
303
            band: 0,
34✔
304
            global_geo_transform,
34✔
305
            grid_array: data.into(),
34✔
306
            properties: RasterProperties::default(),
34✔
307
            cache_hint,
34✔
308
        }
34✔
309
    }
34✔
310

311
    /// Returns true if the grid is a `NoDataGrid`
312
    pub fn is_empty(&self) -> bool {
364✔
313
        self.grid_array.is_empty()
364✔
314
    }
364✔
315

316
    /// Convert the tile into a materialized tile.
317
    pub fn into_materialized_tile(self) -> MaterializedRasterTile<D, T> {
324✔
318
        MaterializedRasterTile {
324✔
319
            grid_array: self.grid_array.into_materialized_masked_grid(),
324✔
320
            time: self.time,
324✔
321
            tile_position: self.tile_position,
324✔
322
            band: 0,
324✔
323
            global_geo_transform: self.global_geo_transform,
324✔
324
            properties: self.properties,
324✔
325
            cache_hint: self.cache_hint.clone_with_current_datetime(),
324✔
326
        }
324✔
327
    }
324✔
328

329
    pub fn materialize(&mut self) {
×
330
        match self.grid_array {
×
331
            GridOrEmpty::Grid(_) => {}
×
332
            GridOrEmpty::Empty(_) => {
×
333
                self.grid_array = self
×
334
                    .grid_array
×
335
                    .clone()
×
336
                    .into_materialized_masked_grid()
×
337
                    .into();
×
338
            }
×
339
        }
340
    }
×
341
}
342

343
impl<G> TemporalBounded for BaseTile<G> {
344
    fn temporal_bounds(&self) -> TimeInterval {
×
345
        self.time
×
346
    }
×
347
}
348

349
impl<G> SpatialPartitioned for BaseTile<G>
350
where
351
    G: GridSize,
352
{
353
    fn spatial_partition(&self) -> SpatialPartition2D {
220✔
354
        self.tile_information().spatial_partition()
220✔
355
    }
220✔
356
}
357

358
impl<D, T, G> Raster<D, T> for BaseTile<G>
359
where
360
    D: GridSize + GridBounds + Clone,
361
    T: Pixel,
362
    G: GridIndexAccess<D::IndexArray, T>,
363
    Self: SpatialBounded + GridShapeAccess<ShapeArray = D::ShapeArray>,
364
{
365
    type DataContainer = G;
366

367
    fn data_container(&self) -> &G {
×
368
        &self.grid_array
×
369
    }
×
370
}
371

372
impl<T, G, I> GridIndexAccess<Option<T>, I> for BaseTile<G>
373
where
374
    G: GridIndexAccess<Option<T>, I>,
375
    T: Pixel,
376
{
377
    fn get_at_grid_index(&self, grid_index: I) -> Result<Option<T>> {
278✔
378
        self.grid_array.get_at_grid_index(grid_index)
278✔
379
    }
278✔
380

381
    fn get_at_grid_index_unchecked(&self, grid_index: I) -> Option<T> {
34,010,520✔
382
        self.grid_array.get_at_grid_index_unchecked(grid_index)
34,010,520✔
383
    }
34,010,520✔
384
}
385

386
impl<T, G, I> GridIndexAccessMut<Option<T>, I> for BaseTile<G>
387
where
388
    G: GridIndexAccessMut<Option<T>, I>,
389
    T: Pixel,
390
{
391
    fn set_at_grid_index(&mut self, grid_index: I, value: Option<T>) -> Result<()> {
×
392
        self.grid_array.set_at_grid_index(grid_index, value)
×
393
    }
×
394

395
    fn set_at_grid_index_unchecked(&mut self, grid_index: I, value: Option<T>) {
×
396
        self.grid_array
×
397
            .set_at_grid_index_unchecked(grid_index, value);
×
398
    }
×
399
}
400

401
impl<G, A> GridShapeAccess for BaseTile<G>
402
where
403
    G: GridShapeAccess<ShapeArray = A>,
404
    A: AsRef<[usize]> + Into<GridShape<A>>,
405
{
406
    type ShapeArray = A;
407

408
    fn grid_shape_array(&self) -> Self::ShapeArray {
2,261✔
409
        self.grid_array.grid_shape_array()
2,261✔
410
    }
2,261✔
411
}
412

413
impl<G> GeoTransformAccess for BaseTile<G> {
414
    fn geo_transform(&self) -> GeoTransform {
1,434✔
415
        self.global_geo_transform
1,434✔
416
    }
1,434✔
417
}
418

419
impl<D, T> From<MaterializedRasterTile<D, T>> for RasterTile<D, T>
420
where
421
    T: Clone,
422
{
423
    fn from(mat_tile: MaterializedRasterTile<D, T>) -> Self {
188✔
424
        RasterTile {
188✔
425
            grid_array: mat_tile.grid_array.into(),
188✔
426
            global_geo_transform: mat_tile.global_geo_transform,
188✔
427
            tile_position: mat_tile.tile_position,
188✔
428
            band: mat_tile.band,
188✔
429
            time: mat_tile.time,
188✔
430
            properties: mat_tile.properties,
188✔
431
            cache_hint: mat_tile.cache_hint,
188✔
432
        }
188✔
433
    }
188✔
434
}
435

436
/// Pretty printer for raster tiles with 2D ASCII grids
437
pub fn display_raster_tile_2d<P: Pixel + std::fmt::Debug>(
×
438
    raster_tile_2d: &RasterTile2D<P>,
×
439
) -> impl std::fmt::Debug + '_ {
×
440
    struct DebugTile<'a, P>(&'a RasterTile2D<P>);
441

442
    impl<P: Pixel> std::fmt::Debug for DebugTile<'_, P> {
443
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
444
            let tile = self.0;
×
445
            let mut fmt = f.debug_struct(stringify!(RasterTile2D));
×
446
            fmt.field("time", &tile.time);
×
447
            fmt.field("tile_position", &tile.tile_position);
×
448
            fmt.field("global_geo_transform", &tile.global_geo_transform);
×
449
            fmt.field("properties", &tile.properties);
×
450

451
            let grid = if let Some(grid) = tile.grid_array.as_masked_grid() {
×
452
                let values: Vec<String> = grid
×
453
                    .masked_element_ref_iterator()
×
454
                    .map(|v| v.map_or('_'.to_string(), |v| format!("{v:?}")))
×
455
                    .collect();
×
456
                let max_digits = values.iter().map(String::len).max().unwrap_or(0);
×
457

×
458
                let mut s = vec![String::new()];
×
459

×
460
                let last_value_index = values.len() - 1;
×
461
                for (i, value) in values.into_iter().enumerate() {
×
462
                    let str_ref = s
×
463
                        .last_mut()
×
464
                        .expect("it shouldn't be empty since it was populated before the loop");
×
465

×
NEW
466
                    let _ = write!(str_ref, "{value:max_digits$}");
×
467

×
468
                    let is_new_line = (i + 1) % grid.grid_shape().axis_size_x() == 0;
×
469
                    if is_new_line && i < last_value_index {
×
470
                        s.push(String::new());
×
471
                    } else {
×
472
                        str_ref.push(' ');
×
473
                    }
×
474
                }
475

476
                s
×
477
            } else {
478
                vec!["empty".to_string()]
×
479
            };
480

481
            fmt.field("grid", &grid);
×
482

×
483
            fmt.finish()
×
484
        }
×
485
    }
486

487
    DebugTile(raster_tile_2d)
×
488
}
×
489

490
#[cfg(test)]
491
mod tests {
492
    use crate::{primitives::Coordinate2D, util::test::TestDefault};
493

494
    use super::*;
495
    use crate::raster::GridIdx;
496

497
    #[test]
498
    fn tile_information_new() {
1✔
499
        let ti = TileInformation::new(
1✔
500
            GridIdx([0, 0]),
1✔
501
            GridShape2D::from([100, 100]),
1✔
502
            GeoTransform::test_default(),
1✔
503
        );
1✔
504
        assert_eq!(ti.global_geo_transform, GeoTransform::test_default());
1✔
505
        assert_eq!(ti.global_tile_position, GridIdx([0, 0]));
1✔
506
        assert_eq!(ti.tile_size_in_pixels, GridShape2D::from([100, 100]));
1✔
507
    }
1✔
508

509
    #[test]
510
    fn tile_information_global_tile_position() {
1✔
511
        let ti = TileInformation::new(
1✔
512
            GridIdx([0, 0]),
1✔
513
            GridShape2D::from([100, 100]),
1✔
514
            GeoTransform::test_default(),
1✔
515
        );
1✔
516
        assert_eq!(ti.global_tile_position(), GridIdx([0, 0]));
1✔
517
    }
1✔
518

519
    #[test]
520
    fn tile_information_local_upper_left() {
1✔
521
        let ti = TileInformation::new(
1✔
522
            GridIdx([0, 0]),
1✔
523
            GridShape2D::from([100, 100]),
1✔
524
            GeoTransform::test_default(),
1✔
525
        );
1✔
526
        assert_eq!(ti.local_upper_left_pixel_idx(), GridIdx([0, 0]));
1✔
527
    }
1✔
528

529
    #[test]
530
    fn tile_information_local_lower_left() {
1✔
531
        let ti = TileInformation::new(
1✔
532
            GridIdx([0, 0]),
1✔
533
            GridShape2D::from([100, 100]),
1✔
534
            GeoTransform::test_default(),
1✔
535
        );
1✔
536
        assert_eq!(ti.local_lower_left_pixel_idx(), GridIdx([99, 0]));
1✔
537
    }
1✔
538

539
    #[test]
540
    fn tile_information_local_upper_right() {
1✔
541
        let ti = TileInformation::new(
1✔
542
            GridIdx([0, 0]),
1✔
543
            GridShape2D::from([100, 100]),
1✔
544
            GeoTransform::test_default(),
1✔
545
        );
1✔
546
        assert_eq!(ti.local_upper_right_pixel_idx(), GridIdx([0, 99]));
1✔
547
    }
1✔
548

549
    #[test]
550
    fn tile_information_local_lower_right() {
1✔
551
        let ti = TileInformation::new(
1✔
552
            GridIdx([0, 0]),
1✔
553
            GridShape2D::from([100, 100]),
1✔
554
            GeoTransform::test_default(),
1✔
555
        );
1✔
556
        assert_eq!(ti.local_lower_right_pixel_idx(), GridIdx([99, 99]));
1✔
557
    }
1✔
558

559
    #[test]
560
    fn tile_information_global_upper_left_idx() {
1✔
561
        let ti = TileInformation::new(
1✔
562
            GridIdx([0, 0]),
1✔
563
            GridShape2D::from([100, 100]),
1✔
564
            GeoTransform::test_default(),
1✔
565
        );
1✔
566
        assert_eq!(ti.global_upper_left_pixel_idx(), GridIdx([0, 0]));
1✔
567
    }
1✔
568

569
    #[test]
570
    fn tile_information_global_upper_left_idx_2_3() {
1✔
571
        let ti = TileInformation::new(
1✔
572
            GridIdx([-2, 3]),
1✔
573
            GridShape2D::from([100, 1000]),
1✔
574
            GeoTransform::test_default(),
1✔
575
        );
1✔
576
        assert_eq!(ti.global_upper_left_pixel_idx(), GridIdx([-200, 3000]));
1✔
577
    }
1✔
578

579
    #[test]
580
    fn tile_information_global_upper_right_idx() {
1✔
581
        let ti = TileInformation::new(
1✔
582
            GridIdx([0, 0]),
1✔
583
            GridShape2D::from([100, 100]),
1✔
584
            GeoTransform::test_default(),
1✔
585
        );
1✔
586
        assert_eq!(ti.global_upper_right_pixel_idx(), GridIdx([0, 99]));
1✔
587
    }
1✔
588

589
    #[test]
590
    fn tile_information_global_upper_right_idx_2_3() {
1✔
591
        let ti = TileInformation::new(
1✔
592
            GridIdx([-2, 3]),
1✔
593
            GridShape2D::from([100, 1000]),
1✔
594
            GeoTransform::test_default(),
1✔
595
        );
1✔
596
        assert_eq!(ti.global_upper_right_pixel_idx(), GridIdx([-200, 3999]));
1✔
597
    }
1✔
598

599
    #[test]
600
    fn tile_information_global_lower_right_idx() {
1✔
601
        let ti = TileInformation::new(
1✔
602
            GridIdx([0, 0]),
1✔
603
            GridShape2D::from([100, 100]),
1✔
604
            GeoTransform::test_default(),
1✔
605
        );
1✔
606
        assert_eq!(ti.global_lower_right_pixel_idx(), GridIdx([99, 99]));
1✔
607
    }
1✔
608

609
    #[test]
610
    fn tile_information_global_lower_right_idx_2_3() {
1✔
611
        let ti = TileInformation::new(
1✔
612
            GridIdx([-2, 3]),
1✔
613
            GridShape2D::from([100, 1000]),
1✔
614
            GeoTransform::test_default(),
1✔
615
        );
1✔
616
        assert_eq!(ti.global_lower_right_pixel_idx(), GridIdx([-101, 3999]));
1✔
617
    }
1✔
618

619
    #[test]
620
    fn tile_information_global_lower_left_idx() {
1✔
621
        let ti = TileInformation::new(
1✔
622
            GridIdx([0, 0]),
1✔
623
            GridShape2D::from([100, 100]),
1✔
624
            GeoTransform::test_default(),
1✔
625
        );
1✔
626
        assert_eq!(ti.global_lower_left_pixel_idx(), GridIdx([99, 0]));
1✔
627
    }
1✔
628

629
    #[test]
630
    fn tile_information_global_lower_left_idx_2_3() {
1✔
631
        let ti = TileInformation::new(
1✔
632
            GridIdx([-2, 3]),
1✔
633
            GridShape2D::from([100, 1000]),
1✔
634
            GeoTransform::test_default(),
1✔
635
        );
1✔
636
        assert_eq!(ti.global_lower_left_pixel_idx(), GridIdx([-101, 3000]));
1✔
637
    }
1✔
638

639
    #[test]
640
    fn tile_information_local_to_global_idx_0_0() {
1✔
641
        let ti = TileInformation::new(
1✔
642
            GridIdx([0, 0]),
1✔
643
            GridShape2D::from([100, 100]),
1✔
644
            GeoTransform::test_default(),
1✔
645
        );
1✔
646
        assert_eq!(
1✔
647
            ti.local_to_global_pixel_idx(GridIdx([25, 75])),
1✔
648
            GridIdx([25, 75])
1✔
649
        );
1✔
650
    }
1✔
651

652
    #[test]
653
    fn tile_information_local_to_global_idx_2_3() {
1✔
654
        let ti = TileInformation::new(
1✔
655
            GridIdx([-2, 3]),
1✔
656
            GridShape2D::from([100, 1000]),
1✔
657
            GeoTransform::test_default(),
1✔
658
        );
1✔
659
        assert_eq!(
1✔
660
            ti.local_to_global_pixel_idx(GridIdx([25, 75])),
1✔
661
            GridIdx([-175, 3075])
1✔
662
        );
1✔
663
    }
1✔
664

665
    #[test]
666
    fn tile_information_spatial_partition() {
1✔
667
        let ti = TileInformation::new(
1✔
668
            GridIdx([-2, 3]),
1✔
669
            GridShape2D::from([100, 1000]),
1✔
670
            GeoTransform::test_default(),
1✔
671
        );
1✔
672
        assert_eq!(
1✔
673
            ti.spatial_partition(),
1✔
674
            SpatialPartition2D::new_unchecked(
1✔
675
                Coordinate2D::new(3000., 200.),
1✔
676
                Coordinate2D::new(4000., 100.)
1✔
677
            )
1✔
678
        );
1✔
679
    }
1✔
680

681
    #[test]
682
    fn tile_information_spatial_bounds_geotransform() {
1✔
683
        let ti = TileInformation::new(
1✔
684
            GridIdx([2, 3]),
1✔
685
            GridShape2D::from([10, 10]),
1✔
686
            GeoTransform::new_with_coordinate_x_y(-180., 0.1, 90., -0.1),
1✔
687
        );
1✔
688
        assert_eq!(
1✔
689
            ti.spatial_partition(),
1✔
690
            SpatialPartition2D::new_unchecked(
1✔
691
                Coordinate2D::new(-177., 88.),
1✔
692
                Coordinate2D::new(-176., 87.)
1✔
693
            )
1✔
694
        );
1✔
695
    }
1✔
696
}
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