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

geo-engine / geoengine / 3929938005

pending completion
3929938005

push

github

GitHub
Merge #713

84930 of 96741 relevant lines covered (87.79%)

79640.1 hits per line

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

88.0
/operators/src/processing/expression/mod.rs
1
use self::{codegen::ExpressionAst, compiled::LinkedExpression, parser::ExpressionParser};
2
use crate::{
3
    engine::{
4
        CreateSpan, ExecutionContext, InitializedRasterOperator, Operator, OperatorData,
5
        OperatorName, RasterOperator, RasterQueryProcessor, RasterResultDescriptor,
6
        TypedRasterQueryProcessor,
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
use tracing::{span, Level};
21

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

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

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

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

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

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

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

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

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

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

123
    #[allow(clippy::many_single_char_names)]
124
    async fn initialize(
10✔
125
        self,
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(context),
11✔
134
            Self::initialize_source(self.b, context),
11✔
135
            Self::initialize_source(self.c, context),
11✔
136
            Self::initialize_source(self.d, context),
11✔
137
            Self::initialize_source(self.e, context),
11✔
138
            Self::initialize_source(self.f, context),
11✔
139
            Self::initialize_source(self.g, context),
11✔
140
            Self::initialize_source(self.h, 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
        context: &dyn ExecutionContext,
70✔
158
    ) -> Result<Option<Box<dyn InitializedRasterOperator>>> {
70✔
159
        if let Some(source) = source {
70✔
160
            Ok(Some(source.initialize(context).await?))
13✔
161
        } else {
162
            Ok(None)
57✔
163
        }
164
    }
70✔
165

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

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

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

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

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

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

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

242
        let expression = ExpressionParser::new(&parameters)?.parse(
10✔
243
            "expression", // TODO: generate and store a unique name
10✔
244
            &self.params.expression,
10✔
245
        )?;
10✔
246

247
        let sources = self.sources.initialize(context).await?;
10✔
248

249
        let spatial_reference = sources.a.result_descriptor().spatial_reference;
10✔
250

10✔
251
        let in_descriptors = sources
10✔
252
            .iter()
10✔
253
            .map(InitializedRasterOperator::result_descriptor)
10✔
254
            .collect::<Vec<_>>();
10✔
255

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

267
        let time = time_interval_extent(in_descriptors.iter().map(|d| d.time));
11✔
268
        let bbox = partitions_extent(in_descriptors.iter().map(|d| d.bbox));
11✔
269

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

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

10✔
294
        let initialized_operator = InitializedExpression {
10✔
295
            result_descriptor,
10✔
296
            sources,
10✔
297
            expression,
10✔
298
            map_no_data: self.params.map_no_data,
10✔
299
        };
10✔
300

10✔
301
        Ok(initialized_operator.boxed())
10✔
302
    }
20✔
303

304
    span_fn!(Expression);
×
305
}
306

307
impl OperatorName for Expression {
308
    const TYPE_NAME: &'static str = "Expression";
309
}
310

311
pub struct InitializedExpression {
312
    result_descriptor: RasterResultDescriptor,
313
    sources: ExpressionInitializedSources,
314
    expression: ExpressionAst,
315
    map_no_data: bool,
316
}
317

318
pub struct ExpressionInitializedSources {
319
    a: Box<dyn InitializedRasterOperator>,
320
    b: Option<Box<dyn InitializedRasterOperator>>,
321
    c: Option<Box<dyn InitializedRasterOperator>>,
322
    d: Option<Box<dyn InitializedRasterOperator>>,
323
    e: Option<Box<dyn InitializedRasterOperator>>,
324
    f: Option<Box<dyn InitializedRasterOperator>>,
325
    g: Option<Box<dyn InitializedRasterOperator>>,
326
    h: Option<Box<dyn InitializedRasterOperator>>,
327
}
328

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

346
#[allow(clippy::many_single_char_names, clippy::too_many_lines)]
347
impl InitializedRasterOperator for InitializedExpression {
348
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor> {
10✔
349
        let output_type = self.result_descriptor().data_type;
10✔
350

351
        let expression = LinkedExpression::new(&self.expression)?;
10✔
352

353
        let query_processors: Vec<TypedRasterQueryProcessor> = self
10✔
354
            .sources
10✔
355
            .iter()
10✔
356
            .map(InitializedRasterOperator::query_processor)
10✔
357
            .collect::<Result<_>>()?;
10✔
358

359
        Ok(match query_processors.len() {
10✔
360
            1 => {
361
                let [a] = <[_; 1]>::try_from(query_processors).expect("len previously checked");
4✔
362
                let query_processor = a.into_f64();
4✔
363
                call_generic_raster_processor!(
×
364
                    output_type,
4✔
365
                    ExpressionQueryProcessor::new(expression, query_processor, self.map_no_data)
4✔
366
                        .boxed()
4✔
367
                )
368

369
                // TODO: We could save prior conversions by monomophizing the differnt expressions
370
                //       However, this would lead to lots of compile symbols and to different results than using the
371
                //       variants with more than one raster.
372
                //
373
                // call_on_generic_raster_processor!(a, p_a => {
374
                //     call_generic_raster_processor!(
375
                //         output_type,
376
                //         ExpressionQueryProcessor::new(
377
                //             expression,
378
                //             p_a,
379
                //             output_no_data_value.as_(),
380
                //             self.map_no_data,
381
                //         ).boxed()
382
                //     )
383
                // })
384
            }
385
            2 => {
386
                let [a, b] = <[_; 2]>::try_from(query_processors).expect("len previously checked");
4✔
387
                let query_processors = (a.into_f64(), b.into_f64());
4✔
388
                call_generic_raster_processor!(
2✔
389
                    output_type,
4✔
390
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
4✔
391
                        .boxed()
4✔
392
                )
393

394
                // TODO: We could save prior conversions by monomophizing the differnt expressions
395
                //       However, this would lead to lots of compile symbols, e.g., 10x10x10 for this case
396
                //
397
                // call_on_bi_generic_raster_processor!(a, b, (p_a, p_b) => {
398
                //     call_generic_raster_processor!(
399
                //         output_type,
400
                //         ExpressionQueryProcessor::new(
401
                //             expression,
402
                //             (p_a, p_b),
403
                //             output_no_data_value.as_(),
404
                //             self.map_no_data,
405
                //         ).boxed()
406
                //     )
407
                // })
408
            }
409
            3 => {
410
                let [a, b, c] =
1✔
411
                    <[_; 3]>::try_from(query_processors).expect("len previously checked");
1✔
412
                let query_processors = [a.into_f64(), b.into_f64(), c.into_f64()];
1✔
413
                call_generic_raster_processor!(
×
414
                    output_type,
1✔
415
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
1✔
416
                        .boxed()
1✔
417
                )
418
            }
419
            4 => {
420
                let [a, b, c, d] =
×
421
                    <[_; 4]>::try_from(query_processors).expect("len previously checked");
×
422
                let query_processors = [a.into_f64(), b.into_f64(), c.into_f64(), d.into_f64()];
×
423
                call_generic_raster_processor!(
×
424
                    output_type,
×
425
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
×
426
                        .boxed()
×
427
                )
428
            }
429
            5 => {
430
                let [a, b, c, d, e] =
×
431
                    <[_; 5]>::try_from(query_processors).expect("len previously checked");
×
432
                let query_processors = [
×
433
                    a.into_f64(),
×
434
                    b.into_f64(),
×
435
                    c.into_f64(),
×
436
                    d.into_f64(),
×
437
                    e.into_f64(),
×
438
                ];
×
439
                call_generic_raster_processor!(
×
440
                    output_type,
×
441
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
×
442
                        .boxed()
×
443
                )
444
            }
445
            6 => {
446
                let [a, b, c, d, e, f] =
×
447
                    <[_; 6]>::try_from(query_processors).expect("len previously checked");
×
448
                let query_processors = [
×
449
                    a.into_f64(),
×
450
                    b.into_f64(),
×
451
                    c.into_f64(),
×
452
                    d.into_f64(),
×
453
                    e.into_f64(),
×
454
                    f.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

463
            7 => {
464
                let [a, b, c, d, e, f, g] =
×
465
                    <[_; 7]>::try_from(query_processors).expect("len previously checked");
×
466
                let query_processors = [
×
467
                    a.into_f64(),
×
468
                    b.into_f64(),
×
469
                    c.into_f64(),
×
470
                    d.into_f64(),
×
471
                    e.into_f64(),
×
472
                    f.into_f64(),
×
473
                    g.into_f64(),
×
474
                ];
×
475
                call_generic_raster_processor!(
×
476
                    output_type,
×
477
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
×
478
                        .boxed()
×
479
                )
480
            }
481
            8 => {
482
                let [a, b, c, d, e, f, g, h] =
1✔
483
                    <[_; 8]>::try_from(query_processors).expect("len previously checked");
1✔
484
                let query_processors = [
1✔
485
                    a.into_f64(),
1✔
486
                    b.into_f64(),
1✔
487
                    c.into_f64(),
1✔
488
                    d.into_f64(),
1✔
489
                    e.into_f64(),
1✔
490
                    f.into_f64(),
1✔
491
                    g.into_f64(),
1✔
492
                    h.into_f64(),
1✔
493
                ];
1✔
494
                call_generic_raster_processor!(
×
495
                    output_type,
1✔
496
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
1✔
497
                        .boxed()
1✔
498
                )
499
            }
500
            _ => return Err(crate::error::Error::InvalidNumberOfExpressionInputs),
×
501
        })
502
    }
10✔
503

504
    fn result_descriptor(&self) -> &RasterResultDescriptor {
13✔
505
        &self.result_descriptor
13✔
506
    }
13✔
507
}
508

509
#[cfg(test)]
510
mod tests {
511
    use super::*;
512
    use crate::engine::{MockExecutionContext, MockQueryContext, QueryProcessor};
513
    use crate::mock::{MockRasterSource, MockRasterSourceParams};
514
    use futures::StreamExt;
515
    use geoengine_datatypes::primitives::{
516
        Measurement, RasterQueryRectangle, SpatialPartition2D, SpatialResolution, TimeInterval,
517
    };
518
    use geoengine_datatypes::raster::{
519
        Grid2D, GridOrEmpty, MapElements, MaskedGrid2D, RasterTile2D, TileInformation,
520
        TilingSpecification,
521
    };
522
    use geoengine_datatypes::spatial_reference::SpatialReference;
523
    use geoengine_datatypes::util::test::TestDefault;
524

525
    #[test]
1✔
526
    fn deserialize_params() {
1✔
527
        let s =
1✔
528
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
529

1✔
530
        assert_eq!(
1✔
531
            serde_json::from_str::<ExpressionParams>(s).unwrap(),
1✔
532
            ExpressionParams {
1✔
533
                expression: "1*A".to_owned(),
1✔
534
                output_type: RasterDataType::F64,
1✔
535
                output_measurement: None,
1✔
536
                map_no_data: false,
1✔
537
            }
1✔
538
        );
1✔
539
    }
1✔
540

541
    #[test]
1✔
542
    fn serialize_params() {
1✔
543
        let s =
1✔
544
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
545

1✔
546
        assert_eq!(
1✔
547
            s,
1✔
548
            serde_json::to_string(&ExpressionParams {
1✔
549
                expression: "1*A".to_owned(),
1✔
550
                output_type: RasterDataType::F64,
1✔
551
                output_measurement: None,
1✔
552
                map_no_data: false,
1✔
553
            })
1✔
554
            .unwrap()
1✔
555
        );
1✔
556
    }
1✔
557

558
    #[test]
1✔
559
    fn serialize_params_no_data() {
1✔
560
        let s =
1✔
561
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
562

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

575
    #[tokio::test]
1✔
576
    async fn basic_unary() {
1✔
577
        let tile_size_in_pixels = [3, 2].into();
1✔
578
        let tiling_specification = TilingSpecification {
1✔
579
            origin_coordinate: [0.0, 0.0].into(),
1✔
580
            tile_size_in_pixels,
1✔
581
        };
1✔
582

1✔
583
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
584

1✔
585
        let raster_a = make_raster(Some(3));
1✔
586

587
        let o = Expression {
1✔
588
            params: ExpressionParams {
1✔
589
                expression: "2 * A".to_string(),
1✔
590
                output_type: RasterDataType::I8,
1✔
591
                output_measurement: Some(Measurement::Unitless),
1✔
592
                map_no_data: false,
1✔
593
            },
1✔
594
            sources: ExpressionSources {
1✔
595
                a: raster_a,
1✔
596
                b: None,
1✔
597
                c: None,
1✔
598
                d: None,
1✔
599
                e: None,
1✔
600
                f: None,
1✔
601
                g: None,
1✔
602
                h: None,
1✔
603
            },
1✔
604
        }
1✔
605
        .boxed()
1✔
606
        .initialize(&ctx)
1✔
607
        .await
×
608
        .unwrap();
1✔
609

1✔
610
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
611

1✔
612
        let ctx = MockQueryContext::new(1.into());
1✔
613
        let result_stream = processor
1✔
614
            .query(
1✔
615
                RasterQueryRectangle {
1✔
616
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
617
                        (0., 3.).into(),
1✔
618
                        (2., 0.).into(),
1✔
619
                    ),
1✔
620
                    time_interval: Default::default(),
1✔
621
                    spatial_resolution: SpatialResolution::one(),
1✔
622
                },
1✔
623
                &ctx,
1✔
624
            )
1✔
625
            .await
×
626
            .unwrap();
1✔
627

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

630
        assert_eq!(result.len(), 1);
1✔
631

632
        assert_eq!(
1✔
633
            result[0].as_ref().unwrap().grid_array,
1✔
634
            GridOrEmpty::from(
1✔
635
                MaskedGrid2D::new(
1✔
636
                    Grid2D::new([3, 2].into(), vec![2, 4, 0, 8, 10, 12],).unwrap(),
1✔
637
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
638
                )
1✔
639
                .unwrap()
1✔
640
            )
1✔
641
        );
1✔
642
    }
643

644
    #[tokio::test]
1✔
645
    async fn unary_map_no_data() {
1✔
646
        let tile_size_in_pixels = [3, 2].into();
1✔
647
        let tiling_specification = TilingSpecification {
1✔
648
            origin_coordinate: [0.0, 0.0].into(),
1✔
649
            tile_size_in_pixels,
1✔
650
        };
1✔
651

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

1✔
654
        let raster_a = make_raster(Some(3));
1✔
655

656
        let o = Expression {
1✔
657
            params: ExpressionParams {
1✔
658
                expression: "2 * A".to_string(),
1✔
659
                output_type: RasterDataType::I8,
1✔
660
                output_measurement: Some(Measurement::Unitless),
1✔
661
                map_no_data: true,
1✔
662
            },
1✔
663
            sources: ExpressionSources {
1✔
664
                a: raster_a,
1✔
665
                b: None,
1✔
666
                c: None,
1✔
667
                d: None,
1✔
668
                e: None,
1✔
669
                f: None,
1✔
670
                g: None,
1✔
671
                h: None,
1✔
672
            },
1✔
673
        }
1✔
674
        .boxed()
1✔
675
        .initialize(&ctx)
1✔
676
        .await
×
677
        .unwrap();
1✔
678

1✔
679
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
680

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

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

699
        assert_eq!(result.len(), 1);
1✔
700

701
        assert_eq!(
1✔
702
            result[0].as_ref().unwrap().grid_array,
1✔
703
            GridOrEmpty::from(
1✔
704
                MaskedGrid2D::new(
1✔
705
                    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✔
706
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
707
                )
1✔
708
                .unwrap()
1✔
709
            )
1✔
710
        );
1✔
711
    }
712

713
    #[tokio::test]
1✔
714
    async fn basic_binary() {
1✔
715
        let tile_size_in_pixels = [3, 2].into();
1✔
716
        let tiling_specification = TilingSpecification {
1✔
717
            origin_coordinate: [0.0, 0.0].into(),
1✔
718
            tile_size_in_pixels,
1✔
719
        };
1✔
720

1✔
721
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
722

1✔
723
        let raster_a = make_raster(None);
1✔
724
        let raster_b = make_raster(None);
1✔
725

726
        let o = Expression {
1✔
727
            params: ExpressionParams {
1✔
728
                expression: "A+B".to_string(),
1✔
729
                output_type: RasterDataType::I8,
1✔
730
                output_measurement: Some(Measurement::Unitless),
1✔
731
                map_no_data: false,
1✔
732
            },
1✔
733
            sources: ExpressionSources {
1✔
734
                a: raster_a,
1✔
735
                b: Some(raster_b),
1✔
736
                c: None,
1✔
737
                d: None,
1✔
738
                e: None,
1✔
739
                f: None,
1✔
740
                g: None,
1✔
741
                h: None,
1✔
742
            },
1✔
743
        }
1✔
744
        .boxed()
1✔
745
        .initialize(&ctx)
1✔
746
        .await
×
747
        .unwrap();
1✔
748

1✔
749
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
750

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

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

769
        assert_eq!(result.len(), 1);
1✔
770

771
        assert_eq!(
1✔
772
            result[0].as_ref().unwrap().grid_array,
1✔
773
            Grid2D::new([3, 2].into(), vec![2, 4, 6, 8, 10, 12],)
1✔
774
                .unwrap()
1✔
775
                .into()
1✔
776
        );
1✔
777
    }
778

779
    #[tokio::test]
1✔
780
    async fn basic_coalesce() {
1✔
781
        let tile_size_in_pixels = [3, 2].into();
1✔
782
        let tiling_specification = TilingSpecification {
1✔
783
            origin_coordinate: [0.0, 0.0].into(),
1✔
784
            tile_size_in_pixels,
1✔
785
        };
1✔
786

1✔
787
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
788

1✔
789
        let raster_a = make_raster(Some(3));
1✔
790
        let raster_b = make_raster(None);
1✔
791

792
        let o = Expression {
1✔
793
            params: ExpressionParams {
1✔
794
                expression: "if A IS NODATA {
1✔
795
                       B * 2
1✔
796
                   } else if A == 6 {
1✔
797
                       NODATA
1✔
798
                   } else {
1✔
799
                       A
1✔
800
                   }"
1✔
801
                .to_string(),
1✔
802
                output_type: RasterDataType::I8,
1✔
803
                output_measurement: Some(Measurement::Unitless),
1✔
804
                map_no_data: true,
1✔
805
            },
1✔
806
            sources: ExpressionSources {
1✔
807
                a: raster_a,
1✔
808
                b: Some(raster_b),
1✔
809
                c: None,
1✔
810
                d: None,
1✔
811
                e: None,
1✔
812
                f: None,
1✔
813
                g: None,
1✔
814
                h: None,
1✔
815
            },
1✔
816
        }
1✔
817
        .boxed()
1✔
818
        .initialize(&ctx)
1✔
819
        .await
×
820
        .unwrap();
1✔
821

1✔
822
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
823

1✔
824
        let ctx = MockQueryContext::new(1.into());
1✔
825
        let result_stream = processor
1✔
826
            .query(
1✔
827
                RasterQueryRectangle {
1✔
828
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
829
                        (0., 3.).into(),
1✔
830
                        (2., 0.).into(),
1✔
831
                    ),
1✔
832
                    time_interval: Default::default(),
1✔
833
                    spatial_resolution: SpatialResolution::one(),
1✔
834
                },
1✔
835
                &ctx,
1✔
836
            )
1✔
837
            .await
×
838
            .unwrap();
1✔
839

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

842
        assert_eq!(result.len(), 1);
1✔
843

844
        assert_eq!(
1✔
845
            result[0].as_ref().unwrap().grid_array,
1✔
846
            GridOrEmpty::from(
1✔
847
                MaskedGrid2D::new(
1✔
848
                    Grid2D::new([3, 2].into(), vec![1, 2, 6, 4, 5, 0],).unwrap(),
1✔
849
                    Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false],).unwrap()
1✔
850
                )
1✔
851
                .unwrap()
1✔
852
            )
1✔
853
        );
1✔
854
    }
855

856
    #[tokio::test]
1✔
857
    async fn basic_ternary() {
1✔
858
        let no_data_value = 3;
1✔
859
        let no_data_value_option = Some(no_data_value);
1✔
860

1✔
861
        let tile_size_in_pixels = [3, 2].into();
1✔
862
        let tiling_specification = TilingSpecification {
1✔
863
            origin_coordinate: [0.0, 0.0].into(),
1✔
864
            tile_size_in_pixels,
1✔
865
        };
1✔
866

1✔
867
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
868

1✔
869
        let raster_a = make_raster(no_data_value_option);
1✔
870
        let raster_b = make_raster(no_data_value_option);
1✔
871
        let raster_c = make_raster(no_data_value_option);
1✔
872

873
        let o = Expression {
1✔
874
            params: ExpressionParams {
1✔
875
                expression: "A+B+C".to_string(),
1✔
876
                output_type: RasterDataType::I8,
1✔
877
                output_measurement: Some(Measurement::Unitless),
1✔
878
                map_no_data: false,
1✔
879
            },
1✔
880
            sources: ExpressionSources {
1✔
881
                a: raster_a,
1✔
882
                b: Some(raster_b),
1✔
883
                c: Some(raster_c),
1✔
884
                d: None,
1✔
885
                e: None,
1✔
886
                f: None,
1✔
887
                g: None,
1✔
888
                h: None,
1✔
889
            },
1✔
890
        }
1✔
891
        .boxed()
1✔
892
        .initialize(&ctx)
1✔
893
        .await
×
894
        .unwrap();
1✔
895

1✔
896
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
897

1✔
898
        let ctx = MockQueryContext::new(1.into());
1✔
899
        let result_stream = processor
1✔
900
            .query(
1✔
901
                RasterQueryRectangle {
1✔
902
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
903
                        (0., 3.).into(),
1✔
904
                        (2., 0.).into(),
1✔
905
                    ),
1✔
906
                    time_interval: Default::default(),
1✔
907
                    spatial_resolution: SpatialResolution::one(),
1✔
908
                },
1✔
909
                &ctx,
1✔
910
            )
1✔
911
            .await
×
912
            .unwrap();
1✔
913

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

916
        assert_eq!(result.len(), 1);
1✔
917

918
        let first_result = result[0].as_ref().unwrap();
1✔
919

1✔
920
        assert!(!first_result.is_empty());
1✔
921

922
        let grid = match &first_result.grid_array {
1✔
923
            GridOrEmpty::Grid(g) => g,
1✔
924
            GridOrEmpty::Empty(_) => panic!(),
×
925
        };
926

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

1✔
929
        assert_eq!(
1✔
930
            res,
1✔
931
            [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✔
932
        );
1✔
933
    }
934

935
    #[tokio::test]
1✔
936
    async fn octave_inputs() {
1✔
937
        let no_data_value = 0;
1✔
938
        let no_data_value_option = Some(no_data_value);
1✔
939

1✔
940
        let tile_size_in_pixels = [3, 2].into();
1✔
941
        let tiling_specification = TilingSpecification {
1✔
942
            origin_coordinate: [0.0, 0.0].into(),
1✔
943
            tile_size_in_pixels,
1✔
944
        };
1✔
945

1✔
946
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
947

1✔
948
        let raster_a = make_raster(no_data_value_option);
1✔
949
        let raster_b = make_raster(no_data_value_option);
1✔
950
        let raster_c = make_raster(no_data_value_option);
1✔
951
        let raster_d = make_raster(no_data_value_option);
1✔
952
        let raster_e = make_raster(no_data_value_option);
1✔
953
        let raster_f = make_raster(no_data_value_option);
1✔
954
        let raster_g = make_raster(no_data_value_option);
1✔
955
        let raster_h = make_raster(no_data_value_option);
1✔
956

957
        let o = Expression {
1✔
958
            params: ExpressionParams {
1✔
959
                expression: "A+B+C+D+E+F+G+H".to_string(),
1✔
960
                output_type: RasterDataType::I8,
1✔
961
                output_measurement: Some(Measurement::Unitless),
1✔
962
                map_no_data: false,
1✔
963
            },
1✔
964
            sources: ExpressionSources {
1✔
965
                a: raster_a,
1✔
966
                b: Some(raster_b),
1✔
967
                c: Some(raster_c),
1✔
968
                d: Some(raster_d),
1✔
969
                e: Some(raster_e),
1✔
970
                f: Some(raster_f),
1✔
971
                g: Some(raster_g),
1✔
972
                h: Some(raster_h),
1✔
973
            },
1✔
974
        }
1✔
975
        .boxed()
1✔
976
        .initialize(&ctx)
1✔
977
        .await
×
978
        .unwrap();
1✔
979

1✔
980
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
981

1✔
982
        let ctx = MockQueryContext::new(1.into());
1✔
983
        let result_stream = processor
1✔
984
            .query(
1✔
985
                RasterQueryRectangle {
1✔
986
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
987
                        (0., 3.).into(),
1✔
988
                        (2., 0.).into(),
1✔
989
                    ),
1✔
990
                    time_interval: Default::default(),
1✔
991
                    spatial_resolution: SpatialResolution::one(),
1✔
992
                },
1✔
993
                &ctx,
1✔
994
            )
1✔
995
            .await
×
996
            .unwrap();
1✔
997

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

1000
        assert_eq!(result.len(), 1);
1✔
1001

1002
        assert_eq!(
1✔
1003
            result[0].as_ref().unwrap().grid_array,
1✔
1004
            Grid2D::new([3, 2].into(), vec![8, 16, 24, 32, 40, 48],)
1✔
1005
                .unwrap()
1✔
1006
                .into()
1✔
1007
        );
1✔
1008
    }
1009

1010
    #[tokio::test]
1✔
1011
    async fn test_functions() {
1✔
1012
        let no_data_value = 0;
1✔
1013
        let tile_size_in_pixels = [3, 2].into();
1✔
1014
        let tiling_specification = TilingSpecification {
1✔
1015
            origin_coordinate: [0.0, 0.0].into(),
1✔
1016
            tile_size_in_pixels,
1✔
1017
        };
1✔
1018

1✔
1019
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1020

1✔
1021
        let raster_a = make_raster(Some(no_data_value));
1✔
1022

1023
        let o = Expression {
1✔
1024
            params: ExpressionParams {
1✔
1025
                expression: "min(A * pi(), 10)".to_string(),
1✔
1026
                output_type: RasterDataType::I8,
1✔
1027
                output_measurement: Some(Measurement::Unitless),
1✔
1028
                map_no_data: false,
1✔
1029
            },
1✔
1030
            sources: ExpressionSources {
1✔
1031
                a: raster_a,
1✔
1032
                b: None,
1✔
1033
                c: None,
1✔
1034
                d: None,
1✔
1035
                e: None,
1✔
1036
                f: None,
1✔
1037
                g: None,
1✔
1038
                h: None,
1✔
1039
            },
1✔
1040
        }
1✔
1041
        .boxed()
1✔
1042
        .initialize(&ectx)
1✔
1043
        .await
×
1044
        .unwrap();
1✔
1045

1✔
1046
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1047

1✔
1048
        let ctx = MockQueryContext::new(1.into());
1✔
1049
        let result_stream = processor
1✔
1050
            .query(
1✔
1051
                RasterQueryRectangle {
1✔
1052
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1053
                        (0., 3.).into(),
1✔
1054
                        (2., 0.).into(),
1✔
1055
                    ),
1✔
1056
                    time_interval: Default::default(),
1✔
1057
                    spatial_resolution: SpatialResolution::one(),
1✔
1058
                },
1✔
1059
                &ctx,
1✔
1060
            )
1✔
1061
            .await
×
1062
            .unwrap();
1✔
1063

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

1066
        assert_eq!(result.len(), 1);
1✔
1067

1068
        assert_eq!(
1✔
1069
            result[0].as_ref().unwrap().grid_array,
1✔
1070
            Grid2D::new([3, 2].into(), vec![3, 6, 9, 10, 10, 10],)
1✔
1071
                .unwrap()
1✔
1072
                .into()
1✔
1073
        );
1✔
1074
    }
1075

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

1079
        let real_raster = if let Some(no_data_value) = no_data_value {
18✔
1080
            MaskedGrid2D::from(raster)
15✔
1081
                .map_elements(|e: Option<i8>| e.filter(|v| *v != no_data_value))
90✔
1082
                .into()
15✔
1083
        } else {
1084
            GridOrEmpty::from(raster)
3✔
1085
        };
1086

1087
        let raster_tile = RasterTile2D::new_with_tile_info(
18✔
1088
            TimeInterval::default(),
18✔
1089
            TileInformation {
18✔
1090
                global_tile_position: [-1, 0].into(),
18✔
1091
                tile_size_in_pixels: [3, 2].into(),
18✔
1092
                global_geo_transform: TestDefault::test_default(),
18✔
1093
            },
18✔
1094
            real_raster,
18✔
1095
        );
18✔
1096

18✔
1097
        MockRasterSource {
18✔
1098
            params: MockRasterSourceParams {
18✔
1099
                data: vec![raster_tile],
18✔
1100
                result_descriptor: RasterResultDescriptor {
18✔
1101
                    data_type: RasterDataType::I8,
18✔
1102
                    spatial_reference: SpatialReference::epsg_4326().into(),
18✔
1103
                    measurement: Measurement::Unitless,
18✔
1104
                    time: None,
18✔
1105
                    bbox: None,
18✔
1106
                    resolution: None,
18✔
1107
                },
18✔
1108
            },
18✔
1109
        }
18✔
1110
        .boxed()
18✔
1111
    }
18✔
1112
}
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