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

geo-engine / geoengine / 7276004606

20 Dec 2023 01:21PM UTC coverage: 89.793% (-0.03%) from 89.823%
7276004606

push

github

web-flow
Merge pull request #906 from geo-engine/raster_result_describer

result descriptors for query processors

1080 of 1240 new or added lines in 43 files covered. (87.1%)

14 existing lines in 6 files now uncovered.

115914 of 129090 relevant lines covered (89.79%)

58688.76 hits per line

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

88.73
/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, RasterBandDescriptor, RasterBandDescriptors,
6
        RasterOperator, RasterQueryProcessor, RasterResultDescriptor, TypedRasterQueryProcessor,
7
        WorkflowOperatorPath,
8
    },
9
    processing::expression::{codegen::Parameter, query_processor::ExpressionQueryProcessor},
10
    util::Result,
11
};
12
use async_trait::async_trait;
13
use futures::try_join;
14
use geoengine_datatypes::{
15
    dataset::NamedData,
16
    primitives::{partitions_extent, time_interval_extent, Measurement, SpatialResolution},
17
    raster::RasterDataType,
18
};
19
use serde::{Deserialize, Serialize};
20
use snafu::ensure;
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)]
19✔
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)]
17✔
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_names_collect(&self, data_names: &mut Vec<NamedData>) {
×
69
        for source in self.iter() {
×
70
            source.data_names_collect(data_names);
×
71
        }
×
72
    }
×
73
}
74

75
impl ExpressionSources {
76
    pub fn new_a(a: Box<dyn RasterOperator>) -> Self {
2✔
77
        Self {
2✔
78
            a,
2✔
79
            b: None,
2✔
80
            c: None,
2✔
81
            d: None,
2✔
82
            e: None,
2✔
83
            f: None,
2✔
84
            g: None,
2✔
85
            h: None,
2✔
86
        }
2✔
87
    }
2✔
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 {
28✔
120
        self.iter().count()
28✔
121
    }
28✔
122

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

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

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

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

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

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

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

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

30✔
217
    parameter.to_string()
30✔
218
}
30✔
219

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

237
        let name = CanonicOperatorName::from(&self);
14✔
238

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

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

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

254
        let spatial_reference = sources.a.result_descriptor().spatial_reference;
14✔
255

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

14✔
261
        // TODO: implement multi-band functionality and remove this check
14✔
262
        ensure!(
14✔
263
            in_descriptors.iter().all(|r| r.bands.len() == 1),
30✔
264
            crate::error::OperatorDoesNotSupportMultiBandsSourcesYet {
×
265
                operator: Expression::TYPE_NAME
×
266
            }
×
267
        );
268

269
        for other_spatial_reference in in_descriptors.iter().skip(1).map(|rd| rd.spatial_reference)
16✔
270
        {
271
            ensure!(
16✔
272
                spatial_reference == other_spatial_reference,
16✔
273
                crate::error::InvalidSpatialReference {
×
274
                    expected: spatial_reference,
×
275
                    found: other_spatial_reference,
×
276
                }
×
277
            );
278
        }
279

280
        let time = time_interval_extent(in_descriptors.iter().map(|d| d.time));
15✔
281
        let bbox = partitions_extent(in_descriptors.iter().map(|d| d.bbox));
15✔
282

14✔
283
        let resolution = in_descriptors
14✔
284
            .iter()
14✔
285
            .map(|d| d.resolution)
30✔
286
            .reduce(|a, b| match (a, b) {
16✔
287
                (Some(a), Some(b)) => {
1✔
288
                    Some(SpatialResolution::new_unchecked(a.x.min(b.x), a.y.min(b.y)))
1✔
289
                }
290
                _ => None,
15✔
291
            })
16✔
292
            .flatten();
14✔
293

14✔
294
        let result_descriptor = RasterResultDescriptor {
14✔
295
            data_type: self.params.output_type,
14✔
296
            spatial_reference,
14✔
297
            time,
14✔
298
            bbox,
14✔
299
            resolution,
14✔
300
            bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
14✔
301
                "band".into(), // TODO: how to name the band?
14✔
302
                self.params
14✔
303
                    .output_measurement
14✔
304
                    .as_ref()
14✔
305
                    .map_or(Measurement::Unitless, Measurement::clone),
14✔
306
            )])
14✔
307
            .unwrap(),
14✔
308
        };
14✔
309

14✔
310
        let initialized_operator = InitializedExpression {
14✔
311
            name,
14✔
312
            result_descriptor,
14✔
313
            sources,
14✔
314
            expression,
14✔
315
            map_no_data: self.params.map_no_data,
14✔
316
        };
14✔
317

14✔
318
        Ok(initialized_operator.boxed())
14✔
319
    }
28✔
320

321
    span_fn!(Expression);
×
322
}
323

324
impl OperatorName for Expression {
325
    const TYPE_NAME: &'static str = "Expression";
326
}
327

328
pub struct InitializedExpression {
329
    name: CanonicOperatorName,
330
    result_descriptor: RasterResultDescriptor,
331
    sources: ExpressionInitializedSources,
332
    expression: ExpressionAst,
333
    map_no_data: bool,
334
}
335

336
pub struct ExpressionInitializedSources {
337
    a: Box<dyn InitializedRasterOperator>,
338
    b: Option<Box<dyn InitializedRasterOperator>>,
339
    c: Option<Box<dyn InitializedRasterOperator>>,
340
    d: Option<Box<dyn InitializedRasterOperator>>,
341
    e: Option<Box<dyn InitializedRasterOperator>>,
342
    f: Option<Box<dyn InitializedRasterOperator>>,
343
    g: Option<Box<dyn InitializedRasterOperator>>,
344
    h: Option<Box<dyn InitializedRasterOperator>>,
345
}
346

347
impl ExpressionInitializedSources {
348
    fn iter(&self) -> impl Iterator<Item = &Box<dyn InitializedRasterOperator>> {
28✔
349
        [
28✔
350
            Some(&self.a),
28✔
351
            self.b.as_ref(),
28✔
352
            self.c.as_ref(),
28✔
353
            self.d.as_ref(),
28✔
354
            self.e.as_ref(),
28✔
355
            self.f.as_ref(),
28✔
356
            self.g.as_ref(),
28✔
357
            self.h.as_ref(),
28✔
358
        ]
28✔
359
        .into_iter()
28✔
360
        .flatten()
28✔
361
    }
28✔
362
}
363

364
#[async_trait]
365
impl InitializedSources<ExpressionInitializedSources> for ExpressionSources {
366
    async fn initialize_sources(
14✔
367
        self,
14✔
368
        path: WorkflowOperatorPath,
14✔
369
        context: &dyn ExecutionContext,
14✔
370
    ) -> Result<ExpressionInitializedSources> {
14✔
371
        self.initialize(path, context).await
14✔
372
    }
28✔
373
}
374

375
#[allow(clippy::many_single_char_names, clippy::too_many_lines)]
376
impl InitializedRasterOperator for InitializedExpression {
377
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor> {
14✔
378
        let output_type = self.result_descriptor().data_type;
14✔
379

380
        let expression = LinkedExpression::new(&self.expression)?;
14✔
381

382
        let query_processors: Vec<TypedRasterQueryProcessor> = self
14✔
383
            .sources
14✔
384
            .iter()
14✔
385
            .map(InitializedRasterOperator::query_processor)
14✔
386
            .collect::<Result<_>>()?;
14✔
387

388
        Ok(match query_processors.len() {
14✔
389
            1 => {
390
                let [a] = <[_; 1]>::try_from(query_processors).expect("len previously checked");
6✔
391
                let query_processor = a.into_f64();
6✔
392
                call_generic_raster_processor!(
×
393
                    output_type,
6✔
394
                    ExpressionQueryProcessor::new(
395
                        expression,
6✔
396
                        query_processor,
6✔
397
                        self.result_descriptor.clone(),
6✔
398
                        self.map_no_data
6✔
399
                    )
400
                    .boxed()
6✔
401
                )
402

403
                // TODO: We could save prior conversions by monomophizing the differnt expressions
404
                //       However, this would lead to lots of compile symbols and to different results than using the
405
                //       variants with more than one raster.
406
                //
407
                // call_on_generic_raster_processor!(a, p_a => {
408
                //     call_generic_raster_processor!(
409
                //         output_type,
410
                //         ExpressionQueryProcessor::new(
411
                //             expression,
412
                //             p_a,
413
                //             output_no_data_value.as_(),
414
                //             self.map_no_data,
415
                //         ).boxed()
416
                //     )
417
                // })
418
            }
419
            2 => {
420
                let [a, b] = <[_; 2]>::try_from(query_processors).expect("len previously checked");
5✔
421
                let query_processors = (a.into_f64(), b.into_f64());
5✔
422
                call_generic_raster_processor!(
2✔
423
                    output_type,
5✔
424
                    ExpressionQueryProcessor::new(
425
                        expression,
5✔
426
                        query_processors,
5✔
427
                        self.result_descriptor.clone(),
5✔
428
                        self.map_no_data
5✔
429
                    )
430
                    .boxed()
5✔
431
                )
432

433
                // TODO: We could save prior conversions by monomophizing the differnt expressions
434
                //       However, this would lead to lots of compile symbols, e.g., 10x10x10 for this case
435
                //
436
                // call_on_bi_generic_raster_processor!(a, b, (p_a, p_b) => {
437
                //     call_generic_raster_processor!(
438
                //         output_type,
439
                //         ExpressionQueryProcessor::new(
440
                //             expression,
441
                //             (p_a, p_b),
442
                //             output_no_data_value.as_(),
443
                //             self.map_no_data,
444
                //         ).boxed()
445
                //     )
446
                // })
447
            }
448
            3 => {
449
                let [a, b, c] =
2✔
450
                    <[_; 3]>::try_from(query_processors).expect("len previously checked");
2✔
451
                let query_processors = [a.into_f64(), b.into_f64(), c.into_f64()];
2✔
452
                call_generic_raster_processor!(
×
453
                    output_type,
2✔
454
                    ExpressionQueryProcessor::new(
455
                        expression,
2✔
456
                        query_processors,
2✔
457
                        self.result_descriptor.clone(),
2✔
458
                        self.map_no_data
2✔
459
                    )
460
                    .boxed()
2✔
461
                )
462
            }
463
            4 => {
464
                let [a, b, c, d] =
×
465
                    <[_; 4]>::try_from(query_processors).expect("len previously checked");
×
466
                let query_processors = [a.into_f64(), b.into_f64(), c.into_f64(), d.into_f64()];
×
467
                call_generic_raster_processor!(
×
468
                    output_type,
×
469
                    ExpressionQueryProcessor::new(
NEW
470
                        expression,
×
NEW
471
                        query_processors,
×
NEW
472
                        self.result_descriptor.clone(),
×
NEW
473
                        self.map_no_data
×
474
                    )
NEW
475
                    .boxed()
×
476
                )
477
            }
478
            5 => {
479
                let [a, b, c, d, e] =
×
480
                    <[_; 5]>::try_from(query_processors).expect("len previously checked");
×
481
                let query_processors = [
×
482
                    a.into_f64(),
×
483
                    b.into_f64(),
×
484
                    c.into_f64(),
×
485
                    d.into_f64(),
×
486
                    e.into_f64(),
×
487
                ];
×
488
                call_generic_raster_processor!(
×
489
                    output_type,
×
490
                    ExpressionQueryProcessor::new(
NEW
491
                        expression,
×
NEW
492
                        query_processors,
×
NEW
493
                        self.result_descriptor.clone(),
×
NEW
494
                        self.map_no_data
×
495
                    )
NEW
496
                    .boxed()
×
497
                )
498
            }
499
            6 => {
500
                let [a, b, c, d, e, f] =
×
501
                    <[_; 6]>::try_from(query_processors).expect("len previously checked");
×
502
                let query_processors = [
×
503
                    a.into_f64(),
×
504
                    b.into_f64(),
×
505
                    c.into_f64(),
×
506
                    d.into_f64(),
×
507
                    e.into_f64(),
×
508
                    f.into_f64(),
×
509
                ];
×
510
                call_generic_raster_processor!(
×
511
                    output_type,
×
512
                    ExpressionQueryProcessor::new(
NEW
513
                        expression,
×
NEW
514
                        query_processors,
×
NEW
515
                        self.result_descriptor.clone(),
×
NEW
516
                        self.map_no_data
×
517
                    )
NEW
518
                    .boxed()
×
519
                )
520
            }
521

522
            7 => {
523
                let [a, b, c, d, e, f, g] =
×
524
                    <[_; 7]>::try_from(query_processors).expect("len previously checked");
×
525
                let query_processors = [
×
526
                    a.into_f64(),
×
527
                    b.into_f64(),
×
528
                    c.into_f64(),
×
529
                    d.into_f64(),
×
530
                    e.into_f64(),
×
531
                    f.into_f64(),
×
532
                    g.into_f64(),
×
533
                ];
×
534
                call_generic_raster_processor!(
×
535
                    output_type,
×
536
                    ExpressionQueryProcessor::new(
NEW
537
                        expression,
×
NEW
538
                        query_processors,
×
NEW
539
                        self.result_descriptor.clone(),
×
NEW
540
                        self.map_no_data
×
541
                    )
NEW
542
                    .boxed()
×
543
                )
544
            }
545
            8 => {
546
                let [a, b, c, d, e, f, g, h] =
1✔
547
                    <[_; 8]>::try_from(query_processors).expect("len previously checked");
1✔
548
                let query_processors = [
1✔
549
                    a.into_f64(),
1✔
550
                    b.into_f64(),
1✔
551
                    c.into_f64(),
1✔
552
                    d.into_f64(),
1✔
553
                    e.into_f64(),
1✔
554
                    f.into_f64(),
1✔
555
                    g.into_f64(),
1✔
556
                    h.into_f64(),
1✔
557
                ];
1✔
558
                call_generic_raster_processor!(
×
559
                    output_type,
1✔
560
                    ExpressionQueryProcessor::new(
561
                        expression,
1✔
562
                        query_processors,
1✔
563
                        self.result_descriptor.clone(),
1✔
564
                        self.map_no_data
1✔
565
                    )
566
                    .boxed()
1✔
567
                )
568
            }
569
            _ => return Err(crate::error::Error::InvalidNumberOfExpressionInputs),
×
570
        })
571
    }
14✔
572

573
    fn result_descriptor(&self) -> &RasterResultDescriptor {
18✔
574
        &self.result_descriptor
18✔
575
    }
18✔
576

577
    fn canonic_name(&self) -> CanonicOperatorName {
×
578
        self.name.clone()
×
579
    }
×
580
}
581

582
#[cfg(test)]
583
mod tests {
584
    use super::*;
585
    use crate::engine::{MockExecutionContext, MockQueryContext, QueryProcessor};
586
    use crate::mock::{MockRasterSource, MockRasterSourceParams};
587
    use futures::StreamExt;
588
    use geoengine_datatypes::primitives::{BandSelection, CacheHint, CacheTtlSeconds};
589
    use geoengine_datatypes::primitives::{
590
        Measurement, RasterQueryRectangle, SpatialPartition2D, SpatialResolution, TimeInterval,
591
    };
592
    use geoengine_datatypes::raster::{
593
        Grid2D, GridOrEmpty, MapElements, MaskedGrid2D, RasterTile2D, TileInformation,
594
        TilingSpecification,
595
    };
596
    use geoengine_datatypes::spatial_reference::SpatialReference;
597
    use geoengine_datatypes::util::test::TestDefault;
598

599
    #[test]
1✔
600
    fn deserialize_params() {
1✔
601
        let s =
1✔
602
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
603

1✔
604
        assert_eq!(
1✔
605
            serde_json::from_str::<ExpressionParams>(s).unwrap(),
1✔
606
            ExpressionParams {
1✔
607
                expression: "1*A".to_owned(),
1✔
608
                output_type: RasterDataType::F64,
1✔
609
                output_measurement: None,
1✔
610
                map_no_data: false,
1✔
611
            }
1✔
612
        );
1✔
613
    }
1✔
614

615
    #[test]
1✔
616
    fn serialize_params() {
1✔
617
        let s =
1✔
618
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
619

1✔
620
        assert_eq!(
1✔
621
            s,
1✔
622
            serde_json::to_string(&ExpressionParams {
1✔
623
                expression: "1*A".to_owned(),
1✔
624
                output_type: RasterDataType::F64,
1✔
625
                output_measurement: None,
1✔
626
                map_no_data: false,
1✔
627
            })
1✔
628
            .unwrap()
1✔
629
        );
1✔
630
    }
1✔
631

632
    #[test]
1✔
633
    fn serialize_params_no_data() {
1✔
634
        let s =
1✔
635
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
636

1✔
637
        assert_eq!(
1✔
638
            s,
1✔
639
            serde_json::to_string(&ExpressionParams {
1✔
640
                expression: "1*A".to_owned(),
1✔
641
                output_type: RasterDataType::F64,
1✔
642
                output_measurement: None,
1✔
643
                map_no_data: false,
1✔
644
            })
1✔
645
            .unwrap()
1✔
646
        );
1✔
647
    }
1✔
648

649
    #[tokio::test]
1✔
650
    async fn basic_unary() {
1✔
651
        let tile_size_in_pixels = [3, 2].into();
1✔
652
        let tiling_specification = TilingSpecification {
1✔
653
            origin_coordinate: [0.0, 0.0].into(),
1✔
654
            tile_size_in_pixels,
1✔
655
        };
1✔
656

1✔
657
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
658

1✔
659
        let raster_a = make_raster(Some(3));
1✔
660

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

1✔
684
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
685

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

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

705
        assert_eq!(result.len(), 1);
1✔
706

707
        assert_eq!(
1✔
708
            result[0].as_ref().unwrap().grid_array,
1✔
709
            GridOrEmpty::from(
1✔
710
                MaskedGrid2D::new(
1✔
711
                    Grid2D::new([3, 2].into(), vec![2, 4, 0, 8, 10, 12],).unwrap(),
1✔
712
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
713
                )
1✔
714
                .unwrap()
1✔
715
            )
1✔
716
        );
1✔
717
    }
718

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

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

1✔
729
        let raster_a = make_raster(Some(3));
1✔
730

731
        let o = Expression {
1✔
732
            params: ExpressionParams {
1✔
733
                expression: "2 * A".to_string(),
1✔
734
                output_type: RasterDataType::I8,
1✔
735
                output_measurement: Some(Measurement::Unitless),
1✔
736
                map_no_data: true,
1✔
737
            },
1✔
738
            sources: ExpressionSources {
1✔
739
                a: raster_a,
1✔
740
                b: None,
1✔
741
                c: None,
1✔
742
                d: None,
1✔
743
                e: None,
1✔
744
                f: None,
1✔
745
                g: None,
1✔
746
                h: None,
1✔
747
            },
1✔
748
        }
1✔
749
        .boxed()
1✔
750
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
751
        .await
×
752
        .unwrap();
1✔
753

1✔
754
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
755

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

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

775
        assert_eq!(result.len(), 1);
1✔
776

777
        assert_eq!(
1✔
778
            result[0].as_ref().unwrap().grid_array,
1✔
779
            GridOrEmpty::from(
1✔
780
                MaskedGrid2D::new(
1✔
781
                    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✔
782
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
783
                )
1✔
784
                .unwrap()
1✔
785
            )
1✔
786
        );
1✔
787
    }
788

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

1✔
797
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
798

1✔
799
        let raster_a = make_raster(None);
1✔
800
        let raster_b = make_raster(None);
1✔
801

802
        let o = Expression {
1✔
803
            params: ExpressionParams {
1✔
804
                expression: "A+B".to_string(),
1✔
805
                output_type: RasterDataType::I8,
1✔
806
                output_measurement: Some(Measurement::Unitless),
1✔
807
                map_no_data: false,
1✔
808
            },
1✔
809
            sources: ExpressionSources {
1✔
810
                a: raster_a,
1✔
811
                b: Some(raster_b),
1✔
812
                c: None,
1✔
813
                d: None,
1✔
814
                e: None,
1✔
815
                f: None,
1✔
816
                g: None,
1✔
817
                h: None,
1✔
818
            },
1✔
819
        }
1✔
820
        .boxed()
1✔
821
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
822
        .await
×
823
        .unwrap();
1✔
824

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

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

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

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

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

856
    #[tokio::test]
1✔
857
    async fn basic_coalesce() {
1✔
858
        let tile_size_in_pixels = [3, 2].into();
1✔
859
        let tiling_specification = TilingSpecification {
1✔
860
            origin_coordinate: [0.0, 0.0].into(),
1✔
861
            tile_size_in_pixels,
1✔
862
        };
1✔
863

1✔
864
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
865

1✔
866
        let raster_a = make_raster(Some(3));
1✔
867
        let raster_b = make_raster(None);
1✔
868

869
        let o = Expression {
1✔
870
            params: ExpressionParams {
1✔
871
                expression: "if A IS NODATA {
1✔
872
                       B * 2
1✔
873
                   } else if A == 6 {
1✔
874
                       NODATA
1✔
875
                   } else {
1✔
876
                       A
1✔
877
                   }"
1✔
878
                .to_string(),
1✔
879
                output_type: RasterDataType::I8,
1✔
880
                output_measurement: Some(Measurement::Unitless),
1✔
881
                map_no_data: true,
1✔
882
            },
1✔
883
            sources: ExpressionSources {
1✔
884
                a: raster_a,
1✔
885
                b: Some(raster_b),
1✔
886
                c: None,
1✔
887
                d: None,
1✔
888
                e: None,
1✔
889
                f: None,
1✔
890
                g: None,
1✔
891
                h: None,
1✔
892
            },
1✔
893
        }
1✔
894
        .boxed()
1✔
895
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
896
        .await
×
897
        .unwrap();
1✔
898

1✔
899
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
900

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

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

920
        assert_eq!(result.len(), 1);
1✔
921

922
        assert_eq!(
1✔
923
            result[0].as_ref().unwrap().grid_array,
1✔
924
            GridOrEmpty::from(
1✔
925
                MaskedGrid2D::new(
1✔
926
                    Grid2D::new([3, 2].into(), vec![1, 2, 6, 4, 5, 0],).unwrap(),
1✔
927
                    Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false],).unwrap()
1✔
928
                )
1✔
929
                .unwrap()
1✔
930
            )
1✔
931
        );
1✔
932
    }
933

934
    #[tokio::test]
1✔
935
    async fn basic_ternary() {
1✔
936
        let no_data_value = 3;
1✔
937
        let no_data_value_option = Some(no_data_value);
1✔
938

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

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

1✔
947
        let raster_a = make_raster(no_data_value_option);
1✔
948
        let raster_b = make_raster(no_data_value_option);
1✔
949
        let raster_c = make_raster(no_data_value_option);
1✔
950

951
        let o = Expression {
1✔
952
            params: ExpressionParams {
1✔
953
                expression: "A+B+C".to_string(),
1✔
954
                output_type: RasterDataType::I8,
1✔
955
                output_measurement: Some(Measurement::Unitless),
1✔
956
                map_no_data: false,
1✔
957
            },
1✔
958
            sources: ExpressionSources {
1✔
959
                a: raster_a,
1✔
960
                b: Some(raster_b),
1✔
961
                c: Some(raster_c),
1✔
962
                d: None,
1✔
963
                e: None,
1✔
964
                f: None,
1✔
965
                g: None,
1✔
966
                h: None,
1✔
967
            },
1✔
968
        }
1✔
969
        .boxed()
1✔
970
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
971
        .await
×
972
        .unwrap();
1✔
973

1✔
974
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
975

1✔
976
        let ctx = MockQueryContext::new(1.into());
1✔
977
        let result_stream = processor
1✔
978
            .query(
1✔
979
                RasterQueryRectangle {
1✔
980
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
981
                        (0., 3.).into(),
1✔
982
                        (2., 0.).into(),
1✔
983
                    ),
1✔
984
                    time_interval: Default::default(),
1✔
985
                    spatial_resolution: SpatialResolution::one(),
1✔
986
                    attributes: BandSelection::first(),
1✔
987
                },
1✔
988
                &ctx,
1✔
989
            )
1✔
990
            .await
×
991
            .unwrap();
1✔
992

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

995
        assert_eq!(result.len(), 1);
1✔
996

997
        let first_result = result[0].as_ref().unwrap();
1✔
998

1✔
999
        assert!(!first_result.is_empty());
1✔
1000

1001
        let grid = match &first_result.grid_array {
1✔
1002
            GridOrEmpty::Grid(g) => g,
1✔
1003
            GridOrEmpty::Empty(_) => panic!(),
×
1004
        };
1005

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

1✔
1008
        assert_eq!(
1✔
1009
            res,
1✔
1010
            [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✔
1011
        );
1✔
1012
    }
1013

1014
    #[tokio::test]
1✔
1015
    async fn octave_inputs() {
1✔
1016
        let no_data_value = 0;
1✔
1017
        let no_data_value_option = Some(no_data_value);
1✔
1018

1✔
1019
        let tile_size_in_pixels = [3, 2].into();
1✔
1020
        let tiling_specification = TilingSpecification {
1✔
1021
            origin_coordinate: [0.0, 0.0].into(),
1✔
1022
            tile_size_in_pixels,
1✔
1023
        };
1✔
1024

1✔
1025
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1026

1✔
1027
        let raster_a = make_raster(no_data_value_option);
1✔
1028
        let raster_b = make_raster(no_data_value_option);
1✔
1029
        let raster_c = make_raster(no_data_value_option);
1✔
1030
        let raster_d = make_raster(no_data_value_option);
1✔
1031
        let raster_e = make_raster(no_data_value_option);
1✔
1032
        let raster_f = make_raster(no_data_value_option);
1✔
1033
        let raster_g = make_raster(no_data_value_option);
1✔
1034
        let raster_h = make_raster(no_data_value_option);
1✔
1035

1036
        let o = Expression {
1✔
1037
            params: ExpressionParams {
1✔
1038
                expression: "A+B+C+D+E+F+G+H".to_string(),
1✔
1039
                output_type: RasterDataType::I8,
1✔
1040
                output_measurement: Some(Measurement::Unitless),
1✔
1041
                map_no_data: false,
1✔
1042
            },
1✔
1043
            sources: ExpressionSources {
1✔
1044
                a: raster_a,
1✔
1045
                b: Some(raster_b),
1✔
1046
                c: Some(raster_c),
1✔
1047
                d: Some(raster_d),
1✔
1048
                e: Some(raster_e),
1✔
1049
                f: Some(raster_f),
1✔
1050
                g: Some(raster_g),
1✔
1051
                h: Some(raster_h),
1✔
1052
            },
1✔
1053
        }
1✔
1054
        .boxed()
1✔
1055
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
1056
        .await
×
1057
        .unwrap();
1✔
1058

1✔
1059
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1060

1✔
1061
        let ctx = MockQueryContext::new(1.into());
1✔
1062
        let result_stream = processor
1✔
1063
            .query(
1✔
1064
                RasterQueryRectangle {
1✔
1065
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1066
                        (0., 3.).into(),
1✔
1067
                        (2., 0.).into(),
1✔
1068
                    ),
1✔
1069
                    time_interval: Default::default(),
1✔
1070
                    spatial_resolution: SpatialResolution::one(),
1✔
1071
                    attributes: BandSelection::first(),
1✔
1072
                },
1✔
1073
                &ctx,
1✔
1074
            )
1✔
1075
            .await
×
1076
            .unwrap();
1✔
1077

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

1080
        assert_eq!(result.len(), 1);
1✔
1081

1082
        assert_eq!(
1✔
1083
            result[0].as_ref().unwrap().grid_array,
1✔
1084
            Grid2D::new([3, 2].into(), vec![8, 16, 24, 32, 40, 48],)
1✔
1085
                .unwrap()
1✔
1086
                .into()
1✔
1087
        );
1✔
1088
    }
1089

1090
    #[tokio::test]
1✔
1091
    async fn test_functions() {
1✔
1092
        let no_data_value = 0;
1✔
1093
        let tile_size_in_pixels = [3, 2].into();
1✔
1094
        let tiling_specification = TilingSpecification {
1✔
1095
            origin_coordinate: [0.0, 0.0].into(),
1✔
1096
            tile_size_in_pixels,
1✔
1097
        };
1✔
1098

1✔
1099
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1100

1✔
1101
        let raster_a = make_raster(Some(no_data_value));
1✔
1102

1103
        let o = Expression {
1✔
1104
            params: ExpressionParams {
1✔
1105
                expression: "min(A * pi(), 10)".to_string(),
1✔
1106
                output_type: RasterDataType::I8,
1✔
1107
                output_measurement: Some(Measurement::Unitless),
1✔
1108
                map_no_data: false,
1✔
1109
            },
1✔
1110
            sources: ExpressionSources {
1✔
1111
                a: raster_a,
1✔
1112
                b: None,
1✔
1113
                c: None,
1✔
1114
                d: None,
1✔
1115
                e: None,
1✔
1116
                f: None,
1✔
1117
                g: None,
1✔
1118
                h: None,
1✔
1119
            },
1✔
1120
        }
1✔
1121
        .boxed()
1✔
1122
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1123
        .await
×
1124
        .unwrap();
1✔
1125

1✔
1126
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1127

1✔
1128
        let ctx = MockQueryContext::new(1.into());
1✔
1129
        let result_stream = processor
1✔
1130
            .query(
1✔
1131
                RasterQueryRectangle {
1✔
1132
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1133
                        (0., 3.).into(),
1✔
1134
                        (2., 0.).into(),
1✔
1135
                    ),
1✔
1136
                    time_interval: Default::default(),
1✔
1137
                    spatial_resolution: SpatialResolution::one(),
1✔
1138
                    attributes: BandSelection::first(),
1✔
1139
                },
1✔
1140
                &ctx,
1✔
1141
            )
1✔
1142
            .await
×
1143
            .unwrap();
1✔
1144

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

1147
        assert_eq!(result.len(), 1);
1✔
1148

1149
        assert_eq!(
1✔
1150
            result[0].as_ref().unwrap().grid_array,
1✔
1151
            Grid2D::new([3, 2].into(), vec![3, 6, 9, 10, 10, 10],)
1✔
1152
                .unwrap()
1✔
1153
                .into()
1✔
1154
        );
1✔
1155
    }
1156

1157
    fn make_raster(no_data_value: Option<i8>) -> Box<dyn RasterOperator> {
18✔
1158
        make_raster_with_cache_hint(no_data_value, CacheHint::no_cache())
18✔
1159
    }
18✔
1160

1161
    fn make_raster_with_cache_hint(
24✔
1162
        no_data_value: Option<i8>,
24✔
1163
        cache_hint: CacheHint,
24✔
1164
    ) -> Box<dyn RasterOperator> {
24✔
1165
        let raster = Grid2D::<i8>::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6]).unwrap();
24✔
1166

1167
        let real_raster = if let Some(no_data_value) = no_data_value {
24✔
1168
            MaskedGrid2D::from(raster)
21✔
1169
                .map_elements(|e: Option<i8>| e.filter(|v| *v != no_data_value))
126✔
1170
                .into()
21✔
1171
        } else {
1172
            GridOrEmpty::from(raster)
3✔
1173
        };
1174

1175
        let raster_tile = RasterTile2D::new_with_tile_info(
24✔
1176
            TimeInterval::default(),
24✔
1177
            TileInformation {
24✔
1178
                global_tile_position: [-1, 0].into(),
24✔
1179
                tile_size_in_pixels: [3, 2].into(),
24✔
1180
                global_geo_transform: TestDefault::test_default(),
24✔
1181
            },
24✔
1182
            0,
24✔
1183
            real_raster,
24✔
1184
            cache_hint,
24✔
1185
        );
24✔
1186

24✔
1187
        MockRasterSource {
24✔
1188
            params: MockRasterSourceParams {
24✔
1189
                data: vec![raster_tile],
24✔
1190
                result_descriptor: RasterResultDescriptor {
24✔
1191
                    data_type: RasterDataType::I8,
24✔
1192
                    spatial_reference: SpatialReference::epsg_4326().into(),
24✔
1193
                    time: None,
24✔
1194
                    bbox: None,
24✔
1195
                    resolution: None,
24✔
1196
                    bands: RasterBandDescriptors::new_single_band(),
24✔
1197
                },
24✔
1198
            },
24✔
1199
        }
24✔
1200
        .boxed()
24✔
1201
    }
24✔
1202

1203
    #[tokio::test]
1✔
1204
    async fn it_attaches_cache_hint_1() {
1✔
1205
        let no_data_value = 0;
1✔
1206
        let tile_size_in_pixels = [3, 2].into();
1✔
1207
        let tiling_specification = TilingSpecification {
1✔
1208
            origin_coordinate: [0.0, 0.0].into(),
1✔
1209
            tile_size_in_pixels,
1✔
1210
        };
1✔
1211

1✔
1212
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1213

1✔
1214
        let cache_hint = CacheHint::seconds(1234);
1✔
1215
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint);
1✔
1216

1217
        let o = Expression {
1✔
1218
            params: ExpressionParams {
1✔
1219
                expression: "min(A * pi(), 10)".to_string(),
1✔
1220
                output_type: RasterDataType::I8,
1✔
1221
                output_measurement: Some(Measurement::Unitless),
1✔
1222
                map_no_data: false,
1✔
1223
            },
1✔
1224
            sources: ExpressionSources {
1✔
1225
                a: raster_a,
1✔
1226
                b: None,
1✔
1227
                c: None,
1✔
1228
                d: None,
1✔
1229
                e: None,
1✔
1230
                f: None,
1✔
1231
                g: None,
1✔
1232
                h: None,
1✔
1233
            },
1✔
1234
        }
1✔
1235
        .boxed()
1✔
1236
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1237
        .await
×
1238
        .unwrap();
1✔
1239

1✔
1240
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1241

1✔
1242
        let ctx = MockQueryContext::new(1.into());
1✔
1243
        let result_stream = processor
1✔
1244
            .query(
1✔
1245
                RasterQueryRectangle {
1✔
1246
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1247
                        (0., 3.).into(),
1✔
1248
                        (2., 0.).into(),
1✔
1249
                    ),
1✔
1250
                    time_interval: Default::default(),
1✔
1251
                    spatial_resolution: SpatialResolution::one(),
1✔
1252
                    attributes: BandSelection::first(),
1✔
1253
                },
1✔
1254
                &ctx,
1✔
1255
            )
1✔
1256
            .await
×
1257
            .unwrap();
1✔
1258

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

1261
        assert_eq!(result.len(), 1);
1✔
1262

1263
        assert!(
1✔
1264
            result[0].as_ref().unwrap().cache_hint.total_ttl_seconds() > CacheTtlSeconds::new(0)
1✔
1265
                && result[0].as_ref().unwrap().cache_hint.total_ttl_seconds()
1✔
1266
                    <= cache_hint.total_ttl_seconds()
1✔
1267
        );
1268
    }
1269

1270
    #[tokio::test]
1✔
1271
    async fn it_attaches_cache_hint_2() {
1✔
1272
        let no_data_value = 0;
1✔
1273
        let tile_size_in_pixels = [3, 2].into();
1✔
1274
        let tiling_specification = TilingSpecification {
1✔
1275
            origin_coordinate: [0.0, 0.0].into(),
1✔
1276
            tile_size_in_pixels,
1✔
1277
        };
1✔
1278

1✔
1279
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1280

1✔
1281
        let cache_hint_a = CacheHint::seconds(1234);
1✔
1282
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint_a);
1✔
1283

1✔
1284
        let cache_hint_b = CacheHint::seconds(4567);
1✔
1285
        let raster_b = make_raster_with_cache_hint(Some(no_data_value), cache_hint_b);
1✔
1286

1287
        let o = Expression {
1✔
1288
            params: ExpressionParams {
1✔
1289
                expression: "A + B".to_string(),
1✔
1290
                output_type: RasterDataType::I8,
1✔
1291
                output_measurement: Some(Measurement::Unitless),
1✔
1292
                map_no_data: false,
1✔
1293
            },
1✔
1294
            sources: ExpressionSources {
1✔
1295
                a: raster_a,
1✔
1296
                b: Some(raster_b),
1✔
1297
                c: None,
1✔
1298
                d: None,
1✔
1299
                e: None,
1✔
1300
                f: None,
1✔
1301
                g: None,
1✔
1302
                h: None,
1✔
1303
            },
1✔
1304
        }
1✔
1305
        .boxed()
1✔
1306
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1307
        .await
×
1308
        .unwrap();
1✔
1309

1✔
1310
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1311

1✔
1312
        let ctx = MockQueryContext::new(1.into());
1✔
1313
        let result_stream = processor
1✔
1314
            .query(
1✔
1315
                RasterQueryRectangle {
1✔
1316
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1317
                        (0., 3.).into(),
1✔
1318
                        (2., 0.).into(),
1✔
1319
                    ),
1✔
1320
                    time_interval: Default::default(),
1✔
1321
                    spatial_resolution: SpatialResolution::one(),
1✔
1322
                    attributes: BandSelection::first(),
1✔
1323
                },
1✔
1324
                &ctx,
1✔
1325
            )
1✔
1326
            .await
×
1327
            .unwrap();
1✔
1328

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

1331
        assert_eq!(result.len(), 1);
1✔
1332

1333
        assert_eq!(
1✔
1334
            result[0].as_ref().unwrap().cache_hint.expires(),
1✔
1335
            cache_hint_a.merged(&cache_hint_b).expires()
1✔
1336
        );
1✔
1337
    }
1338

1339
    #[tokio::test]
1✔
1340
    async fn it_attaches_cache_hint_3() {
1✔
1341
        let no_data_value = 0;
1✔
1342
        let tile_size_in_pixels = [3, 2].into();
1✔
1343
        let tiling_specification = TilingSpecification {
1✔
1344
            origin_coordinate: [0.0, 0.0].into(),
1✔
1345
            tile_size_in_pixels,
1✔
1346
        };
1✔
1347

1✔
1348
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1349

1✔
1350
        let cache_hint_a = CacheHint::seconds(1234);
1✔
1351
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint_a);
1✔
1352

1✔
1353
        let cache_hint_b = CacheHint::seconds(4567);
1✔
1354
        let raster_b = make_raster_with_cache_hint(Some(no_data_value), cache_hint_b);
1✔
1355

1✔
1356
        let cache_hint_c = CacheHint::seconds(7891);
1✔
1357
        let raster_c = make_raster_with_cache_hint(Some(no_data_value), cache_hint_c);
1✔
1358

1359
        let o = Expression {
1✔
1360
            params: ExpressionParams {
1✔
1361
                expression: "A + B".to_string(),
1✔
1362
                output_type: RasterDataType::I8,
1✔
1363
                output_measurement: Some(Measurement::Unitless),
1✔
1364
                map_no_data: false,
1✔
1365
            },
1✔
1366
            sources: ExpressionSources {
1✔
1367
                a: raster_a,
1✔
1368
                b: Some(raster_b),
1✔
1369
                c: Some(raster_c),
1✔
1370
                d: None,
1✔
1371
                e: None,
1✔
1372
                f: None,
1✔
1373
                g: None,
1✔
1374
                h: None,
1✔
1375
            },
1✔
1376
        }
1✔
1377
        .boxed()
1✔
1378
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1379
        .await
×
1380
        .unwrap();
1✔
1381

1✔
1382
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1383

1✔
1384
        let ctx = MockQueryContext::new(1.into());
1✔
1385
        let result_stream = processor
1✔
1386
            .query(
1✔
1387
                RasterQueryRectangle {
1✔
1388
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1389
                        (0., 3.).into(),
1✔
1390
                        (2., 0.).into(),
1✔
1391
                    ),
1✔
1392
                    time_interval: Default::default(),
1✔
1393
                    spatial_resolution: SpatialResolution::one(),
1✔
1394
                    attributes: BandSelection::first(),
1✔
1395
                },
1✔
1396
                &ctx,
1✔
1397
            )
1✔
1398
            .await
×
1399
            .unwrap();
1✔
1400

1401
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
4✔
1402

1403
        assert_eq!(result.len(), 1);
1✔
1404

1405
        assert_eq!(
1✔
1406
            result[0].as_ref().unwrap().cache_hint.expires(),
1✔
1407
            cache_hint_a
1✔
1408
                .merged(&cache_hint_b)
1✔
1409
                .merged(&cache_hint_c)
1✔
1410
                .expires()
1✔
1411
        );
1✔
1412
    }
1413
}
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

© 2026 Coveralls, Inc