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

geo-engine / geoengine / 5006008836

pending completion
5006008836

push

github

GitHub
Merge #785 #787

936 of 936 new or added lines in 50 files covered. (100.0%)

96010 of 107707 relevant lines covered (89.14%)

72676.46 hits per line

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

88.01
/operators/src/processing/expression/mod.rs
1
use self::{codegen::ExpressionAst, compiled::LinkedExpression, parser::ExpressionParser};
2
use crate::{
3
    engine::{
4
        CanonicOperatorName, ExecutionContext, InitializedRasterOperator, InitializedSources,
5
        Operator, OperatorData, OperatorName, RasterOperator, RasterQueryProcessor,
6
        RasterResultDescriptor, TypedRasterQueryProcessor, WorkflowOperatorPath,
7
    },
8
    processing::expression::{codegen::Parameter, query_processor::ExpressionQueryProcessor},
9
    util::Result,
10
};
11
use async_trait::async_trait;
12
use futures::try_join;
13
use geoengine_datatypes::{
14
    dataset::DataId,
15
    primitives::{partitions_extent, time_interval_extent, Measurement, SpatialResolution},
16
    raster::RasterDataType,
17
};
18
use serde::{Deserialize, Serialize};
19
use snafu::ensure;
20

21
pub use self::error::ExpressionError;
22

23
mod codegen;
24
mod compiled;
25
mod error;
26
mod functions;
27
mod parser;
28
mod query_processor;
29

30
/// Parameters for the `Expression` operator.
31
/// * The `expression` must only contain simple arithmetic
32
///     calculations.
33
/// * `output_type` is the data type of the produced raster tiles.
34
/// * `output_no_data_value` is the no data value of the output raster
35
/// * `output_measurement` is the measurement description of the output
36
///
37
/// # Warning // TODO
38
/// The operator *currently* only temporally aligns the inputs when there are exactly two sources
39
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
14✔
40
#[serde(rename_all = "camelCase")]
41
pub struct ExpressionParams {
42
    pub expression: String,
43
    pub output_type: RasterDataType,
44
    pub output_measurement: Option<Measurement>,
45
    pub map_no_data: bool,
46
}
47

48
// TODO: rename to `Expression`
49
/// The `Expression` operator calculates an expression for all pixels of the input rasters and
50
/// produces raster tiles of a given output type
51
pub type Expression = Operator<ExpressionParams, ExpressionSources>;
52

53
#[derive(Debug, Clone, Serialize, Deserialize)]
12✔
54
#[allow(clippy::unsafe_derive_deserialize)] // TODO: remove if this warning is a glitch
55
pub struct ExpressionSources {
56
    a: Box<dyn RasterOperator>,
57
    b: Option<Box<dyn RasterOperator>>,
58
    c: Option<Box<dyn RasterOperator>>,
59
    d: Option<Box<dyn RasterOperator>>,
60
    e: Option<Box<dyn RasterOperator>>,
61
    f: Option<Box<dyn RasterOperator>>,
62
    g: Option<Box<dyn RasterOperator>>,
63
    h: Option<Box<dyn RasterOperator>>,
64
}
65

66
impl OperatorData for ExpressionSources {
67
    fn data_ids_collect(&self, data_ids: &mut Vec<DataId>) {
×
68
        for source in self.iter() {
×
69
            source.data_ids_collect(data_ids);
×
70
        }
×
71
    }
×
72
}
73

74
impl ExpressionSources {
75
    pub fn new_a(a: Box<dyn RasterOperator>) -> Self {
1✔
76
        Self {
1✔
77
            a,
1✔
78
            b: None,
1✔
79
            c: None,
1✔
80
            d: None,
1✔
81
            e: None,
1✔
82
            f: None,
1✔
83
            g: None,
1✔
84
            h: None,
1✔
85
        }
1✔
86
    }
1✔
87

88
    pub fn new_a_b(a: Box<dyn RasterOperator>, b: Box<dyn RasterOperator>) -> Self {
2✔
89
        Self {
2✔
90
            a,
2✔
91
            b: Some(b),
2✔
92
            c: None,
2✔
93
            d: None,
2✔
94
            e: None,
2✔
95
            f: None,
2✔
96
            g: None,
2✔
97
            h: None,
2✔
98
        }
2✔
99
    }
2✔
100

101
    pub fn new_a_b_c(
×
102
        a: Box<dyn RasterOperator>,
×
103
        b: Box<dyn RasterOperator>,
×
104
        c: Box<dyn RasterOperator>,
×
105
    ) -> Self {
×
106
        Self {
×
107
            a,
×
108
            b: Some(b),
×
109
            c: Some(c),
×
110
            d: None,
×
111
            e: None,
×
112
            f: None,
×
113
            g: None,
×
114
            h: None,
×
115
        }
×
116
    }
×
117

118
    fn number_of_sources(&self) -> usize {
20✔
119
        self.iter().count()
20✔
120
    }
20✔
121

122
    #[allow(clippy::many_single_char_names)]
123
    async fn initialize(
10✔
124
        self,
10✔
125
        path: WorkflowOperatorPath,
10✔
126
        context: &dyn ExecutionContext,
10✔
127
    ) -> Result<ExpressionInitializedSources> {
10✔
128
        if self.iter().count() != self.iter_consecutive().count() {
10✔
129
            return Err(ExpressionError::SourcesMustBeConsecutive.into());
×
130
        }
10✔
131

132
        let (a, b, c, d, e, f, g, h) = try_join!(
10✔
133
            self.a.initialize(path.clone_and_append(0), context),
11✔
134
            Self::initialize_source(self.b, path.clone_and_append(1), context),
11✔
135
            Self::initialize_source(self.c, path.clone_and_append(2), context),
11✔
136
            Self::initialize_source(self.d, path.clone_and_append(3), context),
11✔
137
            Self::initialize_source(self.e, path.clone_and_append(4), context),
11✔
138
            Self::initialize_source(self.f, path.clone_and_append(5), context),
11✔
139
            Self::initialize_source(self.g, path.clone_and_append(6), context),
11✔
140
            Self::initialize_source(self.h, path.clone_and_append(7), context),
11✔
141
        )?;
11✔
142

143
        Ok(ExpressionInitializedSources {
10✔
144
            a,
10✔
145
            b,
10✔
146
            c,
10✔
147
            d,
10✔
148
            e,
10✔
149
            f,
10✔
150
            g,
10✔
151
            h,
10✔
152
        })
10✔
153
    }
10✔
154

155
    async fn initialize_source(
70✔
156
        source: Option<Box<dyn RasterOperator>>,
70✔
157
        path: WorkflowOperatorPath,
70✔
158
        context: &dyn ExecutionContext,
70✔
159
    ) -> Result<Option<Box<dyn InitializedRasterOperator>>> {
70✔
160
        if let Some(source) = source {
70✔
161
            Ok(Some(source.initialize(path, context).await?))
13✔
162
        } else {
163
            Ok(None)
57✔
164
        }
165
    }
70✔
166

167
    /// Returns all non-empty sources
168
    #[allow(clippy::borrowed_box)]
169
    fn iter(
30✔
170
        &self,
30✔
171
    ) -> std::iter::Flatten<std::array::IntoIter<Option<&Box<dyn RasterOperator>>, 8>> {
30✔
172
        [
30✔
173
            Some(&self.a),
30✔
174
            self.b.as_ref(),
30✔
175
            self.c.as_ref(),
30✔
176
            self.d.as_ref(),
30✔
177
            self.e.as_ref(),
30✔
178
            self.f.as_ref(),
30✔
179
            self.g.as_ref(),
30✔
180
            self.h.as_ref(),
30✔
181
        ]
30✔
182
        .into_iter()
30✔
183
        .flatten()
30✔
184
    }
30✔
185

186
    /// Returns all sources until the first one is empty
187
    fn iter_consecutive(&self) -> impl Iterator<Item = &Box<dyn RasterOperator>> {
10✔
188
        [
10✔
189
            Some(&self.a),
10✔
190
            self.b.as_ref(),
10✔
191
            self.c.as_ref(),
10✔
192
            self.d.as_ref(),
10✔
193
            self.e.as_ref(),
10✔
194
            self.f.as_ref(),
10✔
195
            self.g.as_ref(),
10✔
196
            self.h.as_ref(),
10✔
197
        ]
10✔
198
        .into_iter()
10✔
199
        .map_while(std::convert::identity)
10✔
200
    }
10✔
201
}
202

203
/// Create a parameter name from an index.
204
/// Starts with `A`.
205
///
206
/// ## Note
207
///
208
/// This function only makes sense for indices between 0 and 25.
209
///
210
fn index_to_parameter(index: usize) -> String {
23✔
211
    let index = index as u32;
23✔
212
    let start_index = 'A' as u32;
23✔
213

23✔
214
    let parameter = char::from_u32(start_index + index).unwrap_or_default();
23✔
215

23✔
216
    parameter.to_string()
23✔
217
}
23✔
218

219
#[typetag::serde]
×
220
#[async_trait]
221
impl RasterOperator for Expression {
222
    async fn _initialize(
10✔
223
        self: Box<Self>,
10✔
224
        path: WorkflowOperatorPath,
10✔
225
        context: &dyn crate::engine::ExecutionContext,
10✔
226
    ) -> Result<Box<dyn InitializedRasterOperator>> {
10✔
227
        // TODO: handle more then 2 inputs, i.e. 1-8
228
        ensure!(
10✔
229
            (1..=8).contains(&self.sources.number_of_sources()),
10✔
230
            crate::error::InvalidNumberOfRasterInputs {
×
231
                expected: 1..9,
×
232
                found: self.sources.number_of_sources()
×
233
            }
×
234
        );
235

236
        let name = CanonicOperatorName::from(&self);
10✔
237

10✔
238
        // we refer to rasters by A, B, C, …
10✔
239
        let parameters = (0..self.sources.number_of_sources())
10✔
240
            .map(|i| {
23✔
241
                let parameter = index_to_parameter(i);
23✔
242
                Parameter::Number(parameter.into())
23✔
243
            })
23✔
244
            .collect::<Vec<_>>();
10✔
245

246
        let expression = ExpressionParser::new(&parameters)?.parse(
10✔
247
            "expression", // TODO: generate and store a unique name
10✔
248
            &self.params.expression,
10✔
249
        )?;
10✔
250

251
        let sources = self.sources.initialize_sources(path, context).await?;
10✔
252

253
        let spatial_reference = sources.a.result_descriptor().spatial_reference;
10✔
254

10✔
255
        let in_descriptors = sources
10✔
256
            .iter()
10✔
257
            .map(InitializedRasterOperator::result_descriptor)
10✔
258
            .collect::<Vec<_>>();
10✔
259

260
        for other_spatial_reference in in_descriptors.iter().skip(1).map(|rd| rd.spatial_reference)
13✔
261
        {
262
            ensure!(
13✔
263
                spatial_reference == other_spatial_reference,
13✔
264
                crate::error::InvalidSpatialReference {
×
265
                    expected: spatial_reference,
×
266
                    found: other_spatial_reference,
×
267
                }
×
268
            );
269
        }
270

271
        let time = time_interval_extent(in_descriptors.iter().map(|d| d.time));
11✔
272
        let bbox = partitions_extent(in_descriptors.iter().map(|d| d.bbox));
11✔
273

10✔
274
        let resolution = in_descriptors
10✔
275
            .iter()
10✔
276
            .map(|d| d.resolution)
23✔
277
            .reduce(|a, b| match (a, b) {
13✔
278
                (Some(a), Some(b)) => {
1✔
279
                    Some(SpatialResolution::new_unchecked(a.x.min(b.x), a.y.min(b.y)))
1✔
280
                }
281
                _ => None,
12✔
282
            })
13✔
283
            .flatten();
10✔
284

10✔
285
        let result_descriptor = RasterResultDescriptor {
10✔
286
            data_type: self.params.output_type,
10✔
287
            spatial_reference,
10✔
288
            measurement: self
10✔
289
                .params
10✔
290
                .output_measurement
10✔
291
                .as_ref()
10✔
292
                .map_or(Measurement::Unitless, Measurement::clone),
10✔
293
            time,
10✔
294
            bbox,
10✔
295
            resolution,
10✔
296
        };
10✔
297

10✔
298
        let initialized_operator = InitializedExpression {
10✔
299
            name,
10✔
300
            result_descriptor,
10✔
301
            sources,
10✔
302
            expression,
10✔
303
            map_no_data: self.params.map_no_data,
10✔
304
        };
10✔
305

10✔
306
        Ok(initialized_operator.boxed())
10✔
307
    }
20✔
308

309
    span_fn!(Expression);
×
310
}
311

312
impl OperatorName for Expression {
313
    const TYPE_NAME: &'static str = "Expression";
314
}
315

316
pub struct InitializedExpression {
317
    name: CanonicOperatorName,
318
    result_descriptor: RasterResultDescriptor,
319
    sources: ExpressionInitializedSources,
320
    expression: ExpressionAst,
321
    map_no_data: bool,
322
}
323

324
pub struct ExpressionInitializedSources {
325
    a: Box<dyn InitializedRasterOperator>,
326
    b: Option<Box<dyn InitializedRasterOperator>>,
327
    c: Option<Box<dyn InitializedRasterOperator>>,
328
    d: Option<Box<dyn InitializedRasterOperator>>,
329
    e: Option<Box<dyn InitializedRasterOperator>>,
330
    f: Option<Box<dyn InitializedRasterOperator>>,
331
    g: Option<Box<dyn InitializedRasterOperator>>,
332
    h: Option<Box<dyn InitializedRasterOperator>>,
333
}
334

335
impl ExpressionInitializedSources {
336
    fn iter(&self) -> impl Iterator<Item = &Box<dyn InitializedRasterOperator>> {
20✔
337
        [
20✔
338
            Some(&self.a),
20✔
339
            self.b.as_ref(),
20✔
340
            self.c.as_ref(),
20✔
341
            self.d.as_ref(),
20✔
342
            self.e.as_ref(),
20✔
343
            self.f.as_ref(),
20✔
344
            self.g.as_ref(),
20✔
345
            self.h.as_ref(),
20✔
346
        ]
20✔
347
        .into_iter()
20✔
348
        .flatten()
20✔
349
    }
20✔
350
}
351

352
#[async_trait]
353
impl InitializedSources<ExpressionInitializedSources> for ExpressionSources {
354
    async fn initialize_sources(
10✔
355
        self,
10✔
356
        path: WorkflowOperatorPath,
10✔
357
        context: &dyn ExecutionContext,
10✔
358
    ) -> Result<ExpressionInitializedSources> {
10✔
359
        self.initialize(path, context).await
10✔
360
    }
20✔
361
}
362

363
#[allow(clippy::many_single_char_names, clippy::too_many_lines)]
364
impl InitializedRasterOperator for InitializedExpression {
365
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor> {
10✔
366
        let output_type = self.result_descriptor().data_type;
10✔
367

368
        let expression = LinkedExpression::new(&self.expression)?;
10✔
369

370
        let query_processors: Vec<TypedRasterQueryProcessor> = self
10✔
371
            .sources
10✔
372
            .iter()
10✔
373
            .map(InitializedRasterOperator::query_processor)
10✔
374
            .collect::<Result<_>>()?;
10✔
375

376
        Ok(match query_processors.len() {
10✔
377
            1 => {
378
                let [a] = <[_; 1]>::try_from(query_processors).expect("len previously checked");
4✔
379
                let query_processor = a.into_f64();
4✔
380
                call_generic_raster_processor!(
×
381
                    output_type,
4✔
382
                    ExpressionQueryProcessor::new(expression, query_processor, self.map_no_data)
4✔
383
                        .boxed()
4✔
384
                )
385

386
                // TODO: We could save prior conversions by monomophizing the differnt expressions
387
                //       However, this would lead to lots of compile symbols and to different results than using the
388
                //       variants with more than one raster.
389
                //
390
                // call_on_generic_raster_processor!(a, p_a => {
391
                //     call_generic_raster_processor!(
392
                //         output_type,
393
                //         ExpressionQueryProcessor::new(
394
                //             expression,
395
                //             p_a,
396
                //             output_no_data_value.as_(),
397
                //             self.map_no_data,
398
                //         ).boxed()
399
                //     )
400
                // })
401
            }
402
            2 => {
403
                let [a, b] = <[_; 2]>::try_from(query_processors).expect("len previously checked");
4✔
404
                let query_processors = (a.into_f64(), b.into_f64());
4✔
405
                call_generic_raster_processor!(
2✔
406
                    output_type,
4✔
407
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
4✔
408
                        .boxed()
4✔
409
                )
410

411
                // TODO: We could save prior conversions by monomophizing the differnt expressions
412
                //       However, this would lead to lots of compile symbols, e.g., 10x10x10 for this case
413
                //
414
                // call_on_bi_generic_raster_processor!(a, b, (p_a, p_b) => {
415
                //     call_generic_raster_processor!(
416
                //         output_type,
417
                //         ExpressionQueryProcessor::new(
418
                //             expression,
419
                //             (p_a, p_b),
420
                //             output_no_data_value.as_(),
421
                //             self.map_no_data,
422
                //         ).boxed()
423
                //     )
424
                // })
425
            }
426
            3 => {
427
                let [a, b, c] =
1✔
428
                    <[_; 3]>::try_from(query_processors).expect("len previously checked");
1✔
429
                let query_processors = [a.into_f64(), b.into_f64(), c.into_f64()];
1✔
430
                call_generic_raster_processor!(
×
431
                    output_type,
1✔
432
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
1✔
433
                        .boxed()
1✔
434
                )
435
            }
436
            4 => {
437
                let [a, b, c, d] =
×
438
                    <[_; 4]>::try_from(query_processors).expect("len previously checked");
×
439
                let query_processors = [a.into_f64(), b.into_f64(), c.into_f64(), d.into_f64()];
×
440
                call_generic_raster_processor!(
×
441
                    output_type,
×
442
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
×
443
                        .boxed()
×
444
                )
445
            }
446
            5 => {
447
                let [a, b, c, d, e] =
×
448
                    <[_; 5]>::try_from(query_processors).expect("len previously checked");
×
449
                let query_processors = [
×
450
                    a.into_f64(),
×
451
                    b.into_f64(),
×
452
                    c.into_f64(),
×
453
                    d.into_f64(),
×
454
                    e.into_f64(),
×
455
                ];
×
456
                call_generic_raster_processor!(
×
457
                    output_type,
×
458
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
×
459
                        .boxed()
×
460
                )
461
            }
462
            6 => {
463
                let [a, b, c, d, e, f] =
×
464
                    <[_; 6]>::try_from(query_processors).expect("len previously checked");
×
465
                let query_processors = [
×
466
                    a.into_f64(),
×
467
                    b.into_f64(),
×
468
                    c.into_f64(),
×
469
                    d.into_f64(),
×
470
                    e.into_f64(),
×
471
                    f.into_f64(),
×
472
                ];
×
473
                call_generic_raster_processor!(
×
474
                    output_type,
×
475
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
×
476
                        .boxed()
×
477
                )
478
            }
479

480
            7 => {
481
                let [a, b, c, d, e, f, g] =
×
482
                    <[_; 7]>::try_from(query_processors).expect("len previously checked");
×
483
                let query_processors = [
×
484
                    a.into_f64(),
×
485
                    b.into_f64(),
×
486
                    c.into_f64(),
×
487
                    d.into_f64(),
×
488
                    e.into_f64(),
×
489
                    f.into_f64(),
×
490
                    g.into_f64(),
×
491
                ];
×
492
                call_generic_raster_processor!(
×
493
                    output_type,
×
494
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
×
495
                        .boxed()
×
496
                )
497
            }
498
            8 => {
499
                let [a, b, c, d, e, f, g, h] =
1✔
500
                    <[_; 8]>::try_from(query_processors).expect("len previously checked");
1✔
501
                let query_processors = [
1✔
502
                    a.into_f64(),
1✔
503
                    b.into_f64(),
1✔
504
                    c.into_f64(),
1✔
505
                    d.into_f64(),
1✔
506
                    e.into_f64(),
1✔
507
                    f.into_f64(),
1✔
508
                    g.into_f64(),
1✔
509
                    h.into_f64(),
1✔
510
                ];
1✔
511
                call_generic_raster_processor!(
×
512
                    output_type,
1✔
513
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
1✔
514
                        .boxed()
1✔
515
                )
516
            }
517
            _ => return Err(crate::error::Error::InvalidNumberOfExpressionInputs),
×
518
        })
519
    }
10✔
520

521
    fn result_descriptor(&self) -> &RasterResultDescriptor {
13✔
522
        &self.result_descriptor
13✔
523
    }
13✔
524

525
    fn canonic_name(&self) -> CanonicOperatorName {
×
526
        self.name.clone()
×
527
    }
×
528
}
529

530
#[cfg(test)]
531
mod tests {
532
    use super::*;
533
    use crate::engine::{MockExecutionContext, MockQueryContext, QueryProcessor};
534
    use crate::mock::{MockRasterSource, MockRasterSourceParams};
535
    use futures::StreamExt;
536
    use geoengine_datatypes::primitives::{
537
        Measurement, RasterQueryRectangle, SpatialPartition2D, SpatialResolution, TimeInterval,
538
    };
539
    use geoengine_datatypes::raster::{
540
        Grid2D, GridOrEmpty, MapElements, MaskedGrid2D, RasterTile2D, TileInformation,
541
        TilingSpecification,
542
    };
543
    use geoengine_datatypes::spatial_reference::SpatialReference;
544
    use geoengine_datatypes::util::test::TestDefault;
545

546
    #[test]
1✔
547
    fn deserialize_params() {
1✔
548
        let s =
1✔
549
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
550

1✔
551
        assert_eq!(
1✔
552
            serde_json::from_str::<ExpressionParams>(s).unwrap(),
1✔
553
            ExpressionParams {
1✔
554
                expression: "1*A".to_owned(),
1✔
555
                output_type: RasterDataType::F64,
1✔
556
                output_measurement: None,
1✔
557
                map_no_data: false,
1✔
558
            }
1✔
559
        );
1✔
560
    }
1✔
561

562
    #[test]
1✔
563
    fn serialize_params() {
1✔
564
        let s =
1✔
565
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
566

1✔
567
        assert_eq!(
1✔
568
            s,
1✔
569
            serde_json::to_string(&ExpressionParams {
1✔
570
                expression: "1*A".to_owned(),
1✔
571
                output_type: RasterDataType::F64,
1✔
572
                output_measurement: None,
1✔
573
                map_no_data: false,
1✔
574
            })
1✔
575
            .unwrap()
1✔
576
        );
1✔
577
    }
1✔
578

579
    #[test]
1✔
580
    fn serialize_params_no_data() {
1✔
581
        let s =
1✔
582
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
583

1✔
584
        assert_eq!(
1✔
585
            s,
1✔
586
            serde_json::to_string(&ExpressionParams {
1✔
587
                expression: "1*A".to_owned(),
1✔
588
                output_type: RasterDataType::F64,
1✔
589
                output_measurement: None,
1✔
590
                map_no_data: false,
1✔
591
            })
1✔
592
            .unwrap()
1✔
593
        );
1✔
594
    }
1✔
595

596
    #[tokio::test]
1✔
597
    async fn basic_unary() {
1✔
598
        let tile_size_in_pixels = [3, 2].into();
1✔
599
        let tiling_specification = TilingSpecification {
1✔
600
            origin_coordinate: [0.0, 0.0].into(),
1✔
601
            tile_size_in_pixels,
1✔
602
        };
1✔
603

1✔
604
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
605

1✔
606
        let raster_a = make_raster(Some(3));
1✔
607

608
        let o = Expression {
1✔
609
            params: ExpressionParams {
1✔
610
                expression: "2 * A".to_string(),
1✔
611
                output_type: RasterDataType::I8,
1✔
612
                output_measurement: Some(Measurement::Unitless),
1✔
613
                map_no_data: false,
1✔
614
            },
1✔
615
            sources: ExpressionSources {
1✔
616
                a: raster_a,
1✔
617
                b: None,
1✔
618
                c: None,
1✔
619
                d: None,
1✔
620
                e: None,
1✔
621
                f: None,
1✔
622
                g: None,
1✔
623
                h: None,
1✔
624
            },
1✔
625
        }
1✔
626
        .boxed()
1✔
627
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
628
        .await
×
629
        .unwrap();
1✔
630

1✔
631
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
632

1✔
633
        let ctx = MockQueryContext::new(1.into());
1✔
634
        let result_stream = processor
1✔
635
            .query(
1✔
636
                RasterQueryRectangle {
1✔
637
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
638
                        (0., 3.).into(),
1✔
639
                        (2., 0.).into(),
1✔
640
                    ),
1✔
641
                    time_interval: Default::default(),
1✔
642
                    spatial_resolution: SpatialResolution::one(),
1✔
643
                },
1✔
644
                &ctx,
1✔
645
            )
1✔
646
            .await
×
647
            .unwrap();
1✔
648

649
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
2✔
650

651
        assert_eq!(result.len(), 1);
1✔
652

653
        assert_eq!(
1✔
654
            result[0].as_ref().unwrap().grid_array,
1✔
655
            GridOrEmpty::from(
1✔
656
                MaskedGrid2D::new(
1✔
657
                    Grid2D::new([3, 2].into(), vec![2, 4, 0, 8, 10, 12],).unwrap(),
1✔
658
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
659
                )
1✔
660
                .unwrap()
1✔
661
            )
1✔
662
        );
1✔
663
    }
664

665
    #[tokio::test]
1✔
666
    async fn unary_map_no_data() {
1✔
667
        let tile_size_in_pixels = [3, 2].into();
1✔
668
        let tiling_specification = TilingSpecification {
1✔
669
            origin_coordinate: [0.0, 0.0].into(),
1✔
670
            tile_size_in_pixels,
1✔
671
        };
1✔
672

1✔
673
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
674

1✔
675
        let raster_a = make_raster(Some(3));
1✔
676

677
        let o = Expression {
1✔
678
            params: ExpressionParams {
1✔
679
                expression: "2 * A".to_string(),
1✔
680
                output_type: RasterDataType::I8,
1✔
681
                output_measurement: Some(Measurement::Unitless),
1✔
682
                map_no_data: true,
1✔
683
            },
1✔
684
            sources: ExpressionSources {
1✔
685
                a: raster_a,
1✔
686
                b: None,
1✔
687
                c: None,
1✔
688
                d: None,
1✔
689
                e: None,
1✔
690
                f: None,
1✔
691
                g: None,
1✔
692
                h: None,
1✔
693
            },
1✔
694
        }
1✔
695
        .boxed()
1✔
696
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
697
        .await
×
698
        .unwrap();
1✔
699

1✔
700
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
701

1✔
702
        let ctx = MockQueryContext::new(1.into());
1✔
703
        let result_stream = processor
1✔
704
            .query(
1✔
705
                RasterQueryRectangle {
1✔
706
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
707
                        (0., 3.).into(),
1✔
708
                        (2., 0.).into(),
1✔
709
                    ),
1✔
710
                    time_interval: Default::default(),
1✔
711
                    spatial_resolution: SpatialResolution::one(),
1✔
712
                },
1✔
713
                &ctx,
1✔
714
            )
1✔
715
            .await
×
716
            .unwrap();
1✔
717

718
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
2✔
719

720
        assert_eq!(result.len(), 1);
1✔
721

722
        assert_eq!(
1✔
723
            result[0].as_ref().unwrap().grid_array,
1✔
724
            GridOrEmpty::from(
1✔
725
                MaskedGrid2D::new(
1✔
726
                    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✔
727
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
728
                )
1✔
729
                .unwrap()
1✔
730
            )
1✔
731
        );
1✔
732
    }
733

734
    #[tokio::test]
1✔
735
    async fn basic_binary() {
1✔
736
        let tile_size_in_pixels = [3, 2].into();
1✔
737
        let tiling_specification = TilingSpecification {
1✔
738
            origin_coordinate: [0.0, 0.0].into(),
1✔
739
            tile_size_in_pixels,
1✔
740
        };
1✔
741

1✔
742
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
743

1✔
744
        let raster_a = make_raster(None);
1✔
745
        let raster_b = make_raster(None);
1✔
746

747
        let o = Expression {
1✔
748
            params: ExpressionParams {
1✔
749
                expression: "A+B".to_string(),
1✔
750
                output_type: RasterDataType::I8,
1✔
751
                output_measurement: Some(Measurement::Unitless),
1✔
752
                map_no_data: false,
1✔
753
            },
1✔
754
            sources: ExpressionSources {
1✔
755
                a: raster_a,
1✔
756
                b: Some(raster_b),
1✔
757
                c: None,
1✔
758
                d: None,
1✔
759
                e: None,
1✔
760
                f: None,
1✔
761
                g: None,
1✔
762
                h: None,
1✔
763
            },
1✔
764
        }
1✔
765
        .boxed()
1✔
766
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
767
        .await
×
768
        .unwrap();
1✔
769

1✔
770
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
771

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

788
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
2✔
789

790
        assert_eq!(result.len(), 1);
1✔
791

792
        assert_eq!(
1✔
793
            result[0].as_ref().unwrap().grid_array,
1✔
794
            Grid2D::new([3, 2].into(), vec![2, 4, 6, 8, 10, 12],)
1✔
795
                .unwrap()
1✔
796
                .into()
1✔
797
        );
1✔
798
    }
799

800
    #[tokio::test]
1✔
801
    async fn basic_coalesce() {
1✔
802
        let tile_size_in_pixels = [3, 2].into();
1✔
803
        let tiling_specification = TilingSpecification {
1✔
804
            origin_coordinate: [0.0, 0.0].into(),
1✔
805
            tile_size_in_pixels,
1✔
806
        };
1✔
807

1✔
808
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
809

1✔
810
        let raster_a = make_raster(Some(3));
1✔
811
        let raster_b = make_raster(None);
1✔
812

813
        let o = Expression {
1✔
814
            params: ExpressionParams {
1✔
815
                expression: "if A IS NODATA {
1✔
816
                       B * 2
1✔
817
                   } else if A == 6 {
1✔
818
                       NODATA
1✔
819
                   } else {
1✔
820
                       A
1✔
821
                   }"
1✔
822
                .to_string(),
1✔
823
                output_type: RasterDataType::I8,
1✔
824
                output_measurement: Some(Measurement::Unitless),
1✔
825
                map_no_data: true,
1✔
826
            },
1✔
827
            sources: ExpressionSources {
1✔
828
                a: raster_a,
1✔
829
                b: Some(raster_b),
1✔
830
                c: None,
1✔
831
                d: None,
1✔
832
                e: None,
1✔
833
                f: None,
1✔
834
                g: None,
1✔
835
                h: None,
1✔
836
            },
1✔
837
        }
1✔
838
        .boxed()
1✔
839
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
840
        .await
×
841
        .unwrap();
1✔
842

1✔
843
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
844

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

861
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
2✔
862

863
        assert_eq!(result.len(), 1);
1✔
864

865
        assert_eq!(
1✔
866
            result[0].as_ref().unwrap().grid_array,
1✔
867
            GridOrEmpty::from(
1✔
868
                MaskedGrid2D::new(
1✔
869
                    Grid2D::new([3, 2].into(), vec![1, 2, 6, 4, 5, 0],).unwrap(),
1✔
870
                    Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false],).unwrap()
1✔
871
                )
1✔
872
                .unwrap()
1✔
873
            )
1✔
874
        );
1✔
875
    }
876

877
    #[tokio::test]
1✔
878
    async fn basic_ternary() {
1✔
879
        let no_data_value = 3;
1✔
880
        let no_data_value_option = Some(no_data_value);
1✔
881

1✔
882
        let tile_size_in_pixels = [3, 2].into();
1✔
883
        let tiling_specification = TilingSpecification {
1✔
884
            origin_coordinate: [0.0, 0.0].into(),
1✔
885
            tile_size_in_pixels,
1✔
886
        };
1✔
887

1✔
888
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
889

1✔
890
        let raster_a = make_raster(no_data_value_option);
1✔
891
        let raster_b = make_raster(no_data_value_option);
1✔
892
        let raster_c = make_raster(no_data_value_option);
1✔
893

894
        let o = Expression {
1✔
895
            params: ExpressionParams {
1✔
896
                expression: "A+B+C".to_string(),
1✔
897
                output_type: RasterDataType::I8,
1✔
898
                output_measurement: Some(Measurement::Unitless),
1✔
899
                map_no_data: false,
1✔
900
            },
1✔
901
            sources: ExpressionSources {
1✔
902
                a: raster_a,
1✔
903
                b: Some(raster_b),
1✔
904
                c: Some(raster_c),
1✔
905
                d: None,
1✔
906
                e: None,
1✔
907
                f: None,
1✔
908
                g: None,
1✔
909
                h: None,
1✔
910
            },
1✔
911
        }
1✔
912
        .boxed()
1✔
913
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
914
        .await
×
915
        .unwrap();
1✔
916

1✔
917
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
918

1✔
919
        let ctx = MockQueryContext::new(1.into());
1✔
920
        let result_stream = processor
1✔
921
            .query(
1✔
922
                RasterQueryRectangle {
1✔
923
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
924
                        (0., 3.).into(),
1✔
925
                        (2., 0.).into(),
1✔
926
                    ),
1✔
927
                    time_interval: Default::default(),
1✔
928
                    spatial_resolution: SpatialResolution::one(),
1✔
929
                },
1✔
930
                &ctx,
1✔
931
            )
1✔
932
            .await
×
933
            .unwrap();
1✔
934

935
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
3✔
936

937
        assert_eq!(result.len(), 1);
1✔
938

939
        let first_result = result[0].as_ref().unwrap();
1✔
940

1✔
941
        assert!(!first_result.is_empty());
1✔
942

943
        let grid = match &first_result.grid_array {
1✔
944
            GridOrEmpty::Grid(g) => g,
1✔
945
            GridOrEmpty::Empty(_) => panic!(),
×
946
        };
947

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

1✔
950
        assert_eq!(
1✔
951
            res,
1✔
952
            [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✔
953
        );
1✔
954
    }
955

956
    #[tokio::test]
1✔
957
    async fn octave_inputs() {
1✔
958
        let no_data_value = 0;
1✔
959
        let no_data_value_option = Some(no_data_value);
1✔
960

1✔
961
        let tile_size_in_pixels = [3, 2].into();
1✔
962
        let tiling_specification = TilingSpecification {
1✔
963
            origin_coordinate: [0.0, 0.0].into(),
1✔
964
            tile_size_in_pixels,
1✔
965
        };
1✔
966

1✔
967
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
968

1✔
969
        let raster_a = make_raster(no_data_value_option);
1✔
970
        let raster_b = make_raster(no_data_value_option);
1✔
971
        let raster_c = make_raster(no_data_value_option);
1✔
972
        let raster_d = make_raster(no_data_value_option);
1✔
973
        let raster_e = make_raster(no_data_value_option);
1✔
974
        let raster_f = make_raster(no_data_value_option);
1✔
975
        let raster_g = make_raster(no_data_value_option);
1✔
976
        let raster_h = make_raster(no_data_value_option);
1✔
977

978
        let o = Expression {
1✔
979
            params: ExpressionParams {
1✔
980
                expression: "A+B+C+D+E+F+G+H".to_string(),
1✔
981
                output_type: RasterDataType::I8,
1✔
982
                output_measurement: Some(Measurement::Unitless),
1✔
983
                map_no_data: false,
1✔
984
            },
1✔
985
            sources: ExpressionSources {
1✔
986
                a: raster_a,
1✔
987
                b: Some(raster_b),
1✔
988
                c: Some(raster_c),
1✔
989
                d: Some(raster_d),
1✔
990
                e: Some(raster_e),
1✔
991
                f: Some(raster_f),
1✔
992
                g: Some(raster_g),
1✔
993
                h: Some(raster_h),
1✔
994
            },
1✔
995
        }
1✔
996
        .boxed()
1✔
997
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
998
        .await
×
999
        .unwrap();
1✔
1000

1✔
1001
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1002

1✔
1003
        let ctx = MockQueryContext::new(1.into());
1✔
1004
        let result_stream = processor
1✔
1005
            .query(
1✔
1006
                RasterQueryRectangle {
1✔
1007
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1008
                        (0., 3.).into(),
1✔
1009
                        (2., 0.).into(),
1✔
1010
                    ),
1✔
1011
                    time_interval: Default::default(),
1✔
1012
                    spatial_resolution: SpatialResolution::one(),
1✔
1013
                },
1✔
1014
                &ctx,
1✔
1015
            )
1✔
1016
            .await
×
1017
            .unwrap();
1✔
1018

1019
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
2✔
1020

1021
        assert_eq!(result.len(), 1);
1✔
1022

1023
        assert_eq!(
1✔
1024
            result[0].as_ref().unwrap().grid_array,
1✔
1025
            Grid2D::new([3, 2].into(), vec![8, 16, 24, 32, 40, 48],)
1✔
1026
                .unwrap()
1✔
1027
                .into()
1✔
1028
        );
1✔
1029
    }
1030

1031
    #[tokio::test]
1✔
1032
    async fn test_functions() {
1✔
1033
        let no_data_value = 0;
1✔
1034
        let tile_size_in_pixels = [3, 2].into();
1✔
1035
        let tiling_specification = TilingSpecification {
1✔
1036
            origin_coordinate: [0.0, 0.0].into(),
1✔
1037
            tile_size_in_pixels,
1✔
1038
        };
1✔
1039

1✔
1040
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1041

1✔
1042
        let raster_a = make_raster(Some(no_data_value));
1✔
1043

1044
        let o = Expression {
1✔
1045
            params: ExpressionParams {
1✔
1046
                expression: "min(A * pi(), 10)".to_string(),
1✔
1047
                output_type: RasterDataType::I8,
1✔
1048
                output_measurement: Some(Measurement::Unitless),
1✔
1049
                map_no_data: false,
1✔
1050
            },
1✔
1051
            sources: ExpressionSources {
1✔
1052
                a: raster_a,
1✔
1053
                b: None,
1✔
1054
                c: None,
1✔
1055
                d: None,
1✔
1056
                e: None,
1✔
1057
                f: None,
1✔
1058
                g: None,
1✔
1059
                h: None,
1✔
1060
            },
1✔
1061
        }
1✔
1062
        .boxed()
1✔
1063
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1064
        .await
×
1065
        .unwrap();
1✔
1066

1✔
1067
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1068

1✔
1069
        let ctx = MockQueryContext::new(1.into());
1✔
1070
        let result_stream = processor
1✔
1071
            .query(
1✔
1072
                RasterQueryRectangle {
1✔
1073
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1074
                        (0., 3.).into(),
1✔
1075
                        (2., 0.).into(),
1✔
1076
                    ),
1✔
1077
                    time_interval: Default::default(),
1✔
1078
                    spatial_resolution: SpatialResolution::one(),
1✔
1079
                },
1✔
1080
                &ctx,
1✔
1081
            )
1✔
1082
            .await
×
1083
            .unwrap();
1✔
1084

1085
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
2✔
1086

1087
        assert_eq!(result.len(), 1);
1✔
1088

1089
        assert_eq!(
1✔
1090
            result[0].as_ref().unwrap().grid_array,
1✔
1091
            Grid2D::new([3, 2].into(), vec![3, 6, 9, 10, 10, 10],)
1✔
1092
                .unwrap()
1✔
1093
                .into()
1✔
1094
        );
1✔
1095
    }
1096

1097
    fn make_raster(no_data_value: Option<i8>) -> Box<dyn RasterOperator> {
18✔
1098
        let raster = Grid2D::<i8>::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6]).unwrap();
18✔
1099

1100
        let real_raster = if let Some(no_data_value) = no_data_value {
18✔
1101
            MaskedGrid2D::from(raster)
15✔
1102
                .map_elements(|e: Option<i8>| e.filter(|v| *v != no_data_value))
90✔
1103
                .into()
15✔
1104
        } else {
1105
            GridOrEmpty::from(raster)
3✔
1106
        };
1107

1108
        let raster_tile = RasterTile2D::new_with_tile_info(
18✔
1109
            TimeInterval::default(),
18✔
1110
            TileInformation {
18✔
1111
                global_tile_position: [-1, 0].into(),
18✔
1112
                tile_size_in_pixels: [3, 2].into(),
18✔
1113
                global_geo_transform: TestDefault::test_default(),
18✔
1114
            },
18✔
1115
            real_raster,
18✔
1116
        );
18✔
1117

18✔
1118
        MockRasterSource {
18✔
1119
            params: MockRasterSourceParams {
18✔
1120
                data: vec![raster_tile],
18✔
1121
                result_descriptor: RasterResultDescriptor {
18✔
1122
                    data_type: RasterDataType::I8,
18✔
1123
                    spatial_reference: SpatialReference::epsg_4326().into(),
18✔
1124
                    measurement: Measurement::Unitless,
18✔
1125
                    time: None,
18✔
1126
                    bbox: None,
18✔
1127
                    resolution: None,
18✔
1128
                },
18✔
1129
            },
18✔
1130
        }
18✔
1131
        .boxed()
18✔
1132
    }
18✔
1133
}
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