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

geo-engine / geoengine / 29828895317

21 Jul 2026 12:08PM UTC coverage: 87.59% (-0.2%) from 87.839%
29828895317

Pull #1192

github

web-flow
Merge bd6b2979b into 1baac981e
Pull Request #1192: feat: add Gdal process pool

3028 of 3724 new or added lines in 38 files covered. (81.31%)

16 existing lines in 5 files now uncovered.

123314 of 140785 relevant lines covered (87.59%)

481313.41 hits per line

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

97.3
/geoengine/operators/src/source/gdal_source/reader.rs
1
use gdal::raster::GdalType;
2
use geoengine_datatypes::raster::{
3
    EmptyGrid, GridBlit, GridBoundingBox2D, GridOrEmpty, Pixel, RasterProperties,
4
};
5
use num::FromPrimitive;
6

7
use crate::source::gdal_worker_process::{
8
    GdalDatasetParameters, GdalPoolDispatcher, GdalProcessPoolError, GdalProcessReadResult,
9
    GridAndProperties, process_common::GdalReadAdvise,
10
};
11

12
pub struct GdalPoolReader(GdalPoolDispatcher);
13

14
impl From<GdalPoolDispatcher> for GdalPoolReader {
15
    fn from(value: GdalPoolDispatcher) -> Self {
1,767✔
16
        GdalPoolReader(value)
1,767✔
17
    }
1,767✔
18
}
19

20
impl GdalPoolReader {
21
    #[inline]
22
    fn worker_instance(&self) -> &GdalPoolDispatcher {
1,767✔
23
        &self.0
1,767✔
24
    }
1,767✔
25

26
    /// Loads a tile using the separate process and the provided parameters and read advise.
27
    /// This is the method where the source operator attaches to the process pool.
28
    /// The method sends a message to the process pool and waits for the response. The response is then converted to a `RasterTile2D` and returned.
29
    /// The method also handles the case where the file is not found and the `file_not_found_handling` is set to `NoData` by returning a tile filled with nodata values.
30
    /// # Errors
31
    /// Returns a `GdalSourceError` if the process returns an error, or if the response cannot be converted to a `RasterTile2D`.
32
    ///
33
    /// # Panics
34
    /// Panics if the response from the process is not in the expected format, or if the grid blitting fails (which should not happen if the bounds are correct).
35
    pub async fn load_tile_data_process<T: Pixel + GdalType + FromPrimitive>(
1,767✔
36
        &self,
1,767✔
37
        dataset_params: GdalDatasetParameters,
1,767✔
38
        read_advise: GdalReadAdvise,
1,767✔
39
    ) -> Result<GridAndProperties<T, GridBoundingBox2D>, GdalProcessPoolError> {
1,767✔
40
        let shared_reader = crate::source::gdal_worker_process::reader::GdalPoolReader::from(
1,767✔
41
            self.worker_instance().clone(),
1,767✔
42
        );
43

44
        let GridAndProperties { grid, properties } = match shared_reader
1,767✔
45
            .read_tile_data::<T>(dataset_params, read_advise)
1,767✔
46
            .await?
1,767✔
47
        {
48
            GdalProcessReadResult::Grid(grid_and_properties) => *grid_and_properties,
1,755✔
NEW
49
            GdalProcessReadResult::FileNotFoundAsNoData => GridAndProperties {
×
NEW
50
                grid: GridOrEmpty::new_empty_shape(read_advise.bounds_of_target),
×
NEW
51
                properties: RasterProperties::default(),
×
NEW
52
            },
×
53
        };
54

55
        // blit the grid to the tile bounds if necessary
56
        let grid = if read_advise.direct_read() {
1,755✔
57
            grid
1,137✔
58
        } else {
59
            let mut tile_raster = GridOrEmpty::from(EmptyGrid::new(read_advise.bounds_of_target));
618✔
60
            tile_raster.grid_blit_from(&grid);
618✔
61
            tile_raster
618✔
62
        };
63

64
        Ok(GridAndProperties { grid, properties })
1,755✔
65
    }
1,755✔
66
}
67

68
#[cfg(test)]
69
mod tests {
70
    use geoengine_datatypes::{
71
        primitives::{AxisAlignedRectangle, CacheHint, SpatialPartition2D, TimeInterval},
72
        raster::{
73
            ChangeGridBounds, GeoTransform, GridShape2D, GridSize, RasterPropertiesEntry,
74
            RasterPropertiesEntryType, RasterPropertiesKey, RasterTile2D, TileInformation,
75
        },
76
        test_data,
77
    };
78

79
    use crate::source::gdal_worker_process::{
80
        FileNotFoundHandling, GdalDatasetGeoTransform, GdalMetadataMapping, GdalProcessPool,
81
        GdalProcessPoolAccess, WorkerConfig, process_common::GdalReadWindow,
82
    };
83

84
    use super::*;
85

86
    // TODO (low): name / test
87
    async fn load_ndvi_jan_2014_by_process(
1✔
88
        gdal_read_advice: GdalReadAdvise,
1✔
89
        tile_information: TileInformation,
1✔
90
        gdal_worker: GdalPoolDispatcher,
1✔
91
    ) -> Result<RasterTile2D<u8>, GdalProcessPoolError> {
1✔
92
        let dataset_params = GdalDatasetParameters {
1✔
93
            file_path: test_data!("raster/modis_ndvi/MOD13A2_M_NDVI_2014-01-01.TIFF").into(),
1✔
94
            rasterband_channel: 1,
1✔
95
            geo_transform: GdalDatasetGeoTransform {
1✔
96
                origin_coordinate: (-180., 90.).into(),
1✔
97
                x_pixel_size: 0.1,
1✔
98
                y_pixel_size: -0.1,
1✔
99
            },
1✔
100
            width: 3600,
1✔
101
            height: 1800,
1✔
102
            file_not_found_handling: FileNotFoundHandling::NoData,
1✔
103
            no_data_value: Some(0.),
1✔
104
            properties_mapping: Some(vec![
1✔
105
                GdalMetadataMapping {
1✔
106
                    source_key: RasterPropertiesKey {
1✔
107
                        domain: None,
1✔
108
                        key: "AREA_OR_POINT".to_string(),
1✔
109
                    },
1✔
110
                    target_type: RasterPropertiesEntryType::String,
1✔
111
                    target_key: RasterPropertiesKey {
1✔
112
                        domain: None,
1✔
113
                        key: "AREA_OR_POINT".to_string(),
1✔
114
                    },
1✔
115
                },
1✔
116
                GdalMetadataMapping {
1✔
117
                    source_key: RasterPropertiesKey {
1✔
118
                        domain: Some("IMAGE_STRUCTURE".to_string()),
1✔
119
                        key: "COMPRESSION".to_string(),
1✔
120
                    },
1✔
121
                    target_type: RasterPropertiesEntryType::String,
1✔
122
                    target_key: RasterPropertiesKey {
1✔
123
                        domain: Some("IMAGE_STRUCTURE_INFO".to_string()),
1✔
124
                        key: "COMPRESSION".to_string(),
1✔
125
                    },
1✔
126
                },
1✔
127
            ]),
1✔
128
            gdal_open_options: None,
1✔
129
            gdal_config_options: None,
1✔
130
            allow_alphaband_as_mask: true,
1✔
131
            retry: None,
1✔
132
        };
1✔
133

134
        GdalPoolReader::from(gdal_worker)
1✔
135
            .load_tile_data_process::<u8>(dataset_params, gdal_read_advice)
1✔
136
            .await
1✔
137
            .map(|r| {
1✔
138
                RasterTile2D::new_with_tile_info_and_properties(
1✔
139
                    TimeInterval::default(),
1✔
140
                    tile_information,
1✔
141
                    0,
142
                    r.grid.unbounded(),
1✔
143
                    r.properties,
1✔
144
                    CacheHint::default(),
1✔
145
                )
146
            })
1✔
147
            .map_err(Into::into)
1✔
148
    }
1✔
149

150
    fn tile_information_with_partition_and_shape(
1✔
151
        partition: SpatialPartition2D,
1✔
152
        shape: GridShape2D,
1✔
153
    ) -> TileInformation {
1✔
154
        let real_geotransform = GeoTransform::new(
1✔
155
            partition.upper_left(),
1✔
156
            partition.size_x() / shape.axis_size_x() as f64,
1✔
157
            -partition.size_y() / shape.axis_size_y() as f64,
1✔
158
        );
159

160
        TileInformation {
1✔
161
            tile_size_in_pixels: shape,
1✔
162
            global_tile_position: [0, 0].into(),
1✔
163
            global_geo_transform: real_geotransform,
1✔
164
        }
1✔
165
    }
1✔
166

167
    #[tokio::test]
168
    async fn test_load_tile_data_process() {
1✔
169
        let output_shape: GridShape2D = [8, 8].into();
1✔
170
        let output_bounds =
1✔
171
            SpatialPartition2D::new_unchecked((-180., 90.).into(), (-179.2, 89.2).into());
1✔
172

173
        let gdal_read_advice = GdalReadAdvise {
1✔
174
            gdal_read_widow: GdalReadWindow::new([0, 0].into(), output_shape),
1✔
175
            read_window_bounds: GridBoundingBox2D::new([0, 0], [7, 7]).unwrap(),
1✔
176
            bounds_of_target: GridBoundingBox2D::new([0, 0], [7, 7]).unwrap(),
1✔
177
            flip_y: false,
1✔
178
        };
1✔
179

180
        let tile_information =
1✔
181
            tile_information_with_partition_and_shape(output_bounds, output_shape);
1✔
182

183
        let gpp = GdalProcessPool::new(2, 2, 2, WorkerConfig::default());
1✔
184
        let gw = gpp.get_gdal_worker();
1✔
185

186
        let RasterTile2D {
1✔
187
            global_geo_transform: _,
1✔
188
            grid_array: grid,
1✔
189
            tile_position: _,
1✔
190
            band: _,
1✔
191
            time: _,
1✔
192
            properties,
1✔
193
            cache_hint: _,
1✔
194
        } = load_ndvi_jan_2014_by_process(gdal_read_advice, tile_information, gw)
1✔
195
            .await
1✔
196
            .unwrap();
1✔
197

198
        assert!(!grid.is_empty());
1✔
199

200
        let grid = grid.into_materialized_masked_grid();
1✔
201

202
        assert_eq!(grid.inner_grid.data.len(), 64);
1✔
203
        assert_eq!(
1✔
204
            grid.inner_grid.data,
205
            &[
206
                255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
207
                255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
208
                255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
209
                255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
210
            ]
211
        );
212

213
        assert_eq!(grid.validity_mask.data.len(), 64);
1✔
214
        assert_eq!(grid.validity_mask.data, &[true; 64]);
1✔
215

216
        assert!((properties.scale_option()).is_none());
1✔
217
        assert!(properties.offset_option().is_none());
1✔
218
        assert_eq!(
1✔
219
            properties.get_property(&RasterPropertiesKey {
1✔
220
                domain: Some("IMAGE_STRUCTURE_INFO".to_string()),
1✔
221
                key: "COMPRESSION".to_string(),
1✔
222
            }),
1✔
223
            Some(&RasterPropertiesEntry::String("LZW".to_string()))
1✔
224
        );
225
        assert_eq!(
1✔
226
            properties.get_property(&RasterPropertiesKey {
1✔
227
                domain: None,
1✔
228
                key: "AREA_OR_POINT".to_string(),
1✔
229
            }),
1✔
230
            Some(&RasterPropertiesEntry::String("Area".to_string()))
1✔
231
        );
1✔
232
    }
1✔
233
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc