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

geo-engine / geoengine / 11911118784

19 Nov 2024 10:06AM UTC coverage: 90.448% (-0.2%) from 90.687%
11911118784

push

github

web-flow
Merge pull request #994 from geo-engine/workspace-dependencies

use workspace dependencies, update toolchain, use global lock in expression

9 of 11 new or added lines in 6 files covered. (81.82%)

369 existing lines in 74 files now uncovered.

132871 of 146904 relevant lines covered (90.45%)

54798.62 hits per line

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

99.15
/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
    processing::expression::canonicalize_name,
15
    util::Result,
16
};
17
use async_trait::async_trait;
18
use geoengine_datatypes::raster::RasterDataType;
19
use geoengine_expression::{
20
    DataType, ExpressionAst, ExpressionParser, LinkedExpression, Parameter,
21
};
22
use serde::{Deserialize, Serialize};
23
use snafu::ensure;
24
use std::borrow::Cow;
25

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

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

32✔
55
    let parameter = char::from_u32(start_index + index).unwrap_or_default();
32✔
56

32✔
57
    parameter.to_string()
32✔
58
}
32✔
59

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

70
        let source = self.sources.initialize_sources(path, context).await?.raster;
151✔
71

72
        let in_descriptor = source.result_descriptor();
16✔
73

16✔
74
        ensure!(
16✔
75
            !in_descriptor.bands.is_empty() && in_descriptor.bands.len() <= 8,
16✔
UNCOV
76
            InvalidNumberOfExpressionInputBands {
×
77
                found: in_descriptor.bands.len()
×
78
            }
×
79
        );
80

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

89
        let expression = ExpressionParser::new(&parameters, DataType::Number)
16✔
90
            .map_err(RasterExpressionError::from)?
16✔
91
            .parse(
16✔
92
                self.params
16✔
93
                    .output_band
16✔
94
                    .as_ref()
16✔
95
                    .map_or(Cow::Borrowed("expression"), |b| {
16✔
96
                        // allow only idents
1✔
97
                        Cow::Owned(canonicalize_name(&b.name))
1✔
98
                    })
16✔
99
                    .as_ref(),
16✔
100
                &self.params.expression,
16✔
101
            )
16✔
102
            .map_err(RasterExpressionError::from)?;
16✔
103

104
        let result_descriptor = RasterResultDescriptor {
16✔
105
            data_type: self.params.output_type,
16✔
106
            spatial_reference: in_descriptor.spatial_reference,
16✔
107
            time: in_descriptor.time,
16✔
108
            bbox: in_descriptor.bbox,
16✔
109
            resolution: in_descriptor.resolution,
16✔
110
            bands: RasterBandDescriptors::new(vec![self
16✔
111
                .params
16✔
112
                .output_band
16✔
113
                .unwrap_or(RasterBandDescriptor::new_unitless("expression".into()))])?,
16✔
114
        };
115

116
        let initialized_operator = InitializedExpression {
16✔
117
            name,
16✔
118
            result_descriptor,
16✔
119
            source,
16✔
120
            expression,
16✔
121
            map_no_data: self.params.map_no_data,
16✔
122
        };
16✔
123

16✔
124
        Ok(initialized_operator.boxed())
16✔
125
    }
32✔
126

127
    span_fn!(Expression);
128
}
129

130
impl OperatorName for Expression {
131
    const TYPE_NAME: &'static str = "Expression";
132
}
133

134
pub struct InitializedExpression {
135
    name: CanonicOperatorName,
136
    result_descriptor: RasterResultDescriptor,
137
    source: Box<dyn InitializedRasterOperator>,
138
    expression: ExpressionAst,
139
    map_no_data: bool,
140
}
141

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

165
impl InitializedRasterOperator for InitializedExpression {
166
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor> {
16✔
167
        let output_type = self.result_descriptor().data_type;
16✔
168

169
        // TODO: spawn a blocking task for the compilation process
170
        let expression_dependencies = get_expression_dependencies()
16✔
171
            .map_err(|source| RasterExpressionError::Dependencies { source })?;
16✔
172
        let expression = LinkedExpression::new(
16✔
173
            self.expression.name(),
16✔
174
            &self.expression.code(),
16✔
175
            expression_dependencies,
16✔
176
        )
16✔
177
        .map_err(RasterExpressionError::from)?;
16✔
178

179
        let source_processor = self.source.query_processor()?.into_f64();
16✔
180

181
        Ok(generate_match_cases!(
×
182
            self.source.result_descriptor().bands.len(),
16✔
183
            output_type,
8✔
184
            expression,
4✔
185
            source_processor,
4✔
186
            self.result_descriptor.clone(),
4✔
187
            self.map_no_data,
4✔
188
            1,
189
            2,
190
            3,
191
            4,
192
            5,
193
            6,
194
            7,
195
            8
196
        ))
197
    }
16✔
198

199
    fn result_descriptor(&self) -> &RasterResultDescriptor {
21✔
200
        &self.result_descriptor
21✔
201
    }
21✔
202

203
    fn canonic_name(&self) -> CanonicOperatorName {
×
204
        self.name.clone()
×
205
    }
×
206
}
207

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

228
    #[test]
229
    fn deserialize_params() {
1✔
230
        let s =
1✔
231
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
232

1✔
233
        assert_eq!(
1✔
234
            serde_json::from_str::<ExpressionParams>(s).unwrap(),
1✔
235
            ExpressionParams {
1✔
236
                expression: "1*A".to_owned(),
1✔
237
                output_type: RasterDataType::F64,
1✔
238
                output_band: None,
1✔
239
                map_no_data: false,
1✔
240
            }
1✔
241
        );
1✔
242
    }
1✔
243

244
    #[test]
245
    fn serialize_params() {
1✔
246
        let s = r#"{"expression":"1*A","outputType":"F64","outputBand":null,"mapNoData":false}"#;
1✔
247

1✔
248
        assert_eq!(
1✔
249
            s,
1✔
250
            serde_json::to_string(&ExpressionParams {
1✔
251
                expression: "1*A".to_owned(),
1✔
252
                output_type: RasterDataType::F64,
1✔
253
                output_band: None,
1✔
254
                map_no_data: false,
1✔
255
            })
1✔
256
            .unwrap()
1✔
257
        );
1✔
258
    }
1✔
259

260
    #[test]
261
    fn serialize_params_no_data() {
1✔
262
        let s = r#"{"expression":"1*A","outputType":"F64","outputBand":null,"mapNoData":false}"#;
1✔
263

1✔
264
        assert_eq!(
1✔
265
            s,
1✔
266
            serde_json::to_string(&ExpressionParams {
1✔
267
                expression: "1*A".to_owned(),
1✔
268
                output_type: RasterDataType::F64,
1✔
269
                output_band: None,
1✔
270
                map_no_data: false,
1✔
271
            })
1✔
272
            .unwrap()
1✔
273
        );
1✔
274
    }
1✔
275

276
    #[tokio::test]
277
    async fn basic_unary() {
1✔
278
        let tile_size_in_pixels = [3, 2].into();
1✔
279
        let tiling_specification = TilingSpecification {
1✔
280
            origin_coordinate: [0.0, 0.0].into(),
1✔
281
            tile_size_in_pixels,
1✔
282
        };
1✔
283

1✔
284
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
285

1✔
286
        let raster_a = make_raster(Some(3));
1✔
287

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

1✔
302
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
303

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

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

1✔
323
        assert_eq!(result.len(), 1);
1✔
324

1✔
325
        assert_eq!(
1✔
326
            result[0].as_ref().unwrap().grid_array,
1✔
327
            GridOrEmpty::from(
1✔
328
                MaskedGrid2D::new(
1✔
329
                    Grid2D::new([3, 2].into(), vec![2, 4, 0, 8, 10, 12],).unwrap(),
1✔
330
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
331
                )
1✔
332
                .unwrap()
1✔
333
            )
1✔
334
        );
1✔
335
    }
1✔
336

337
    #[tokio::test]
338
    async fn unary_map_no_data() {
1✔
339
        let tile_size_in_pixels = [3, 2].into();
1✔
340
        let tiling_specification = TilingSpecification {
1✔
341
            origin_coordinate: [0.0, 0.0].into(),
1✔
342
            tile_size_in_pixels,
1✔
343
        };
1✔
344

1✔
345
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
346

1✔
347
        let raster_a = make_raster(Some(3));
1✔
348

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

1✔
363
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
364

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

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

1✔
384
        assert_eq!(result.len(), 1);
1✔
385

1✔
386
        assert_eq!(
1✔
387
            result[0].as_ref().unwrap().grid_array,
1✔
388
            GridOrEmpty::from(
1✔
389
                MaskedGrid2D::new(
1✔
390
                    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✔
391
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
392
                )
1✔
393
                .unwrap()
1✔
394
            )
1✔
395
        );
1✔
396
    }
1✔
397

398
    #[tokio::test]
399
    async fn basic_binary() {
1✔
400
        let tile_size_in_pixels = [3, 2].into();
1✔
401
        let tiling_specification = TilingSpecification {
1✔
402
            origin_coordinate: [0.0, 0.0].into(),
1✔
403
            tile_size_in_pixels,
1✔
404
        };
1✔
405

1✔
406
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
407

1✔
408
        let raster_a = make_raster(None);
1✔
409
        let raster_b = make_raster(None);
1✔
410

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

1✔
435
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
436

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

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

1✔
456
        assert_eq!(result.len(), 1);
1✔
457

1✔
458
        assert_eq!(
1✔
459
            result[0].as_ref().unwrap().grid_array,
1✔
460
            Grid2D::new([3, 2].into(), vec![2, 4, 6, 8, 10, 12],)
1✔
461
                .unwrap()
1✔
462
                .into()
1✔
463
        );
1✔
464
    }
1✔
465

466
    #[tokio::test]
467
    async fn basic_coalesce() {
1✔
468
        let tile_size_in_pixels = [3, 2].into();
1✔
469
        let tiling_specification = TilingSpecification {
1✔
470
            origin_coordinate: [0.0, 0.0].into(),
1✔
471
            tile_size_in_pixels,
1✔
472
        };
1✔
473

1✔
474
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
475

1✔
476
        let raster_a = make_raster(Some(3));
1✔
477
        let raster_b = make_raster(None);
1✔
478

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

1✔
510
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
511

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

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

1✔
531
        assert_eq!(result.len(), 1);
1✔
532

1✔
533
        assert_eq!(
1✔
534
            result[0].as_ref().unwrap().grid_array,
1✔
535
            GridOrEmpty::from(
1✔
536
                MaskedGrid2D::new(
1✔
537
                    Grid2D::new([3, 2].into(), vec![1, 2, 6, 4, 5, 0],).unwrap(),
1✔
538
                    Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false],).unwrap()
1✔
539
                )
1✔
540
                .unwrap()
1✔
541
            )
1✔
542
        );
1✔
543
    }
1✔
544

545
    #[tokio::test]
546
    async fn basic_ternary() {
1✔
547
        let no_data_value = 3;
1✔
548
        let no_data_value_option = Some(no_data_value);
1✔
549

1✔
550
        let tile_size_in_pixels = [3, 2].into();
1✔
551
        let tiling_specification = TilingSpecification {
1✔
552
            origin_coordinate: [0.0, 0.0].into(),
1✔
553
            tile_size_in_pixels,
1✔
554
        };
1✔
555

1✔
556
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
557

1✔
558
        let raster_a = make_raster(no_data_value_option);
1✔
559
        let raster_b = make_raster(no_data_value_option);
1✔
560
        let raster_c = make_raster(no_data_value_option);
1✔
561

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

1✔
586
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
587

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

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

1✔
607
        assert_eq!(result.len(), 1);
1✔
608

1✔
609
        let first_result = result[0].as_ref().unwrap();
1✔
610

1✔
611
        assert!(!first_result.is_empty());
1✔
612

1✔
613
        let grid = match &first_result.grid_array {
1✔
614
            GridOrEmpty::Grid(g) => g,
1✔
615
            GridOrEmpty::Empty(_) => panic!(),
1✔
616
        };
1✔
617

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

1✔
620
        assert_eq!(
1✔
621
            res,
1✔
622
            [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✔
623
        );
1✔
624
    }
1✔
625

626
    #[tokio::test]
627
    async fn octave_inputs() {
1✔
628
        let no_data_value = 0;
1✔
629
        let no_data_value_option = Some(no_data_value);
1✔
630

1✔
631
        let tile_size_in_pixels = [3, 2].into();
1✔
632
        let tiling_specification = TilingSpecification {
1✔
633
            origin_coordinate: [0.0, 0.0].into(),
1✔
634
            tile_size_in_pixels,
1✔
635
        };
1✔
636

1✔
637
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
638

1✔
639
        let raster_a = make_raster(no_data_value_option);
1✔
640
        let raster_b = make_raster(no_data_value_option);
1✔
641
        let raster_c = make_raster(no_data_value_option);
1✔
642
        let raster_d = make_raster(no_data_value_option);
1✔
643
        let raster_e = make_raster(no_data_value_option);
1✔
644
        let raster_f = make_raster(no_data_value_option);
1✔
645
        let raster_g = make_raster(no_data_value_option);
1✔
646
        let raster_h = make_raster(no_data_value_option);
1✔
647

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

1✔
675
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
676

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

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

1✔
696
        assert_eq!(result.len(), 1);
1✔
697

1✔
698
        assert_eq!(
1✔
699
            result[0].as_ref().unwrap().grid_array,
1✔
700
            Grid2D::new([3, 2].into(), vec![8, 16, 24, 32, 40, 48],)
1✔
701
                .unwrap()
1✔
702
                .into()
1✔
703
        );
1✔
704
    }
1✔
705

706
    #[tokio::test]
707
    async fn it_classifies() {
1✔
708
        let tile_size_in_pixels = [3, 2].into();
1✔
709
        let tiling_specification = TilingSpecification {
1✔
710
            origin_coordinate: [0.0, 0.0].into(),
1✔
711
            tile_size_in_pixels,
1✔
712
        };
1✔
713

1✔
714
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
715

1✔
716
        let operator = Expression {
1✔
717
            params: ExpressionParams {
1✔
718
                expression: "if A <= 1 { 0 } else if A <= 3 { 1 } else { 2 }".to_string(),
1✔
719
                output_type: RasterDataType::U8,
1✔
720
                output_band: Some(RasterBandDescriptor::new(
1✔
721
                    "a class".into(),
1✔
722
                    Measurement::classification(
1✔
723
                        "classes".into(),
1✔
724
                        [
1✔
725
                            (0, "Class A".into()),
1✔
726
                            (1, "Class B".into()),
1✔
727
                            (2, "Class C".into()),
1✔
728
                        ]
1✔
729
                        .into_iter()
1✔
730
                        .collect(),
1✔
731
                    ),
1✔
732
                )),
1✔
733
                map_no_data: false,
1✔
734
            },
1✔
735
            sources: SingleRasterSource {
1✔
736
                raster: RasterStacker {
1✔
737
                    params: RasterStackerParams {
1✔
738
                        rename_bands: RenameBands::Default,
1✔
739
                    },
1✔
740
                    sources: MultipleRasterSources {
1✔
741
                        rasters: vec![make_raster(None)],
1✔
742
                    },
1✔
743
                }
1✔
744
                .boxed(),
1✔
745
            },
1✔
746
        }
1✔
747
        .boxed()
1✔
748
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
749
        .await
1✔
750
        .unwrap();
1✔
751

1✔
752
        let processor = operator.query_processor().unwrap().get_u8().unwrap();
1✔
753

1✔
754
        let ctx = MockQueryContext::new(1.into());
1✔
755
        let result_stream = processor
1✔
756
            .query(
1✔
757
                RasterQueryRectangle {
1✔
758
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
759
                        (0., 3.).into(),
1✔
760
                        (2., 0.).into(),
1✔
761
                    ),
1✔
762
                    time_interval: Default::default(),
1✔
763
                    spatial_resolution: SpatialResolution::one(),
1✔
764
                    attributes: BandSelection::first(),
1✔
765
                },
1✔
766
                &ctx,
1✔
767
            )
1✔
768
            .await
1✔
769
            .unwrap();
1✔
770

1✔
771
        let result: Vec<Result<RasterTile2D<u8>>> = result_stream.collect().await;
2✔
772

1✔
773
        assert_eq!(result.len(), 1);
1✔
774

1✔
775
        let first_result = result[0].as_ref().unwrap();
1✔
776

1✔
777
        assert!(!first_result.is_empty());
1✔
778

1✔
779
        let grid = match &first_result.grid_array {
1✔
780
            GridOrEmpty::Grid(g) => g,
1✔
781
            GridOrEmpty::Empty(_) => panic!(),
1✔
782
        };
1✔
783

1✔
784
        let res: Vec<Option<u8>> = grid.masked_element_deref_iterator().collect();
1✔
785

1✔
786
        assert_eq!(res, [Some(0), Some(1), Some(1), Some(2), Some(2), Some(2)]);
1✔
787
    }
1✔
788

789
    #[tokio::test]
790
    async fn test_functions() {
1✔
791
        let no_data_value = 0;
1✔
792
        let tile_size_in_pixels = [3, 2].into();
1✔
793
        let tiling_specification = TilingSpecification {
1✔
794
            origin_coordinate: [0.0, 0.0].into(),
1✔
795
            tile_size_in_pixels,
1✔
796
        };
1✔
797

1✔
798
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
799

1✔
800
        let raster_a = make_raster(Some(no_data_value));
1✔
801

1✔
802
        let o = Expression {
1✔
803
            params: ExpressionParams {
1✔
804
                expression: "min(A * pi(), 10)".to_string(),
1✔
805
                output_type: RasterDataType::I8,
1✔
806
                output_band: None,
1✔
807
                map_no_data: false,
1✔
808
            },
1✔
809
            sources: SingleRasterSource {
1✔
810
                raster: RasterStacker {
1✔
811
                    params: RasterStackerParams {
1✔
812
                        rename_bands: RenameBands::Default,
1✔
813
                    },
1✔
814
                    sources: MultipleRasterSources {
1✔
815
                        rasters: vec![raster_a],
1✔
816
                    },
1✔
817
                }
1✔
818
                .boxed(),
1✔
819
            },
1✔
820
        }
1✔
821
        .boxed()
1✔
822
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
823
        .await
1✔
824
        .unwrap();
1✔
825

1✔
826
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
827

1✔
828
        let ctx = MockQueryContext::new(1.into());
1✔
829
        let result_stream = processor
1✔
830
            .query(
1✔
831
                RasterQueryRectangle {
1✔
832
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
833
                        (0., 3.).into(),
1✔
834
                        (2., 0.).into(),
1✔
835
                    ),
1✔
836
                    time_interval: Default::default(),
1✔
837
                    spatial_resolution: SpatialResolution::one(),
1✔
838
                    attributes: BandSelection::first(),
1✔
839
                },
1✔
840
                &ctx,
1✔
841
            )
1✔
842
            .await
1✔
843
            .unwrap();
1✔
844

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

1✔
847
        assert_eq!(result.len(), 1);
1✔
848

1✔
849
        assert_eq!(
1✔
850
            result[0].as_ref().unwrap().grid_array,
1✔
851
            Grid2D::new([3, 2].into(), vec![3, 6, 9, 10, 10, 10],)
1✔
852
                .unwrap()
1✔
853
                .into()
1✔
854
        );
1✔
855
    }
1✔
856

857
    fn make_raster(no_data_value: Option<i8>) -> Box<dyn RasterOperator> {
19✔
858
        make_raster_with_cache_hint(no_data_value, CacheHint::no_cache())
19✔
859
    }
19✔
860

861
    fn make_raster_with_cache_hint(
25✔
862
        no_data_value: Option<i8>,
25✔
863
        cache_hint: CacheHint,
25✔
864
    ) -> Box<dyn RasterOperator> {
25✔
865
        let raster = Grid2D::<i8>::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6]).unwrap();
25✔
866

867
        let real_raster = if let Some(no_data_value) = no_data_value {
25✔
868
            MaskedGrid2D::from(raster)
21✔
869
                .map_elements(|e: Option<i8>| e.filter(|v| *v != no_data_value))
126✔
870
                .into()
21✔
871
        } else {
872
            GridOrEmpty::from(raster)
4✔
873
        };
874

875
        let raster_tile = RasterTile2D::new_with_tile_info(
25✔
876
            TimeInterval::default(),
25✔
877
            TileInformation {
25✔
878
                global_tile_position: [-1, 0].into(),
25✔
879
                tile_size_in_pixels: [3, 2].into(),
25✔
880
                global_geo_transform: TestDefault::test_default(),
25✔
881
            },
25✔
882
            0,
25✔
883
            real_raster,
25✔
884
            cache_hint,
25✔
885
        );
25✔
886

25✔
887
        MockRasterSource {
25✔
888
            params: MockRasterSourceParams {
25✔
889
                data: vec![raster_tile],
25✔
890
                result_descriptor: RasterResultDescriptor {
25✔
891
                    data_type: RasterDataType::I8,
25✔
892
                    spatial_reference: SpatialReference::epsg_4326().into(),
25✔
893
                    time: None,
25✔
894
                    bbox: None,
25✔
895
                    resolution: None,
25✔
896
                    bands: RasterBandDescriptors::new_single_band(),
25✔
897
                },
25✔
898
            },
25✔
899
        }
25✔
900
        .boxed()
25✔
901
    }
25✔
902

903
    #[tokio::test]
904
    async fn it_attaches_cache_hint_1() {
1✔
905
        let no_data_value = 0;
1✔
906
        let tile_size_in_pixels = [3, 2].into();
1✔
907
        let tiling_specification = TilingSpecification {
1✔
908
            origin_coordinate: [0.0, 0.0].into(),
1✔
909
            tile_size_in_pixels,
1✔
910
        };
1✔
911

1✔
912
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
913

1✔
914
        let cache_hint = CacheHint::seconds(1234);
1✔
915
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint);
1✔
916

1✔
917
        let o = Expression {
1✔
918
            params: ExpressionParams {
1✔
919
                expression: "min(A * pi(), 10)".to_string(),
1✔
920
                output_type: RasterDataType::I8,
1✔
921
                output_band: None,
1✔
922
                map_no_data: false,
1✔
923
            },
1✔
924
            sources: SingleRasterSource {
1✔
925
                raster: RasterStacker {
1✔
926
                    params: RasterStackerParams {
1✔
927
                        rename_bands: RenameBands::Default,
1✔
928
                    },
1✔
929
                    sources: MultipleRasterSources {
1✔
930
                        rasters: vec![raster_a],
1✔
931
                    },
1✔
932
                }
1✔
933
                .boxed(),
1✔
934
            },
1✔
935
        }
1✔
936
        .boxed()
1✔
937
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
938
        .await
1✔
939
        .unwrap();
1✔
940

1✔
941
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
942

1✔
943
        let ctx = MockQueryContext::new(1.into());
1✔
944
        let result_stream = processor
1✔
945
            .query(
1✔
946
                RasterQueryRectangle {
1✔
947
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
948
                        (0., 3.).into(),
1✔
949
                        (2., 0.).into(),
1✔
950
                    ),
1✔
951
                    time_interval: Default::default(),
1✔
952
                    spatial_resolution: SpatialResolution::one(),
1✔
953
                    attributes: BandSelection::first(),
1✔
954
                },
1✔
955
                &ctx,
1✔
956
            )
1✔
957
            .await
1✔
958
            .unwrap();
1✔
959

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

1✔
962
        assert_eq!(result.len(), 1);
1✔
963

1✔
964
        assert!(
1✔
965
            result[0].as_ref().unwrap().cache_hint.total_ttl_seconds() > CacheTtlSeconds::new(0)
1✔
966
                && result[0].as_ref().unwrap().cache_hint.total_ttl_seconds()
1✔
967
                    <= cache_hint.total_ttl_seconds()
1✔
968
        );
1✔
969
    }
1✔
970

971
    #[tokio::test]
972
    async fn it_attaches_cache_hint_2() {
1✔
973
        let no_data_value = 0;
1✔
974
        let tile_size_in_pixels = [3, 2].into();
1✔
975
        let tiling_specification = TilingSpecification {
1✔
976
            origin_coordinate: [0.0, 0.0].into(),
1✔
977
            tile_size_in_pixels,
1✔
978
        };
1✔
979

1✔
980
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
981

1✔
982
        let cache_hint_a = CacheHint::seconds(1234);
1✔
983
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint_a);
1✔
984

1✔
985
        let cache_hint_b = CacheHint::seconds(4567);
1✔
986
        let raster_b = make_raster_with_cache_hint(Some(no_data_value), cache_hint_b);
1✔
987

1✔
988
        let o = Expression {
1✔
989
            params: ExpressionParams {
1✔
990
                expression: "A + B".to_string(),
1✔
991
                output_type: RasterDataType::I8,
1✔
992
                output_band: None,
1✔
993
                map_no_data: false,
1✔
994
            },
1✔
995
            sources: SingleRasterSource {
1✔
996
                raster: RasterStacker {
1✔
997
                    params: RasterStackerParams {
1✔
998
                        rename_bands: RenameBands::Default,
1✔
999
                    },
1✔
1000
                    sources: MultipleRasterSources {
1✔
1001
                        rasters: vec![raster_a, raster_b],
1✔
1002
                    },
1✔
1003
                }
1✔
1004
                .boxed(),
1✔
1005
            },
1✔
1006
        }
1✔
1007
        .boxed()
1✔
1008
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1009
        .await
1✔
1010
        .unwrap();
1✔
1011

1✔
1012
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1013

1✔
1014
        let ctx = MockQueryContext::new(1.into());
1✔
1015
        let result_stream = processor
1✔
1016
            .query(
1✔
1017
                RasterQueryRectangle {
1✔
1018
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1019
                        (0., 3.).into(),
1✔
1020
                        (2., 0.).into(),
1✔
1021
                    ),
1✔
1022
                    time_interval: Default::default(),
1✔
1023
                    spatial_resolution: SpatialResolution::one(),
1✔
1024
                    attributes: BandSelection::first(),
1✔
1025
                },
1✔
1026
                &ctx,
1✔
1027
            )
1✔
1028
            .await
1✔
1029
            .unwrap();
1✔
1030

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

1✔
1033
        assert_eq!(result.len(), 1);
1✔
1034

1✔
1035
        assert_eq!(
1✔
1036
            result[0].as_ref().unwrap().cache_hint.expires(),
1✔
1037
            cache_hint_a.merged(&cache_hint_b).expires()
1✔
1038
        );
1✔
1039
    }
1✔
1040

1041
    #[tokio::test]
1042
    async fn it_attaches_cache_hint_3() {
1✔
1043
        let no_data_value = 0;
1✔
1044
        let tile_size_in_pixels = [3, 2].into();
1✔
1045
        let tiling_specification = TilingSpecification {
1✔
1046
            origin_coordinate: [0.0, 0.0].into(),
1✔
1047
            tile_size_in_pixels,
1✔
1048
        };
1✔
1049

1✔
1050
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1051

1✔
1052
        let cache_hint_a = CacheHint::seconds(1234);
1✔
1053
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint_a);
1✔
1054

1✔
1055
        let cache_hint_b = CacheHint::seconds(4567);
1✔
1056
        let raster_b = make_raster_with_cache_hint(Some(no_data_value), cache_hint_b);
1✔
1057

1✔
1058
        let cache_hint_c = CacheHint::seconds(7891);
1✔
1059
        let raster_c = make_raster_with_cache_hint(Some(no_data_value), cache_hint_c);
1✔
1060

1✔
1061
        let o = Expression {
1✔
1062
            params: ExpressionParams {
1✔
1063
                expression: "A + B".to_string(),
1✔
1064
                output_type: RasterDataType::I8,
1✔
1065
                output_band: None,
1✔
1066
                map_no_data: false,
1✔
1067
            },
1✔
1068
            sources: SingleRasterSource {
1✔
1069
                raster: RasterStacker {
1✔
1070
                    params: RasterStackerParams {
1✔
1071
                        rename_bands: RenameBands::Default,
1✔
1072
                    },
1✔
1073
                    sources: MultipleRasterSources {
1✔
1074
                        rasters: vec![raster_a, raster_b, raster_c],
1✔
1075
                    },
1✔
1076
                }
1✔
1077
                .boxed(),
1✔
1078
            },
1✔
1079
        }
1✔
1080
        .boxed()
1✔
1081
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1082
        .await
1✔
1083
        .unwrap();
1✔
1084

1✔
1085
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1086

1✔
1087
        let ctx = MockQueryContext::new(1.into());
1✔
1088
        let result_stream = processor
1✔
1089
            .query(
1✔
1090
                RasterQueryRectangle {
1✔
1091
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1092
                        (0., 3.).into(),
1✔
1093
                        (2., 0.).into(),
1✔
1094
                    ),
1✔
1095
                    time_interval: Default::default(),
1✔
1096
                    spatial_resolution: SpatialResolution::one(),
1✔
1097
                    attributes: BandSelection::first(),
1✔
1098
                },
1✔
1099
                &ctx,
1✔
1100
            )
1✔
1101
            .await
1✔
1102
            .unwrap();
1✔
1103

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

1✔
1106
        assert_eq!(result.len(), 1);
1✔
1107

1✔
1108
        assert_eq!(
1✔
1109
            result[0].as_ref().unwrap().cache_hint.expires(),
1✔
1110
            cache_hint_a
1✔
1111
                .merged(&cache_hint_b)
1✔
1112
                .merged(&cache_hint_c)
1✔
1113
                .expires()
1✔
1114
        );
1✔
1115
    }
1✔
1116
}
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