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

geo-engine / geoengine / 12469296660

23 Dec 2024 03:15PM UTC coverage: 90.56% (-0.1%) from 90.695%
12469296660

push

github

web-flow
Merge pull request #998 from geo-engine/quota_log_wip

Quota and Data usage Logging

859 of 1214 new or added lines in 66 files covered. (70.76%)

3 existing lines in 2 files now uncovered.

133923 of 147883 relevant lines covered (90.56%)

54439.32 hits per line

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

98.52
/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)]
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
16✔
71
            .sources
16✔
72
            .initialize_sources(path.clone(), context)
16✔
73
            .await?
16✔
74
            .raster;
75

76
        let in_descriptor = source.result_descriptor();
16✔
77

16✔
78
        ensure!(
16✔
79
            !in_descriptor.bands.is_empty() && in_descriptor.bands.len() <= 8,
16✔
80
            InvalidNumberOfExpressionInputBands {
×
81
                found: in_descriptor.bands.len()
×
82
            }
×
83
        );
84

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

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

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

120
        let initialized_operator = InitializedExpression {
16✔
121
            name,
16✔
122
            path,
16✔
123
            result_descriptor,
16✔
124
            source,
16✔
125
            expression,
16✔
126
            map_no_data: self.params.map_no_data,
16✔
127
        };
16✔
128

16✔
129
        Ok(initialized_operator.boxed())
16✔
130
    }
32✔
131

132
    span_fn!(Expression);
133
}
134

135
impl OperatorName for Expression {
136
    const TYPE_NAME: &'static str = "Expression";
137
}
138

139
pub struct InitializedExpression {
140
    name: CanonicOperatorName,
141
    path: WorkflowOperatorPath,
142
    result_descriptor: RasterResultDescriptor,
143
    source: Box<dyn InitializedRasterOperator>,
144
    expression: ExpressionAst,
145
    map_no_data: bool,
146
}
147

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

171
impl InitializedRasterOperator for InitializedExpression {
172
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor> {
16✔
173
        let output_type = self.result_descriptor().data_type;
16✔
174

175
        // TODO: spawn a blocking task for the compilation process
176
        let expression_dependencies = get_expression_dependencies()
16✔
177
            .map_err(|source| RasterExpressionError::Dependencies { source })?;
16✔
178
        let expression = LinkedExpression::new(
16✔
179
            self.expression.name(),
16✔
180
            &self.expression.code(),
16✔
181
            expression_dependencies,
16✔
182
        )
16✔
183
        .map_err(RasterExpressionError::from)?;
16✔
184

185
        let source_processor = self.source.query_processor()?.into_f64();
16✔
186

187
        Ok(generate_match_cases!(
×
188
            self.source.result_descriptor().bands.len(),
16✔
189
            output_type,
8✔
190
            expression,
4✔
191
            source_processor,
4✔
192
            self.result_descriptor.clone(),
4✔
193
            self.map_no_data,
4✔
194
            1,
195
            2,
196
            3,
197
            4,
198
            5,
199
            6,
200
            7,
201
            8
202
        ))
203
    }
16✔
204

205
    fn result_descriptor(&self) -> &RasterResultDescriptor {
21✔
206
        &self.result_descriptor
21✔
207
    }
21✔
208

209
    fn canonic_name(&self) -> CanonicOperatorName {
×
210
        self.name.clone()
×
211
    }
×
212

NEW
213
    fn name(&self) -> &'static str {
×
NEW
214
        Expression::TYPE_NAME
×
NEW
215
    }
×
216

NEW
217
    fn path(&self) -> WorkflowOperatorPath {
×
NEW
218
        self.path.clone()
×
NEW
219
    }
×
220
}
221

222
#[cfg(test)]
223
mod tests {
224
    use super::*;
225
    use crate::engine::{
226
        MockExecutionContext, MockQueryContext, MultipleRasterSources, QueryProcessor,
227
    };
228
    use crate::mock::{MockRasterSource, MockRasterSourceParams};
229
    use crate::processing::{RasterStacker, RasterStackerParams};
230
    use futures::StreamExt;
231
    use geoengine_datatypes::primitives::{BandSelection, CacheHint, CacheTtlSeconds, Measurement};
232
    use geoengine_datatypes::primitives::{
233
        RasterQueryRectangle, SpatialPartition2D, SpatialResolution, TimeInterval,
234
    };
235
    use geoengine_datatypes::raster::{
236
        Grid2D, GridOrEmpty, MapElements, MaskedGrid2D, RasterTile2D, RenameBands, TileInformation,
237
        TilingSpecification,
238
    };
239
    use geoengine_datatypes::spatial_reference::SpatialReference;
240
    use geoengine_datatypes::util::test::TestDefault;
241

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

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

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

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

274
    #[test]
275
    fn serialize_params_no_data() {
1✔
276
        let s = r#"{"expression":"1*A","outputType":"F64","outputBand":null,"mapNoData":false}"#;
1✔
277

1✔
278
        assert_eq!(
1✔
279
            s,
1✔
280
            serde_json::to_string(&ExpressionParams {
1✔
281
                expression: "1*A".to_owned(),
1✔
282
                output_type: RasterDataType::F64,
1✔
283
                output_band: None,
1✔
284
                map_no_data: false,
1✔
285
            })
1✔
286
            .unwrap()
1✔
287
        );
1✔
288
    }
1✔
289

290
    #[tokio::test]
291
    async fn basic_unary() {
1✔
292
        let tile_size_in_pixels = [3, 2].into();
1✔
293
        let tiling_specification = TilingSpecification {
1✔
294
            origin_coordinate: [0.0, 0.0].into(),
1✔
295
            tile_size_in_pixels,
1✔
296
        };
1✔
297

1✔
298
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
299

1✔
300
        let raster_a = make_raster(Some(3));
1✔
301

1✔
302
        let o = Expression {
1✔
303
            params: ExpressionParams {
1✔
304
                expression: "2 * A".to_string(),
1✔
305
                output_type: RasterDataType::I8,
1✔
306
                output_band: None,
1✔
307
                map_no_data: false,
1✔
308
            },
1✔
309
            sources: SingleRasterSource { raster: raster_a },
1✔
310
        }
1✔
311
        .boxed()
1✔
312
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
313
        .await
1✔
314
        .unwrap();
1✔
315

1✔
316
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
317

1✔
318
        let ctx = MockQueryContext::new(1.into());
1✔
319
        let result_stream = processor
1✔
320
            .query(
1✔
321
                RasterQueryRectangle {
1✔
322
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
323
                        (0., 3.).into(),
1✔
324
                        (2., 0.).into(),
1✔
325
                    ),
1✔
326
                    time_interval: Default::default(),
1✔
327
                    spatial_resolution: SpatialResolution::one(),
1✔
328
                    attributes: BandSelection::first(),
1✔
329
                },
1✔
330
                &ctx,
1✔
331
            )
1✔
332
            .await
1✔
333
            .unwrap();
1✔
334

1✔
335
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
1✔
336

1✔
337
        assert_eq!(result.len(), 1);
1✔
338

1✔
339
        assert_eq!(
1✔
340
            result[0].as_ref().unwrap().grid_array,
1✔
341
            GridOrEmpty::from(
1✔
342
                MaskedGrid2D::new(
1✔
343
                    Grid2D::new([3, 2].into(), vec![2, 4, 0, 8, 10, 12],).unwrap(),
1✔
344
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
345
                )
1✔
346
                .unwrap()
1✔
347
            )
1✔
348
        );
1✔
349
    }
1✔
350

351
    #[tokio::test]
352
    async fn unary_map_no_data() {
1✔
353
        let tile_size_in_pixels = [3, 2].into();
1✔
354
        let tiling_specification = TilingSpecification {
1✔
355
            origin_coordinate: [0.0, 0.0].into(),
1✔
356
            tile_size_in_pixels,
1✔
357
        };
1✔
358

1✔
359
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
360

1✔
361
        let raster_a = make_raster(Some(3));
1✔
362

1✔
363
        let o = Expression {
1✔
364
            params: ExpressionParams {
1✔
365
                expression: "2 * A".to_string(),
1✔
366
                output_type: RasterDataType::I8,
1✔
367
                output_band: None,
1✔
368
                map_no_data: true,
1✔
369
            },
1✔
370
            sources: SingleRasterSource { raster: raster_a },
1✔
371
        }
1✔
372
        .boxed()
1✔
373
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
374
        .await
1✔
375
        .unwrap();
1✔
376

1✔
377
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
378

1✔
379
        let ctx = MockQueryContext::new(1.into());
1✔
380
        let result_stream = processor
1✔
381
            .query(
1✔
382
                RasterQueryRectangle {
1✔
383
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
384
                        (0., 3.).into(),
1✔
385
                        (2., 0.).into(),
1✔
386
                    ),
1✔
387
                    time_interval: Default::default(),
1✔
388
                    spatial_resolution: SpatialResolution::one(),
1✔
389
                    attributes: BandSelection::first(),
1✔
390
                },
1✔
391
                &ctx,
1✔
392
            )
1✔
393
            .await
1✔
394
            .unwrap();
1✔
395

1✔
396
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
1✔
397

1✔
398
        assert_eq!(result.len(), 1);
1✔
399

1✔
400
        assert_eq!(
1✔
401
            result[0].as_ref().unwrap().grid_array,
1✔
402
            GridOrEmpty::from(
1✔
403
                MaskedGrid2D::new(
1✔
404
                    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✔
405
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
406
                )
1✔
407
                .unwrap()
1✔
408
            )
1✔
409
        );
1✔
410
    }
1✔
411

412
    #[tokio::test]
413
    async fn basic_binary() {
1✔
414
        let tile_size_in_pixels = [3, 2].into();
1✔
415
        let tiling_specification = TilingSpecification {
1✔
416
            origin_coordinate: [0.0, 0.0].into(),
1✔
417
            tile_size_in_pixels,
1✔
418
        };
1✔
419

1✔
420
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
421

1✔
422
        let raster_a = make_raster(None);
1✔
423
        let raster_b = make_raster(None);
1✔
424

1✔
425
        let o = Expression {
1✔
426
            params: ExpressionParams {
1✔
427
                expression: "A+B".to_string(),
1✔
428
                output_type: RasterDataType::I8,
1✔
429
                output_band: None,
1✔
430
                map_no_data: false,
1✔
431
            },
1✔
432
            sources: SingleRasterSource {
1✔
433
                raster: RasterStacker {
1✔
434
                    params: RasterStackerParams {
1✔
435
                        rename_bands: RenameBands::Default,
1✔
436
                    },
1✔
437
                    sources: MultipleRasterSources {
1✔
438
                        rasters: vec![raster_a, raster_b],
1✔
439
                    },
1✔
440
                }
1✔
441
                .boxed(),
1✔
442
            },
1✔
443
        }
1✔
444
        .boxed()
1✔
445
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
446
        .await
1✔
447
        .unwrap();
1✔
448

1✔
449
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
450

1✔
451
        let ctx = MockQueryContext::new(1.into());
1✔
452
        let result_stream = processor
1✔
453
            .query(
1✔
454
                RasterQueryRectangle {
1✔
455
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
456
                        (0., 3.).into(),
1✔
457
                        (2., 0.).into(),
1✔
458
                    ),
1✔
459
                    time_interval: Default::default(),
1✔
460
                    spatial_resolution: SpatialResolution::one(),
1✔
461
                    attributes: BandSelection::first(),
1✔
462
                },
1✔
463
                &ctx,
1✔
464
            )
1✔
465
            .await
1✔
466
            .unwrap();
1✔
467

1✔
468
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
1✔
469

1✔
470
        assert_eq!(result.len(), 1);
1✔
471

1✔
472
        assert_eq!(
1✔
473
            result[0].as_ref().unwrap().grid_array,
1✔
474
            Grid2D::new([3, 2].into(), vec![2, 4, 6, 8, 10, 12],)
1✔
475
                .unwrap()
1✔
476
                .into()
1✔
477
        );
1✔
478
    }
1✔
479

480
    #[tokio::test]
481
    async fn basic_coalesce() {
1✔
482
        let tile_size_in_pixels = [3, 2].into();
1✔
483
        let tiling_specification = TilingSpecification {
1✔
484
            origin_coordinate: [0.0, 0.0].into(),
1✔
485
            tile_size_in_pixels,
1✔
486
        };
1✔
487

1✔
488
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
489

1✔
490
        let raster_a = make_raster(Some(3));
1✔
491
        let raster_b = make_raster(None);
1✔
492

1✔
493
        let o = Expression {
1✔
494
            params: ExpressionParams {
1✔
495
                expression: "if A IS NODATA {
1✔
496
                       B * 2
1✔
497
                   } else if A == 6 {
1✔
498
                       NODATA
1✔
499
                   } else {
1✔
500
                       A
1✔
501
                   }"
1✔
502
                .to_string(),
1✔
503
                output_type: RasterDataType::I8,
1✔
504
                output_band: None,
1✔
505
                map_no_data: true,
1✔
506
            },
1✔
507
            sources: SingleRasterSource {
1✔
508
                raster: RasterStacker {
1✔
509
                    params: RasterStackerParams {
1✔
510
                        rename_bands: RenameBands::Default,
1✔
511
                    },
1✔
512
                    sources: MultipleRasterSources {
1✔
513
                        rasters: vec![raster_a, raster_b],
1✔
514
                    },
1✔
515
                }
1✔
516
                .boxed(),
1✔
517
            },
1✔
518
        }
1✔
519
        .boxed()
1✔
520
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
521
        .await
1✔
522
        .unwrap();
1✔
523

1✔
524
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
525

1✔
526
        let ctx = MockQueryContext::new(1.into());
1✔
527
        let result_stream = processor
1✔
528
            .query(
1✔
529
                RasterQueryRectangle {
1✔
530
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
531
                        (0., 3.).into(),
1✔
532
                        (2., 0.).into(),
1✔
533
                    ),
1✔
534
                    time_interval: Default::default(),
1✔
535
                    spatial_resolution: SpatialResolution::one(),
1✔
536
                    attributes: BandSelection::first(),
1✔
537
                },
1✔
538
                &ctx,
1✔
539
            )
1✔
540
            .await
1✔
541
            .unwrap();
1✔
542

1✔
543
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
1✔
544

1✔
545
        assert_eq!(result.len(), 1);
1✔
546

1✔
547
        assert_eq!(
1✔
548
            result[0].as_ref().unwrap().grid_array,
1✔
549
            GridOrEmpty::from(
1✔
550
                MaskedGrid2D::new(
1✔
551
                    Grid2D::new([3, 2].into(), vec![1, 2, 6, 4, 5, 0],).unwrap(),
1✔
552
                    Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false],).unwrap()
1✔
553
                )
1✔
554
                .unwrap()
1✔
555
            )
1✔
556
        );
1✔
557
    }
1✔
558

559
    #[tokio::test]
560
    async fn basic_ternary() {
1✔
561
        let no_data_value = 3;
1✔
562
        let no_data_value_option = Some(no_data_value);
1✔
563

1✔
564
        let tile_size_in_pixels = [3, 2].into();
1✔
565
        let tiling_specification = TilingSpecification {
1✔
566
            origin_coordinate: [0.0, 0.0].into(),
1✔
567
            tile_size_in_pixels,
1✔
568
        };
1✔
569

1✔
570
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
571

1✔
572
        let raster_a = make_raster(no_data_value_option);
1✔
573
        let raster_b = make_raster(no_data_value_option);
1✔
574
        let raster_c = make_raster(no_data_value_option);
1✔
575

1✔
576
        let o = Expression {
1✔
577
            params: ExpressionParams {
1✔
578
                expression: "A+B+C".to_string(),
1✔
579
                output_type: RasterDataType::I8,
1✔
580
                output_band: None,
1✔
581
                map_no_data: false,
1✔
582
            },
1✔
583
            sources: SingleRasterSource {
1✔
584
                raster: RasterStacker {
1✔
585
                    params: RasterStackerParams {
1✔
586
                        rename_bands: RenameBands::Default,
1✔
587
                    },
1✔
588
                    sources: MultipleRasterSources {
1✔
589
                        rasters: vec![raster_a, raster_b, raster_c],
1✔
590
                    },
1✔
591
                }
1✔
592
                .boxed(),
1✔
593
            },
1✔
594
        }
1✔
595
        .boxed()
1✔
596
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
597
        .await
1✔
598
        .unwrap();
1✔
599

1✔
600
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
601

1✔
602
        let ctx = MockQueryContext::new(1.into());
1✔
603
        let result_stream = processor
1✔
604
            .query(
1✔
605
                RasterQueryRectangle {
1✔
606
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
607
                        (0., 3.).into(),
1✔
608
                        (2., 0.).into(),
1✔
609
                    ),
1✔
610
                    time_interval: Default::default(),
1✔
611
                    spatial_resolution: SpatialResolution::one(),
1✔
612
                    attributes: BandSelection::first(),
1✔
613
                },
1✔
614
                &ctx,
1✔
615
            )
1✔
616
            .await
1✔
617
            .unwrap();
1✔
618

1✔
619
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
1✔
620

1✔
621
        assert_eq!(result.len(), 1);
1✔
622

1✔
623
        let first_result = result[0].as_ref().unwrap();
1✔
624

1✔
625
        assert!(!first_result.is_empty());
1✔
626

1✔
627
        let grid = match &first_result.grid_array {
1✔
628
            GridOrEmpty::Grid(g) => g,
1✔
629
            GridOrEmpty::Empty(_) => panic!(),
1✔
630
        };
1✔
631

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

1✔
634
        assert_eq!(
1✔
635
            res,
1✔
636
            [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✔
637
        );
1✔
638
    }
1✔
639

640
    #[tokio::test]
641
    async fn octave_inputs() {
1✔
642
        let no_data_value = 0;
1✔
643
        let no_data_value_option = Some(no_data_value);
1✔
644

1✔
645
        let tile_size_in_pixels = [3, 2].into();
1✔
646
        let tiling_specification = TilingSpecification {
1✔
647
            origin_coordinate: [0.0, 0.0].into(),
1✔
648
            tile_size_in_pixels,
1✔
649
        };
1✔
650

1✔
651
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
652

1✔
653
        let raster_a = make_raster(no_data_value_option);
1✔
654
        let raster_b = make_raster(no_data_value_option);
1✔
655
        let raster_c = make_raster(no_data_value_option);
1✔
656
        let raster_d = make_raster(no_data_value_option);
1✔
657
        let raster_e = make_raster(no_data_value_option);
1✔
658
        let raster_f = make_raster(no_data_value_option);
1✔
659
        let raster_g = make_raster(no_data_value_option);
1✔
660
        let raster_h = make_raster(no_data_value_option);
1✔
661

1✔
662
        let o = Expression {
1✔
663
            params: ExpressionParams {
1✔
664
                expression: "A+B+C+D+E+F+G+H".to_string(),
1✔
665
                output_type: RasterDataType::I8,
1✔
666
                output_band: None,
1✔
667
                map_no_data: false,
1✔
668
            },
1✔
669
            sources: SingleRasterSource {
1✔
670
                raster: RasterStacker {
1✔
671
                    params: RasterStackerParams {
1✔
672
                        rename_bands: RenameBands::Default,
1✔
673
                    },
1✔
674
                    sources: MultipleRasterSources {
1✔
675
                        rasters: vec![
1✔
676
                            raster_a, raster_b, raster_c, raster_d, raster_e, raster_f, raster_g,
1✔
677
                            raster_h,
1✔
678
                        ],
1✔
679
                    },
1✔
680
                }
1✔
681
                .boxed(),
1✔
682
            },
1✔
683
        }
1✔
684
        .boxed()
1✔
685
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
686
        .await
1✔
687
        .unwrap();
1✔
688

1✔
689
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
690

1✔
691
        let ctx = MockQueryContext::new(1.into());
1✔
692
        let result_stream = processor
1✔
693
            .query(
1✔
694
                RasterQueryRectangle {
1✔
695
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
696
                        (0., 3.).into(),
1✔
697
                        (2., 0.).into(),
1✔
698
                    ),
1✔
699
                    time_interval: Default::default(),
1✔
700
                    spatial_resolution: SpatialResolution::one(),
1✔
701
                    attributes: BandSelection::first(),
1✔
702
                },
1✔
703
                &ctx,
1✔
704
            )
1✔
705
            .await
1✔
706
            .unwrap();
1✔
707

1✔
708
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
1✔
709

1✔
710
        assert_eq!(result.len(), 1);
1✔
711

1✔
712
        assert_eq!(
1✔
713
            result[0].as_ref().unwrap().grid_array,
1✔
714
            Grid2D::new([3, 2].into(), vec![8, 16, 24, 32, 40, 48],)
1✔
715
                .unwrap()
1✔
716
                .into()
1✔
717
        );
1✔
718
    }
1✔
719

720
    #[tokio::test]
721
    async fn it_classifies() {
1✔
722
        let tile_size_in_pixels = [3, 2].into();
1✔
723
        let tiling_specification = TilingSpecification {
1✔
724
            origin_coordinate: [0.0, 0.0].into(),
1✔
725
            tile_size_in_pixels,
1✔
726
        };
1✔
727

1✔
728
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
729

1✔
730
        let operator = Expression {
1✔
731
            params: ExpressionParams {
1✔
732
                expression: "if A <= 1 { 0 } else if A <= 3 { 1 } else { 2 }".to_string(),
1✔
733
                output_type: RasterDataType::U8,
1✔
734
                output_band: Some(RasterBandDescriptor::new(
1✔
735
                    "a class".into(),
1✔
736
                    Measurement::classification(
1✔
737
                        "classes".into(),
1✔
738
                        [
1✔
739
                            (0, "Class A".into()),
1✔
740
                            (1, "Class B".into()),
1✔
741
                            (2, "Class C".into()),
1✔
742
                        ]
1✔
743
                        .into_iter()
1✔
744
                        .collect(),
1✔
745
                    ),
1✔
746
                )),
1✔
747
                map_no_data: false,
1✔
748
            },
1✔
749
            sources: SingleRasterSource {
1✔
750
                raster: RasterStacker {
1✔
751
                    params: RasterStackerParams {
1✔
752
                        rename_bands: RenameBands::Default,
1✔
753
                    },
1✔
754
                    sources: MultipleRasterSources {
1✔
755
                        rasters: vec![make_raster(None)],
1✔
756
                    },
1✔
757
                }
1✔
758
                .boxed(),
1✔
759
            },
1✔
760
        }
1✔
761
        .boxed()
1✔
762
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
763
        .await
1✔
764
        .unwrap();
1✔
765

1✔
766
        let processor = operator.query_processor().unwrap().get_u8().unwrap();
1✔
767

1✔
768
        let ctx = MockQueryContext::new(1.into());
1✔
769
        let result_stream = processor
1✔
770
            .query(
1✔
771
                RasterQueryRectangle {
1✔
772
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
773
                        (0., 3.).into(),
1✔
774
                        (2., 0.).into(),
1✔
775
                    ),
1✔
776
                    time_interval: Default::default(),
1✔
777
                    spatial_resolution: SpatialResolution::one(),
1✔
778
                    attributes: BandSelection::first(),
1✔
779
                },
1✔
780
                &ctx,
1✔
781
            )
1✔
782
            .await
1✔
783
            .unwrap();
1✔
784

1✔
785
        let result: Vec<Result<RasterTile2D<u8>>> = result_stream.collect().await;
1✔
786

1✔
787
        assert_eq!(result.len(), 1);
1✔
788

1✔
789
        let first_result = result[0].as_ref().unwrap();
1✔
790

1✔
791
        assert!(!first_result.is_empty());
1✔
792

1✔
793
        let grid = match &first_result.grid_array {
1✔
794
            GridOrEmpty::Grid(g) => g,
1✔
795
            GridOrEmpty::Empty(_) => panic!(),
1✔
796
        };
1✔
797

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

1✔
800
        assert_eq!(res, [Some(0), Some(1), Some(1), Some(2), Some(2), Some(2)]);
1✔
801
    }
1✔
802

803
    #[tokio::test]
804
    async fn test_functions() {
1✔
805
        let no_data_value = 0;
1✔
806
        let tile_size_in_pixels = [3, 2].into();
1✔
807
        let tiling_specification = TilingSpecification {
1✔
808
            origin_coordinate: [0.0, 0.0].into(),
1✔
809
            tile_size_in_pixels,
1✔
810
        };
1✔
811

1✔
812
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
813

1✔
814
        let raster_a = make_raster(Some(no_data_value));
1✔
815

1✔
816
        let o = Expression {
1✔
817
            params: ExpressionParams {
1✔
818
                expression: "min(A * pi(), 10)".to_string(),
1✔
819
                output_type: RasterDataType::I8,
1✔
820
                output_band: None,
1✔
821
                map_no_data: false,
1✔
822
            },
1✔
823
            sources: SingleRasterSource {
1✔
824
                raster: RasterStacker {
1✔
825
                    params: RasterStackerParams {
1✔
826
                        rename_bands: RenameBands::Default,
1✔
827
                    },
1✔
828
                    sources: MultipleRasterSources {
1✔
829
                        rasters: vec![raster_a],
1✔
830
                    },
1✔
831
                }
1✔
832
                .boxed(),
1✔
833
            },
1✔
834
        }
1✔
835
        .boxed()
1✔
836
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
837
        .await
1✔
838
        .unwrap();
1✔
839

1✔
840
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
841

1✔
842
        let ctx = MockQueryContext::new(1.into());
1✔
843
        let result_stream = processor
1✔
844
            .query(
1✔
845
                RasterQueryRectangle {
1✔
846
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
847
                        (0., 3.).into(),
1✔
848
                        (2., 0.).into(),
1✔
849
                    ),
1✔
850
                    time_interval: Default::default(),
1✔
851
                    spatial_resolution: SpatialResolution::one(),
1✔
852
                    attributes: BandSelection::first(),
1✔
853
                },
1✔
854
                &ctx,
1✔
855
            )
1✔
856
            .await
1✔
857
            .unwrap();
1✔
858

1✔
859
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
1✔
860

1✔
861
        assert_eq!(result.len(), 1);
1✔
862

1✔
863
        assert_eq!(
1✔
864
            result[0].as_ref().unwrap().grid_array,
1✔
865
            Grid2D::new([3, 2].into(), vec![3, 6, 9, 10, 10, 10],)
1✔
866
                .unwrap()
1✔
867
                .into()
1✔
868
        );
1✔
869
    }
1✔
870

871
    fn make_raster(no_data_value: Option<i8>) -> Box<dyn RasterOperator> {
19✔
872
        make_raster_with_cache_hint(no_data_value, CacheHint::no_cache())
19✔
873
    }
19✔
874

875
    fn make_raster_with_cache_hint(
25✔
876
        no_data_value: Option<i8>,
25✔
877
        cache_hint: CacheHint,
25✔
878
    ) -> Box<dyn RasterOperator> {
25✔
879
        let raster = Grid2D::<i8>::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6]).unwrap();
25✔
880

881
        let real_raster = if let Some(no_data_value) = no_data_value {
25✔
882
            MaskedGrid2D::from(raster)
21✔
883
                .map_elements(|e: Option<i8>| e.filter(|v| *v != no_data_value))
126✔
884
                .into()
21✔
885
        } else {
886
            GridOrEmpty::from(raster)
4✔
887
        };
888

889
        let raster_tile = RasterTile2D::new_with_tile_info(
25✔
890
            TimeInterval::default(),
25✔
891
            TileInformation {
25✔
892
                global_tile_position: [-1, 0].into(),
25✔
893
                tile_size_in_pixels: [3, 2].into(),
25✔
894
                global_geo_transform: TestDefault::test_default(),
25✔
895
            },
25✔
896
            0,
25✔
897
            real_raster,
25✔
898
            cache_hint,
25✔
899
        );
25✔
900

25✔
901
        MockRasterSource {
25✔
902
            params: MockRasterSourceParams {
25✔
903
                data: vec![raster_tile],
25✔
904
                result_descriptor: RasterResultDescriptor {
25✔
905
                    data_type: RasterDataType::I8,
25✔
906
                    spatial_reference: SpatialReference::epsg_4326().into(),
25✔
907
                    time: None,
25✔
908
                    bbox: None,
25✔
909
                    resolution: None,
25✔
910
                    bands: RasterBandDescriptors::new_single_band(),
25✔
911
                },
25✔
912
            },
25✔
913
        }
25✔
914
        .boxed()
25✔
915
    }
25✔
916

917
    #[tokio::test]
918
    async fn it_attaches_cache_hint_1() {
1✔
919
        let no_data_value = 0;
1✔
920
        let tile_size_in_pixels = [3, 2].into();
1✔
921
        let tiling_specification = TilingSpecification {
1✔
922
            origin_coordinate: [0.0, 0.0].into(),
1✔
923
            tile_size_in_pixels,
1✔
924
        };
1✔
925

1✔
926
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
927

1✔
928
        let cache_hint = CacheHint::seconds(1234);
1✔
929
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint);
1✔
930

1✔
931
        let o = Expression {
1✔
932
            params: ExpressionParams {
1✔
933
                expression: "min(A * pi(), 10)".to_string(),
1✔
934
                output_type: RasterDataType::I8,
1✔
935
                output_band: None,
1✔
936
                map_no_data: false,
1✔
937
            },
1✔
938
            sources: SingleRasterSource {
1✔
939
                raster: RasterStacker {
1✔
940
                    params: RasterStackerParams {
1✔
941
                        rename_bands: RenameBands::Default,
1✔
942
                    },
1✔
943
                    sources: MultipleRasterSources {
1✔
944
                        rasters: vec![raster_a],
1✔
945
                    },
1✔
946
                }
1✔
947
                .boxed(),
1✔
948
            },
1✔
949
        }
1✔
950
        .boxed()
1✔
951
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
952
        .await
1✔
953
        .unwrap();
1✔
954

1✔
955
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
956

1✔
957
        let ctx = MockQueryContext::new(1.into());
1✔
958
        let result_stream = processor
1✔
959
            .query(
1✔
960
                RasterQueryRectangle {
1✔
961
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
962
                        (0., 3.).into(),
1✔
963
                        (2., 0.).into(),
1✔
964
                    ),
1✔
965
                    time_interval: Default::default(),
1✔
966
                    spatial_resolution: SpatialResolution::one(),
1✔
967
                    attributes: BandSelection::first(),
1✔
968
                },
1✔
969
                &ctx,
1✔
970
            )
1✔
971
            .await
1✔
972
            .unwrap();
1✔
973

1✔
974
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
1✔
975

1✔
976
        assert_eq!(result.len(), 1);
1✔
977

1✔
978
        assert!(
1✔
979
            result[0].as_ref().unwrap().cache_hint.total_ttl_seconds() > CacheTtlSeconds::new(0)
1✔
980
                && result[0].as_ref().unwrap().cache_hint.total_ttl_seconds()
1✔
981
                    <= cache_hint.total_ttl_seconds()
1✔
982
        );
1✔
983
    }
1✔
984

985
    #[tokio::test]
986
    async fn it_attaches_cache_hint_2() {
1✔
987
        let no_data_value = 0;
1✔
988
        let tile_size_in_pixels = [3, 2].into();
1✔
989
        let tiling_specification = TilingSpecification {
1✔
990
            origin_coordinate: [0.0, 0.0].into(),
1✔
991
            tile_size_in_pixels,
1✔
992
        };
1✔
993

1✔
994
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
995

1✔
996
        let cache_hint_a = CacheHint::seconds(1234);
1✔
997
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint_a);
1✔
998

1✔
999
        let cache_hint_b = CacheHint::seconds(4567);
1✔
1000
        let raster_b = make_raster_with_cache_hint(Some(no_data_value), cache_hint_b);
1✔
1001

1✔
1002
        let o = Expression {
1✔
1003
            params: ExpressionParams {
1✔
1004
                expression: "A + B".to_string(),
1✔
1005
                output_type: RasterDataType::I8,
1✔
1006
                output_band: None,
1✔
1007
                map_no_data: false,
1✔
1008
            },
1✔
1009
            sources: SingleRasterSource {
1✔
1010
                raster: RasterStacker {
1✔
1011
                    params: RasterStackerParams {
1✔
1012
                        rename_bands: RenameBands::Default,
1✔
1013
                    },
1✔
1014
                    sources: MultipleRasterSources {
1✔
1015
                        rasters: vec![raster_a, raster_b],
1✔
1016
                    },
1✔
1017
                }
1✔
1018
                .boxed(),
1✔
1019
            },
1✔
1020
        }
1✔
1021
        .boxed()
1✔
1022
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1023
        .await
1✔
1024
        .unwrap();
1✔
1025

1✔
1026
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1027

1✔
1028
        let ctx = MockQueryContext::new(1.into());
1✔
1029
        let result_stream = processor
1✔
1030
            .query(
1✔
1031
                RasterQueryRectangle {
1✔
1032
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1033
                        (0., 3.).into(),
1✔
1034
                        (2., 0.).into(),
1✔
1035
                    ),
1✔
1036
                    time_interval: Default::default(),
1✔
1037
                    spatial_resolution: SpatialResolution::one(),
1✔
1038
                    attributes: BandSelection::first(),
1✔
1039
                },
1✔
1040
                &ctx,
1✔
1041
            )
1✔
1042
            .await
1✔
1043
            .unwrap();
1✔
1044

1✔
1045
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
1✔
1046

1✔
1047
        assert_eq!(result.len(), 1);
1✔
1048

1✔
1049
        assert_eq!(
1✔
1050
            result[0].as_ref().unwrap().cache_hint.expires(),
1✔
1051
            cache_hint_a.merged(&cache_hint_b).expires()
1✔
1052
        );
1✔
1053
    }
1✔
1054

1055
    #[tokio::test]
1056
    async fn it_attaches_cache_hint_3() {
1✔
1057
        let no_data_value = 0;
1✔
1058
        let tile_size_in_pixels = [3, 2].into();
1✔
1059
        let tiling_specification = TilingSpecification {
1✔
1060
            origin_coordinate: [0.0, 0.0].into(),
1✔
1061
            tile_size_in_pixels,
1✔
1062
        };
1✔
1063

1✔
1064
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1065

1✔
1066
        let cache_hint_a = CacheHint::seconds(1234);
1✔
1067
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint_a);
1✔
1068

1✔
1069
        let cache_hint_b = CacheHint::seconds(4567);
1✔
1070
        let raster_b = make_raster_with_cache_hint(Some(no_data_value), cache_hint_b);
1✔
1071

1✔
1072
        let cache_hint_c = CacheHint::seconds(7891);
1✔
1073
        let raster_c = make_raster_with_cache_hint(Some(no_data_value), cache_hint_c);
1✔
1074

1✔
1075
        let o = Expression {
1✔
1076
            params: ExpressionParams {
1✔
1077
                expression: "A + B".to_string(),
1✔
1078
                output_type: RasterDataType::I8,
1✔
1079
                output_band: None,
1✔
1080
                map_no_data: false,
1✔
1081
            },
1✔
1082
            sources: SingleRasterSource {
1✔
1083
                raster: RasterStacker {
1✔
1084
                    params: RasterStackerParams {
1✔
1085
                        rename_bands: RenameBands::Default,
1✔
1086
                    },
1✔
1087
                    sources: MultipleRasterSources {
1✔
1088
                        rasters: vec![raster_a, raster_b, raster_c],
1✔
1089
                    },
1✔
1090
                }
1✔
1091
                .boxed(),
1✔
1092
            },
1✔
1093
        }
1✔
1094
        .boxed()
1✔
1095
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1096
        .await
1✔
1097
        .unwrap();
1✔
1098

1✔
1099
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1100

1✔
1101
        let ctx = MockQueryContext::new(1.into());
1✔
1102
        let result_stream = processor
1✔
1103
            .query(
1✔
1104
                RasterQueryRectangle {
1✔
1105
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1106
                        (0., 3.).into(),
1✔
1107
                        (2., 0.).into(),
1✔
1108
                    ),
1✔
1109
                    time_interval: Default::default(),
1✔
1110
                    spatial_resolution: SpatialResolution::one(),
1✔
1111
                    attributes: BandSelection::first(),
1✔
1112
                },
1✔
1113
                &ctx,
1✔
1114
            )
1✔
1115
            .await
1✔
1116
            .unwrap();
1✔
1117

1✔
1118
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
1✔
1119

1✔
1120
        assert_eq!(result.len(), 1);
1✔
1121

1✔
1122
        assert_eq!(
1✔
1123
            result[0].as_ref().unwrap().cache_hint.expires(),
1✔
1124
            cache_hint_a
1✔
1125
                .merged(&cache_hint_b)
1✔
1126
                .merged(&cache_hint_c)
1✔
1127
                .expires()
1✔
1128
        );
1✔
1129
    }
1✔
1130
}
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