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

geo-engine / geoengine / 10178074589

31 Jul 2024 09:34AM UTC coverage: 91.068% (+0.4%) from 90.682%
10178074589

push

github

web-flow
Merge pull request #973 from geo-engine/remove-XGB-update-toolchain

Remove-XGB-update-toolchain

81 of 88 new or added lines in 29 files covered. (92.05%)

456 existing lines in 119 files now uncovered.

131088 of 143945 relevant lines covered (91.07%)

53581.03 hits per line

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

99.19
/operators/src/processing/expression/raster_operator.rs
1
use super::{
2
    get_expression_dependencies,
3
    raster_query_processor::{ExpressionInput, ExpressionQueryProcessor},
4
    RasterExpressionError,
5
};
6
use crate::{
7
    engine::{
8
        CanonicOperatorName, InitializedRasterOperator, InitializedSources, Operator, OperatorName,
9
        RasterBandDescriptor, RasterBandDescriptors, RasterOperator, RasterQueryProcessor,
10
        RasterResultDescriptor, SingleRasterSource, TypedRasterQueryProcessor,
11
        WorkflowOperatorPath,
12
    },
13
    error::InvalidNumberOfExpressionInputBands,
14
    util::Result,
15
};
16
use async_trait::async_trait;
17
use geoengine_datatypes::raster::RasterDataType;
18
use geoengine_expression::{
19
    DataType, ExpressionAst, ExpressionParser, LinkedExpression, Parameter,
20
};
21
use serde::{Deserialize, Serialize};
22
use snafu::ensure;
23

24
/// Parameters for the `Expression` operator.
25
/// * The `expression` must only contain simple arithmetic
26
///     calculations.
27
/// * `output_type` is the data type of the produced raster tiles.
28
/// * `output_no_data_value` is the no data value of the output raster
29
/// * `output_measurement` is the measurement description of the output
30
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
5✔
31
#[serde(rename_all = "camelCase")]
32
pub struct ExpressionParams {
33
    pub expression: String,
34
    pub output_type: RasterDataType,
35
    pub output_band: Option<RasterBandDescriptor>,
36
    pub map_no_data: bool,
37
}
38
/// The `Expression` operator calculates an expression for all pixels of the input rasters bands and
39
/// produces raster tiles of a given output type
40
pub type Expression = Operator<ExpressionParams, SingleRasterSource>;
41

42
/// Create a parameter name from an index.
43
/// Starts with `A`.
44
///
45
/// ## Note
46
///
47
/// This function only makes sense for indices between 0 and 25.
48
///
49
fn index_to_parameter(index: usize) -> String {
31✔
50
    let index = index as u32;
31✔
51
    let start_index = 'A' as u32;
31✔
52

31✔
53
    let parameter = char::from_u32(start_index + index).unwrap_or_default();
31✔
54

31✔
55
    parameter.to_string()
31✔
56
}
31✔
57

58
#[typetag::serde]
×
59
#[async_trait]
60
impl RasterOperator for Expression {
61
    async fn _initialize(
62
        self: Box<Self>,
63
        path: WorkflowOperatorPath,
64
        context: &dyn crate::engine::ExecutionContext,
65
    ) -> Result<Box<dyn InitializedRasterOperator>> {
15✔
66
        let name = CanonicOperatorName::from(&self);
15✔
67

15✔
68
        let source = self.sources.initialize_sources(path, context).await?.raster;
137✔
69

15✔
70
        let in_descriptor = source.result_descriptor();
15✔
71

15✔
72
        ensure!(
15✔
73
            !in_descriptor.bands.is_empty() && in_descriptor.bands.len() <= 8,
15✔
74
            InvalidNumberOfExpressionInputBands {
15✔
75
                found: in_descriptor.bands.len()
×
76
            }
×
77
        );
15✔
78

15✔
79
        // we refer to raster bands by A, B, C, …
15✔
80
        let parameters = (0..in_descriptor.bands.len())
15✔
81
            .map(|i| {
31✔
82
                let parameter = index_to_parameter(i);
31✔
83
                Parameter::Number(parameter.into())
31✔
84
            })
31✔
85
            .collect::<Vec<_>>();
15✔
86

15✔
87
        let expression = ExpressionParser::new(&parameters, DataType::Number)
15✔
88
            .map_err(RasterExpressionError::from)?
15✔
89
            .parse(
15✔
90
                self.params
15✔
91
                    .output_band
15✔
92
                    .as_ref()
15✔
93
                    .map_or("expression", |b| &b.name),
15✔
94
                &self.params.expression,
15✔
95
            )
15✔
96
            .map_err(RasterExpressionError::from)?;
15✔
97

15✔
98
        let result_descriptor = RasterResultDescriptor {
15✔
99
            data_type: self.params.output_type,
15✔
100
            spatial_reference: in_descriptor.spatial_reference,
15✔
101
            time: in_descriptor.time,
15✔
102
            bbox: in_descriptor.bbox,
15✔
103
            resolution: in_descriptor.resolution,
15✔
104
            bands: RasterBandDescriptors::new(vec![self
15✔
105
                .params
15✔
106
                .output_band
15✔
107
                .unwrap_or(RasterBandDescriptor::new_unitless("expression".into()))])?,
15✔
108
        };
15✔
109

15✔
110
        let initialized_operator = InitializedExpression {
15✔
111
            name,
15✔
112
            result_descriptor,
15✔
113
            source,
15✔
114
            expression,
15✔
115
            map_no_data: self.params.map_no_data,
15✔
116
        };
15✔
117

15✔
118
        Ok(initialized_operator.boxed())
15✔
119
    }
15✔
120

121
    span_fn!(Expression);
122
}
123

124
impl OperatorName for Expression {
125
    const TYPE_NAME: &'static str = "Expression";
126
}
127

128
pub struct InitializedExpression {
129
    name: CanonicOperatorName,
130
    result_descriptor: RasterResultDescriptor,
131
    source: Box<dyn InitializedRasterOperator>,
132
    expression: ExpressionAst,
133
    map_no_data: bool,
134
}
135

136
/// Macro for generating the match cases for number of bands to the `ExpressionInput` struct.
137
macro_rules! generate_match_cases {
138
    ($num_bands:expr, $output_type:expr, $expression:expr, $source_processor:expr, $result_descriptor:expr, $map_no_data:expr, $($x:expr),*) => {
139
        match $num_bands {
140
            $(
141
                $x => call_generic_raster_processor!(
142
                    $output_type,
143
                    ExpressionQueryProcessor::new(
144
                        $expression,
145
                        ExpressionInput::<$x> {
146
                            raster: $source_processor,
147
                        },
148
                        $result_descriptor,
149
                        $map_no_data.clone(),
150
                    )
151
                    .boxed()
152
                ),
153
            )*
154
            _ => unreachable!("number of bands was checked to be between 1 and 8"),
155
        }
156
    };
157
}
158

159
impl InitializedRasterOperator for InitializedExpression {
160
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor> {
15✔
161
        let output_type = self.result_descriptor().data_type;
15✔
162

163
        // TODO: spawn a blocking task for the compilation process
164
        let expression_dependencies = get_expression_dependencies()
15✔
165
            .map_err(|source| RasterExpressionError::Dependencies { source })?;
15✔
166
        let expression = LinkedExpression::new(
15✔
167
            self.expression.name(),
15✔
168
            &self.expression.code(),
15✔
169
            expression_dependencies,
15✔
170
        )
15✔
171
        .map_err(RasterExpressionError::from)?;
15✔
172

173
        let source_processor = self.source.query_processor()?.into_f64();
15✔
174

UNCOV
175
        Ok(generate_match_cases!(
×
176
            self.source.result_descriptor().bands.len(),
15✔
177
            output_type,
7✔
178
            expression,
3✔
179
            source_processor,
3✔
180
            self.result_descriptor.clone(),
3✔
181
            self.map_no_data,
3✔
182
            1,
183
            2,
184
            3,
185
            4,
186
            5,
187
            6,
188
            7,
189
            8
190
        ))
191
    }
15✔
192

193
    fn result_descriptor(&self) -> &RasterResultDescriptor {
20✔
194
        &self.result_descriptor
20✔
195
    }
20✔
196

197
    fn canonic_name(&self) -> CanonicOperatorName {
×
198
        self.name.clone()
×
199
    }
×
200
}
201

202
#[cfg(test)]
203
mod tests {
204
    use super::*;
205
    use crate::engine::{
206
        MockExecutionContext, MockQueryContext, MultipleRasterSources, QueryProcessor,
207
    };
208
    use crate::mock::{MockRasterSource, MockRasterSourceParams};
209
    use crate::processing::{RasterStacker, RasterStackerParams};
210
    use futures::StreamExt;
211
    use geoengine_datatypes::primitives::{BandSelection, CacheHint, CacheTtlSeconds};
212
    use geoengine_datatypes::primitives::{
213
        RasterQueryRectangle, SpatialPartition2D, SpatialResolution, TimeInterval,
214
    };
215
    use geoengine_datatypes::raster::{
216
        Grid2D, GridOrEmpty, MapElements, MaskedGrid2D, RasterTile2D, RenameBands, TileInformation,
217
        TilingSpecification,
218
    };
219
    use geoengine_datatypes::spatial_reference::SpatialReference;
220
    use geoengine_datatypes::util::test::TestDefault;
221

222
    #[test]
223
    fn deserialize_params() {
1✔
224
        let s =
1✔
225
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
226

1✔
227
        assert_eq!(
1✔
228
            serde_json::from_str::<ExpressionParams>(s).unwrap(),
1✔
229
            ExpressionParams {
1✔
230
                expression: "1*A".to_owned(),
1✔
231
                output_type: RasterDataType::F64,
1✔
232
                output_band: None,
1✔
233
                map_no_data: false,
1✔
234
            }
1✔
235
        );
1✔
236
    }
1✔
237

238
    #[test]
239
    fn serialize_params() {
1✔
240
        let s = r#"{"expression":"1*A","outputType":"F64","outputBand":null,"mapNoData":false}"#;
1✔
241

1✔
242
        assert_eq!(
1✔
243
            s,
1✔
244
            serde_json::to_string(&ExpressionParams {
1✔
245
                expression: "1*A".to_owned(),
1✔
246
                output_type: RasterDataType::F64,
1✔
247
                output_band: None,
1✔
248
                map_no_data: false,
1✔
249
            })
1✔
250
            .unwrap()
1✔
251
        );
1✔
252
    }
1✔
253

254
    #[test]
255
    fn serialize_params_no_data() {
1✔
256
        let s = r#"{"expression":"1*A","outputType":"F64","outputBand":null,"mapNoData":false}"#;
1✔
257

1✔
258
        assert_eq!(
1✔
259
            s,
1✔
260
            serde_json::to_string(&ExpressionParams {
1✔
261
                expression: "1*A".to_owned(),
1✔
262
                output_type: RasterDataType::F64,
1✔
263
                output_band: None,
1✔
264
                map_no_data: false,
1✔
265
            })
1✔
266
            .unwrap()
1✔
267
        );
1✔
268
    }
1✔
269

270
    #[tokio::test]
271
    async fn basic_unary() {
1✔
272
        let tile_size_in_pixels = [3, 2].into();
1✔
273
        let tiling_specification = TilingSpecification {
1✔
274
            origin_coordinate: [0.0, 0.0].into(),
1✔
275
            tile_size_in_pixels,
1✔
276
        };
1✔
277

1✔
278
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
279

1✔
280
        let raster_a = make_raster(Some(3));
1✔
281

1✔
282
        let o = Expression {
1✔
283
            params: ExpressionParams {
1✔
284
                expression: "2 * A".to_string(),
1✔
285
                output_type: RasterDataType::I8,
1✔
286
                output_band: None,
1✔
287
                map_no_data: false,
1✔
288
            },
1✔
289
            sources: SingleRasterSource { raster: raster_a },
1✔
290
        }
1✔
291
        .boxed()
1✔
292
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
293
        .await
1✔
294
        .unwrap();
1✔
295

1✔
296
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
297

1✔
298
        let ctx = MockQueryContext::new(1.into());
1✔
299
        let result_stream = processor
1✔
300
            .query(
1✔
301
                RasterQueryRectangle {
1✔
302
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
303
                        (0., 3.).into(),
1✔
304
                        (2., 0.).into(),
1✔
305
                    ),
1✔
306
                    time_interval: Default::default(),
1✔
307
                    spatial_resolution: SpatialResolution::one(),
1✔
308
                    attributes: BandSelection::first(),
1✔
309
                },
1✔
310
                &ctx,
1✔
311
            )
1✔
312
            .await
1✔
313
            .unwrap();
1✔
314

1✔
315
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
2✔
316

1✔
317
        assert_eq!(result.len(), 1);
1✔
318

1✔
319
        assert_eq!(
1✔
320
            result[0].as_ref().unwrap().grid_array,
1✔
321
            GridOrEmpty::from(
1✔
322
                MaskedGrid2D::new(
1✔
323
                    Grid2D::new([3, 2].into(), vec![2, 4, 0, 8, 10, 12],).unwrap(),
1✔
324
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
325
                )
1✔
326
                .unwrap()
1✔
327
            )
1✔
328
        );
1✔
329
    }
1✔
330

331
    #[tokio::test]
332
    async fn unary_map_no_data() {
1✔
333
        let tile_size_in_pixels = [3, 2].into();
1✔
334
        let tiling_specification = TilingSpecification {
1✔
335
            origin_coordinate: [0.0, 0.0].into(),
1✔
336
            tile_size_in_pixels,
1✔
337
        };
1✔
338

1✔
339
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
340

1✔
341
        let raster_a = make_raster(Some(3));
1✔
342

1✔
343
        let o = Expression {
1✔
344
            params: ExpressionParams {
1✔
345
                expression: "2 * A".to_string(),
1✔
346
                output_type: RasterDataType::I8,
1✔
347
                output_band: None,
1✔
348
                map_no_data: true,
1✔
349
            },
1✔
350
            sources: SingleRasterSource { raster: raster_a },
1✔
351
        }
1✔
352
        .boxed()
1✔
353
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
354
        .await
1✔
355
        .unwrap();
1✔
356

1✔
357
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
358

1✔
359
        let ctx = MockQueryContext::new(1.into());
1✔
360
        let result_stream = processor
1✔
361
            .query(
1✔
362
                RasterQueryRectangle {
1✔
363
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
364
                        (0., 3.).into(),
1✔
365
                        (2., 0.).into(),
1✔
366
                    ),
1✔
367
                    time_interval: Default::default(),
1✔
368
                    spatial_resolution: SpatialResolution::one(),
1✔
369
                    attributes: BandSelection::first(),
1✔
370
                },
1✔
371
                &ctx,
1✔
372
            )
1✔
373
            .await
1✔
374
            .unwrap();
1✔
375

1✔
376
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
2✔
377

1✔
378
        assert_eq!(result.len(), 1);
1✔
379

1✔
380
        assert_eq!(
1✔
381
            result[0].as_ref().unwrap().grid_array,
1✔
382
            GridOrEmpty::from(
1✔
383
                MaskedGrid2D::new(
1✔
384
                    Grid2D::new([3, 2].into(), vec![2, 4, 0, 8, 10, 12],).unwrap(), // pixels with no data are turned to Default::default() wich is 0. And 0 is the out_no_data value.
1✔
385
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
386
                )
1✔
387
                .unwrap()
1✔
388
            )
1✔
389
        );
1✔
390
    }
1✔
391

392
    #[tokio::test]
393
    async fn basic_binary() {
1✔
394
        let tile_size_in_pixels = [3, 2].into();
1✔
395
        let tiling_specification = TilingSpecification {
1✔
396
            origin_coordinate: [0.0, 0.0].into(),
1✔
397
            tile_size_in_pixels,
1✔
398
        };
1✔
399

1✔
400
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
401

1✔
402
        let raster_a = make_raster(None);
1✔
403
        let raster_b = make_raster(None);
1✔
404

1✔
405
        let o = Expression {
1✔
406
            params: ExpressionParams {
1✔
407
                expression: "A+B".to_string(),
1✔
408
                output_type: RasterDataType::I8,
1✔
409
                output_band: None,
1✔
410
                map_no_data: false,
1✔
411
            },
1✔
412
            sources: SingleRasterSource {
1✔
413
                raster: RasterStacker {
1✔
414
                    params: RasterStackerParams {
1✔
415
                        rename_bands: RenameBands::Default,
1✔
416
                    },
1✔
417
                    sources: MultipleRasterSources {
1✔
418
                        rasters: vec![raster_a, raster_b],
1✔
419
                    },
1✔
420
                }
1✔
421
                .boxed(),
1✔
422
            },
1✔
423
        }
1✔
424
        .boxed()
1✔
425
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
426
        .await
1✔
427
        .unwrap();
1✔
428

1✔
429
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
430

1✔
431
        let ctx = MockQueryContext::new(1.into());
1✔
432
        let result_stream = processor
1✔
433
            .query(
1✔
434
                RasterQueryRectangle {
1✔
435
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
436
                        (0., 3.).into(),
1✔
437
                        (2., 0.).into(),
1✔
438
                    ),
1✔
439
                    time_interval: Default::default(),
1✔
440
                    spatial_resolution: SpatialResolution::one(),
1✔
441
                    attributes: BandSelection::first(),
1✔
442
                },
1✔
443
                &ctx,
1✔
444
            )
1✔
445
            .await
1✔
446
            .unwrap();
1✔
447

1✔
448
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
3✔
449

1✔
450
        assert_eq!(result.len(), 1);
1✔
451

1✔
452
        assert_eq!(
1✔
453
            result[0].as_ref().unwrap().grid_array,
1✔
454
            Grid2D::new([3, 2].into(), vec![2, 4, 6, 8, 10, 12],)
1✔
455
                .unwrap()
1✔
456
                .into()
1✔
457
        );
1✔
458
    }
1✔
459

460
    #[tokio::test]
461
    async fn basic_coalesce() {
1✔
462
        let tile_size_in_pixels = [3, 2].into();
1✔
463
        let tiling_specification = TilingSpecification {
1✔
464
            origin_coordinate: [0.0, 0.0].into(),
1✔
465
            tile_size_in_pixels,
1✔
466
        };
1✔
467

1✔
468
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
469

1✔
470
        let raster_a = make_raster(Some(3));
1✔
471
        let raster_b = make_raster(None);
1✔
472

1✔
473
        let o = Expression {
1✔
474
            params: ExpressionParams {
1✔
475
                expression: "if A IS NODATA {
1✔
476
                       B * 2
1✔
477
                   } else if A == 6 {
1✔
478
                       NODATA
1✔
479
                   } else {
1✔
480
                       A
1✔
481
                   }"
1✔
482
                .to_string(),
1✔
483
                output_type: RasterDataType::I8,
1✔
484
                output_band: None,
1✔
485
                map_no_data: true,
1✔
486
            },
1✔
487
            sources: SingleRasterSource {
1✔
488
                raster: RasterStacker {
1✔
489
                    params: RasterStackerParams {
1✔
490
                        rename_bands: RenameBands::Default,
1✔
491
                    },
1✔
492
                    sources: MultipleRasterSources {
1✔
493
                        rasters: vec![raster_a, raster_b],
1✔
494
                    },
1✔
495
                }
1✔
496
                .boxed(),
1✔
497
            },
1✔
498
        }
1✔
499
        .boxed()
1✔
500
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
501
        .await
1✔
502
        .unwrap();
1✔
503

1✔
504
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
505

1✔
506
        let ctx = MockQueryContext::new(1.into());
1✔
507
        let result_stream = processor
1✔
508
            .query(
1✔
509
                RasterQueryRectangle {
1✔
510
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
511
                        (0., 3.).into(),
1✔
512
                        (2., 0.).into(),
1✔
513
                    ),
1✔
514
                    time_interval: Default::default(),
1✔
515
                    spatial_resolution: SpatialResolution::one(),
1✔
516
                    attributes: BandSelection::first(),
1✔
517
                },
1✔
518
                &ctx,
1✔
519
            )
1✔
520
            .await
1✔
521
            .unwrap();
1✔
522

1✔
523
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
3✔
524

1✔
525
        assert_eq!(result.len(), 1);
1✔
526

1✔
527
        assert_eq!(
1✔
528
            result[0].as_ref().unwrap().grid_array,
1✔
529
            GridOrEmpty::from(
1✔
530
                MaskedGrid2D::new(
1✔
531
                    Grid2D::new([3, 2].into(), vec![1, 2, 6, 4, 5, 0],).unwrap(),
1✔
532
                    Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false],).unwrap()
1✔
533
                )
1✔
534
                .unwrap()
1✔
535
            )
1✔
536
        );
1✔
537
    }
1✔
538

539
    #[tokio::test]
540
    async fn basic_ternary() {
1✔
541
        let no_data_value = 3;
1✔
542
        let no_data_value_option = Some(no_data_value);
1✔
543

1✔
544
        let tile_size_in_pixels = [3, 2].into();
1✔
545
        let tiling_specification = TilingSpecification {
1✔
546
            origin_coordinate: [0.0, 0.0].into(),
1✔
547
            tile_size_in_pixels,
1✔
548
        };
1✔
549

1✔
550
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
551

1✔
552
        let raster_a = make_raster(no_data_value_option);
1✔
553
        let raster_b = make_raster(no_data_value_option);
1✔
554
        let raster_c = make_raster(no_data_value_option);
1✔
555

1✔
556
        let o = Expression {
1✔
557
            params: ExpressionParams {
1✔
558
                expression: "A+B+C".to_string(),
1✔
559
                output_type: RasterDataType::I8,
1✔
560
                output_band: None,
1✔
561
                map_no_data: false,
1✔
562
            },
1✔
563
            sources: SingleRasterSource {
1✔
564
                raster: RasterStacker {
1✔
565
                    params: RasterStackerParams {
1✔
566
                        rename_bands: RenameBands::Default,
1✔
567
                    },
1✔
568
                    sources: MultipleRasterSources {
1✔
569
                        rasters: vec![raster_a, raster_b, raster_c],
1✔
570
                    },
1✔
571
                }
1✔
572
                .boxed(),
1✔
573
            },
1✔
574
        }
1✔
575
        .boxed()
1✔
576
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
577
        .await
1✔
578
        .unwrap();
1✔
579

1✔
580
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
581

1✔
582
        let ctx = MockQueryContext::new(1.into());
1✔
583
        let result_stream = processor
1✔
584
            .query(
1✔
585
                RasterQueryRectangle {
1✔
586
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
587
                        (0., 3.).into(),
1✔
588
                        (2., 0.).into(),
1✔
589
                    ),
1✔
590
                    time_interval: Default::default(),
1✔
591
                    spatial_resolution: SpatialResolution::one(),
1✔
592
                    attributes: BandSelection::first(),
1✔
593
                },
1✔
594
                &ctx,
1✔
595
            )
1✔
596
            .await
1✔
597
            .unwrap();
1✔
598

1✔
599
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
4✔
600

1✔
601
        assert_eq!(result.len(), 1);
1✔
602

1✔
603
        let first_result = result[0].as_ref().unwrap();
1✔
604

1✔
605
        assert!(!first_result.is_empty());
1✔
606

1✔
607
        let grid = match &first_result.grid_array {
1✔
608
            GridOrEmpty::Grid(g) => g,
1✔
609
            GridOrEmpty::Empty(_) => panic!(),
1✔
610
        };
1✔
611

1✔
612
        let res: Vec<Option<i8>> = grid.masked_element_deref_iterator().collect();
1✔
613

1✔
614
        assert_eq!(
1✔
615
            res,
1✔
616
            [Some(3), Some(6), None, Some(12), Some(15), Some(18)] // third is None is because all inputs are masked because 3 == no_data_value
1✔
617
        );
1✔
618
    }
1✔
619

620
    #[tokio::test]
621
    async fn octave_inputs() {
1✔
622
        let no_data_value = 0;
1✔
623
        let no_data_value_option = Some(no_data_value);
1✔
624

1✔
625
        let tile_size_in_pixels = [3, 2].into();
1✔
626
        let tiling_specification = TilingSpecification {
1✔
627
            origin_coordinate: [0.0, 0.0].into(),
1✔
628
            tile_size_in_pixels,
1✔
629
        };
1✔
630

1✔
631
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
632

1✔
633
        let raster_a = make_raster(no_data_value_option);
1✔
634
        let raster_b = make_raster(no_data_value_option);
1✔
635
        let raster_c = make_raster(no_data_value_option);
1✔
636
        let raster_d = make_raster(no_data_value_option);
1✔
637
        let raster_e = make_raster(no_data_value_option);
1✔
638
        let raster_f = make_raster(no_data_value_option);
1✔
639
        let raster_g = make_raster(no_data_value_option);
1✔
640
        let raster_h = make_raster(no_data_value_option);
1✔
641

1✔
642
        let o = Expression {
1✔
643
            params: ExpressionParams {
1✔
644
                expression: "A+B+C+D+E+F+G+H".to_string(),
1✔
645
                output_type: RasterDataType::I8,
1✔
646
                output_band: None,
1✔
647
                map_no_data: false,
1✔
648
            },
1✔
649
            sources: SingleRasterSource {
1✔
650
                raster: RasterStacker {
1✔
651
                    params: RasterStackerParams {
1✔
652
                        rename_bands: RenameBands::Default,
1✔
653
                    },
1✔
654
                    sources: MultipleRasterSources {
1✔
655
                        rasters: vec![
1✔
656
                            raster_a, raster_b, raster_c, raster_d, raster_e, raster_f, raster_g,
1✔
657
                            raster_h,
1✔
658
                        ],
1✔
659
                    },
1✔
660
                }
1✔
661
                .boxed(),
1✔
662
            },
1✔
663
        }
1✔
664
        .boxed()
1✔
665
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
666
        .await
1✔
667
        .unwrap();
1✔
668

1✔
669
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
670

1✔
671
        let ctx = MockQueryContext::new(1.into());
1✔
672
        let result_stream = processor
1✔
673
            .query(
1✔
674
                RasterQueryRectangle {
1✔
675
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
676
                        (0., 3.).into(),
1✔
677
                        (2., 0.).into(),
1✔
678
                    ),
1✔
679
                    time_interval: Default::default(),
1✔
680
                    spatial_resolution: SpatialResolution::one(),
1✔
681
                    attributes: BandSelection::first(),
1✔
682
                },
1✔
683
                &ctx,
1✔
684
            )
1✔
685
            .await
1✔
686
            .unwrap();
1✔
687

1✔
688
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
9✔
689

1✔
690
        assert_eq!(result.len(), 1);
1✔
691

1✔
692
        assert_eq!(
1✔
693
            result[0].as_ref().unwrap().grid_array,
1✔
694
            Grid2D::new([3, 2].into(), vec![8, 16, 24, 32, 40, 48],)
1✔
695
                .unwrap()
1✔
696
                .into()
1✔
697
        );
1✔
698
    }
1✔
699

700
    #[tokio::test]
701
    async fn test_functions() {
1✔
702
        let no_data_value = 0;
1✔
703
        let tile_size_in_pixels = [3, 2].into();
1✔
704
        let tiling_specification = TilingSpecification {
1✔
705
            origin_coordinate: [0.0, 0.0].into(),
1✔
706
            tile_size_in_pixels,
1✔
707
        };
1✔
708

1✔
709
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
710

1✔
711
        let raster_a = make_raster(Some(no_data_value));
1✔
712

1✔
713
        let o = Expression {
1✔
714
            params: ExpressionParams {
1✔
715
                expression: "min(A * pi(), 10)".to_string(),
1✔
716
                output_type: RasterDataType::I8,
1✔
717
                output_band: None,
1✔
718
                map_no_data: false,
1✔
719
            },
1✔
720
            sources: SingleRasterSource {
1✔
721
                raster: RasterStacker {
1✔
722
                    params: RasterStackerParams {
1✔
723
                        rename_bands: RenameBands::Default,
1✔
724
                    },
1✔
725
                    sources: MultipleRasterSources {
1✔
726
                        rasters: vec![raster_a],
1✔
727
                    },
1✔
728
                }
1✔
729
                .boxed(),
1✔
730
            },
1✔
731
        }
1✔
732
        .boxed()
1✔
733
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
734
        .await
1✔
735
        .unwrap();
1✔
736

1✔
737
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
738

1✔
739
        let ctx = MockQueryContext::new(1.into());
1✔
740
        let result_stream = processor
1✔
741
            .query(
1✔
742
                RasterQueryRectangle {
1✔
743
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
744
                        (0., 3.).into(),
1✔
745
                        (2., 0.).into(),
1✔
746
                    ),
1✔
747
                    time_interval: Default::default(),
1✔
748
                    spatial_resolution: SpatialResolution::one(),
1✔
749
                    attributes: BandSelection::first(),
1✔
750
                },
1✔
751
                &ctx,
1✔
752
            )
1✔
753
            .await
1✔
754
            .unwrap();
1✔
755

1✔
756
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
2✔
757

1✔
758
        assert_eq!(result.len(), 1);
1✔
759

1✔
760
        assert_eq!(
1✔
761
            result[0].as_ref().unwrap().grid_array,
1✔
762
            Grid2D::new([3, 2].into(), vec![3, 6, 9, 10, 10, 10],)
1✔
763
                .unwrap()
1✔
764
                .into()
1✔
765
        );
1✔
766
    }
1✔
767

768
    fn make_raster(no_data_value: Option<i8>) -> Box<dyn RasterOperator> {
18✔
769
        make_raster_with_cache_hint(no_data_value, CacheHint::no_cache())
18✔
770
    }
18✔
771

772
    fn make_raster_with_cache_hint(
24✔
773
        no_data_value: Option<i8>,
24✔
774
        cache_hint: CacheHint,
24✔
775
    ) -> Box<dyn RasterOperator> {
24✔
776
        let raster = Grid2D::<i8>::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6]).unwrap();
24✔
777

778
        let real_raster = if let Some(no_data_value) = no_data_value {
24✔
779
            MaskedGrid2D::from(raster)
21✔
780
                .map_elements(|e: Option<i8>| e.filter(|v| *v != no_data_value))
126✔
781
                .into()
21✔
782
        } else {
783
            GridOrEmpty::from(raster)
3✔
784
        };
785

786
        let raster_tile = RasterTile2D::new_with_tile_info(
24✔
787
            TimeInterval::default(),
24✔
788
            TileInformation {
24✔
789
                global_tile_position: [-1, 0].into(),
24✔
790
                tile_size_in_pixels: [3, 2].into(),
24✔
791
                global_geo_transform: TestDefault::test_default(),
24✔
792
            },
24✔
793
            0,
24✔
794
            real_raster,
24✔
795
            cache_hint,
24✔
796
        );
24✔
797

24✔
798
        MockRasterSource {
24✔
799
            params: MockRasterSourceParams {
24✔
800
                data: vec![raster_tile],
24✔
801
                result_descriptor: RasterResultDescriptor {
24✔
802
                    data_type: RasterDataType::I8,
24✔
803
                    spatial_reference: SpatialReference::epsg_4326().into(),
24✔
804
                    time: None,
24✔
805
                    bbox: None,
24✔
806
                    resolution: None,
24✔
807
                    bands: RasterBandDescriptors::new_single_band(),
24✔
808
                },
24✔
809
            },
24✔
810
        }
24✔
811
        .boxed()
24✔
812
    }
24✔
813

814
    #[tokio::test]
815
    async fn it_attaches_cache_hint_1() {
1✔
816
        let no_data_value = 0;
1✔
817
        let tile_size_in_pixels = [3, 2].into();
1✔
818
        let tiling_specification = TilingSpecification {
1✔
819
            origin_coordinate: [0.0, 0.0].into(),
1✔
820
            tile_size_in_pixels,
1✔
821
        };
1✔
822

1✔
823
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
824

1✔
825
        let cache_hint = CacheHint::seconds(1234);
1✔
826
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint);
1✔
827

1✔
828
        let o = Expression {
1✔
829
            params: ExpressionParams {
1✔
830
                expression: "min(A * pi(), 10)".to_string(),
1✔
831
                output_type: RasterDataType::I8,
1✔
832
                output_band: None,
1✔
833
                map_no_data: false,
1✔
834
            },
1✔
835
            sources: SingleRasterSource {
1✔
836
                raster: RasterStacker {
1✔
837
                    params: RasterStackerParams {
1✔
838
                        rename_bands: RenameBands::Default,
1✔
839
                    },
1✔
840
                    sources: MultipleRasterSources {
1✔
841
                        rasters: vec![raster_a],
1✔
842
                    },
1✔
843
                }
1✔
844
                .boxed(),
1✔
845
            },
1✔
846
        }
1✔
847
        .boxed()
1✔
848
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
849
        .await
1✔
850
        .unwrap();
1✔
851

1✔
852
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
853

1✔
854
        let ctx = MockQueryContext::new(1.into());
1✔
855
        let result_stream = processor
1✔
856
            .query(
1✔
857
                RasterQueryRectangle {
1✔
858
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
859
                        (0., 3.).into(),
1✔
860
                        (2., 0.).into(),
1✔
861
                    ),
1✔
862
                    time_interval: Default::default(),
1✔
863
                    spatial_resolution: SpatialResolution::one(),
1✔
864
                    attributes: BandSelection::first(),
1✔
865
                },
1✔
866
                &ctx,
1✔
867
            )
1✔
868
            .await
1✔
869
            .unwrap();
1✔
870

1✔
871
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
2✔
872

1✔
873
        assert_eq!(result.len(), 1);
1✔
874

1✔
875
        assert!(
1✔
876
            result[0].as_ref().unwrap().cache_hint.total_ttl_seconds() > CacheTtlSeconds::new(0)
1✔
877
                && result[0].as_ref().unwrap().cache_hint.total_ttl_seconds()
1✔
878
                    <= cache_hint.total_ttl_seconds()
1✔
879
        );
1✔
880
    }
1✔
881

882
    #[tokio::test]
883
    async fn it_attaches_cache_hint_2() {
1✔
884
        let no_data_value = 0;
1✔
885
        let tile_size_in_pixels = [3, 2].into();
1✔
886
        let tiling_specification = TilingSpecification {
1✔
887
            origin_coordinate: [0.0, 0.0].into(),
1✔
888
            tile_size_in_pixels,
1✔
889
        };
1✔
890

1✔
891
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
892

1✔
893
        let cache_hint_a = CacheHint::seconds(1234);
1✔
894
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint_a);
1✔
895

1✔
896
        let cache_hint_b = CacheHint::seconds(4567);
1✔
897
        let raster_b = make_raster_with_cache_hint(Some(no_data_value), cache_hint_b);
1✔
898

1✔
899
        let o = Expression {
1✔
900
            params: ExpressionParams {
1✔
901
                expression: "A + B".to_string(),
1✔
902
                output_type: RasterDataType::I8,
1✔
903
                output_band: None,
1✔
904
                map_no_data: false,
1✔
905
            },
1✔
906
            sources: SingleRasterSource {
1✔
907
                raster: RasterStacker {
1✔
908
                    params: RasterStackerParams {
1✔
909
                        rename_bands: RenameBands::Default,
1✔
910
                    },
1✔
911
                    sources: MultipleRasterSources {
1✔
912
                        rasters: vec![raster_a, raster_b],
1✔
913
                    },
1✔
914
                }
1✔
915
                .boxed(),
1✔
916
            },
1✔
917
        }
1✔
918
        .boxed()
1✔
919
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
920
        .await
1✔
921
        .unwrap();
1✔
922

1✔
923
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
924

1✔
925
        let ctx = MockQueryContext::new(1.into());
1✔
926
        let result_stream = processor
1✔
927
            .query(
1✔
928
                RasterQueryRectangle {
1✔
929
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
930
                        (0., 3.).into(),
1✔
931
                        (2., 0.).into(),
1✔
932
                    ),
1✔
933
                    time_interval: Default::default(),
1✔
934
                    spatial_resolution: SpatialResolution::one(),
1✔
935
                    attributes: BandSelection::first(),
1✔
936
                },
1✔
937
                &ctx,
1✔
938
            )
1✔
939
            .await
1✔
940
            .unwrap();
1✔
941

1✔
942
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
3✔
943

1✔
944
        assert_eq!(result.len(), 1);
1✔
945

1✔
946
        assert_eq!(
1✔
947
            result[0].as_ref().unwrap().cache_hint.expires(),
1✔
948
            cache_hint_a.merged(&cache_hint_b).expires()
1✔
949
        );
1✔
950
    }
1✔
951

952
    #[tokio::test]
953
    async fn it_attaches_cache_hint_3() {
1✔
954
        let no_data_value = 0;
1✔
955
        let tile_size_in_pixels = [3, 2].into();
1✔
956
        let tiling_specification = TilingSpecification {
1✔
957
            origin_coordinate: [0.0, 0.0].into(),
1✔
958
            tile_size_in_pixels,
1✔
959
        };
1✔
960

1✔
961
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
962

1✔
963
        let cache_hint_a = CacheHint::seconds(1234);
1✔
964
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint_a);
1✔
965

1✔
966
        let cache_hint_b = CacheHint::seconds(4567);
1✔
967
        let raster_b = make_raster_with_cache_hint(Some(no_data_value), cache_hint_b);
1✔
968

1✔
969
        let cache_hint_c = CacheHint::seconds(7891);
1✔
970
        let raster_c = make_raster_with_cache_hint(Some(no_data_value), cache_hint_c);
1✔
971

1✔
972
        let o = Expression {
1✔
973
            params: ExpressionParams {
1✔
974
                expression: "A + B".to_string(),
1✔
975
                output_type: RasterDataType::I8,
1✔
976
                output_band: None,
1✔
977
                map_no_data: false,
1✔
978
            },
1✔
979
            sources: SingleRasterSource {
1✔
980
                raster: RasterStacker {
1✔
981
                    params: RasterStackerParams {
1✔
982
                        rename_bands: RenameBands::Default,
1✔
983
                    },
1✔
984
                    sources: MultipleRasterSources {
1✔
985
                        rasters: vec![raster_a, raster_b, raster_c],
1✔
986
                    },
1✔
987
                }
1✔
988
                .boxed(),
1✔
989
            },
1✔
990
        }
1✔
991
        .boxed()
1✔
992
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
993
        .await
1✔
994
        .unwrap();
1✔
995

1✔
996
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
997

1✔
998
        let ctx = MockQueryContext::new(1.into());
1✔
999
        let result_stream = processor
1✔
1000
            .query(
1✔
1001
                RasterQueryRectangle {
1✔
1002
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1003
                        (0., 3.).into(),
1✔
1004
                        (2., 0.).into(),
1✔
1005
                    ),
1✔
1006
                    time_interval: Default::default(),
1✔
1007
                    spatial_resolution: SpatialResolution::one(),
1✔
1008
                    attributes: BandSelection::first(),
1✔
1009
                },
1✔
1010
                &ctx,
1✔
1011
            )
1✔
1012
            .await
1✔
1013
            .unwrap();
1✔
1014

1✔
1015
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
4✔
1016

1✔
1017
        assert_eq!(result.len(), 1);
1✔
1018

1✔
1019
        assert_eq!(
1✔
1020
            result[0].as_ref().unwrap().cache_hint.expires(),
1✔
1021
            cache_hint_a
1✔
1022
                .merged(&cache_hint_b)
1✔
1023
                .merged(&cache_hint_c)
1✔
1024
                .expires()
1✔
1025
        );
1✔
1026
    }
1✔
1027
}
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

© 2025 Coveralls, Inc