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

geo-engine / geoengine / 7006568925

27 Nov 2023 02:07PM UTC coverage: 89.651% (+0.2%) from 89.498%
7006568925

push

github

web-flow
Merge pull request #888 from geo-engine/raster_stacks

raster stacking

4032 of 4274 new or added lines in 107 files covered. (94.34%)

12 existing lines in 8 files now uncovered.

113020 of 126066 relevant lines covered (89.65%)

59901.79 hits per line

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

89.59
/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)]
17✔
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)]
15✔
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 {
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 {
26✔
120
        self.iter().count()
26✔
121
    }
26✔
122

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

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

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

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

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

187
    /// Returns all sources until the first one is empty
188
    fn iter_consecutive(&self) -> impl Iterator<Item = &Box<dyn RasterOperator>> {
13✔
189
        [
13✔
190
            Some(&self.a),
13✔
191
            self.b.as_ref(),
13✔
192
            self.c.as_ref(),
13✔
193
            self.d.as_ref(),
13✔
194
            self.e.as_ref(),
13✔
195
            self.f.as_ref(),
13✔
196
            self.g.as_ref(),
13✔
197
            self.h.as_ref(),
13✔
198
        ]
13✔
199
        .into_iter()
13✔
200
        .map_while(std::convert::identity)
13✔
201
    }
13✔
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 {
29✔
212
    let index = index as u32;
29✔
213
    let start_index = 'A' as u32;
29✔
214

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

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

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

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

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

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

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

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

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

13✔
261
        // TODO: implement multi-band functionality and remove this check
13✔
262
        ensure!(
13✔
263
            in_descriptors.iter().all(|r| r.bands.len() == 1),
29✔
NEW
264
            crate::error::OperatorDoesNotSupportMultiBandsSourcesYet {
×
NEW
265
                operator: Expression::TYPE_NAME
×
NEW
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));
14✔
281
        let bbox = partitions_extent(in_descriptors.iter().map(|d| d.bbox));
14✔
282

13✔
283
        let resolution = in_descriptors
13✔
284
            .iter()
13✔
285
            .map(|d| d.resolution)
29✔
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();
13✔
293

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

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

13✔
318
        Ok(initialized_operator.boxed())
13✔
319
    }
26✔
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>> {
26✔
349
        [
26✔
350
            Some(&self.a),
26✔
351
            self.b.as_ref(),
26✔
352
            self.c.as_ref(),
26✔
353
            self.d.as_ref(),
26✔
354
            self.e.as_ref(),
26✔
355
            self.f.as_ref(),
26✔
356
            self.g.as_ref(),
26✔
357
            self.h.as_ref(),
26✔
358
        ]
26✔
359
        .into_iter()
26✔
360
        .flatten()
26✔
361
    }
26✔
362
}
363

364
#[async_trait]
365
impl InitializedSources<ExpressionInitializedSources> for ExpressionSources {
366
    async fn initialize_sources(
13✔
367
        self,
13✔
368
        path: WorkflowOperatorPath,
13✔
369
        context: &dyn ExecutionContext,
13✔
370
    ) -> Result<ExpressionInitializedSources> {
13✔
371
        self.initialize(path, context).await
13✔
372
    }
26✔
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> {
13✔
378
        let output_type = self.result_descriptor().data_type;
13✔
379

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

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

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

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

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

492
            7 => {
493
                let [a, b, c, d, e, f, g] =
×
494
                    <[_; 7]>::try_from(query_processors).expect("len previously checked");
×
495
                let query_processors = [
×
496
                    a.into_f64(),
×
497
                    b.into_f64(),
×
498
                    c.into_f64(),
×
499
                    d.into_f64(),
×
500
                    e.into_f64(),
×
501
                    f.into_f64(),
×
502
                    g.into_f64(),
×
503
                ];
×
504
                call_generic_raster_processor!(
×
505
                    output_type,
×
506
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
×
507
                        .boxed()
×
508
                )
509
            }
510
            8 => {
511
                let [a, b, c, d, e, f, g, h] =
1✔
512
                    <[_; 8]>::try_from(query_processors).expect("len previously checked");
1✔
513
                let query_processors = [
1✔
514
                    a.into_f64(),
1✔
515
                    b.into_f64(),
1✔
516
                    c.into_f64(),
1✔
517
                    d.into_f64(),
1✔
518
                    e.into_f64(),
1✔
519
                    f.into_f64(),
1✔
520
                    g.into_f64(),
1✔
521
                    h.into_f64(),
1✔
522
                ];
1✔
523
                call_generic_raster_processor!(
×
524
                    output_type,
1✔
525
                    ExpressionQueryProcessor::new(expression, query_processors, self.map_no_data)
1✔
526
                        .boxed()
1✔
527
                )
528
            }
529
            _ => return Err(crate::error::Error::InvalidNumberOfExpressionInputs),
×
530
        })
531
    }
13✔
532

533
    fn result_descriptor(&self) -> &RasterResultDescriptor {
16✔
534
        &self.result_descriptor
16✔
535
    }
16✔
536

537
    fn canonic_name(&self) -> CanonicOperatorName {
×
538
        self.name.clone()
×
539
    }
×
540
}
541

542
#[cfg(test)]
543
mod tests {
544
    use super::*;
545
    use crate::engine::{MockExecutionContext, MockQueryContext, QueryProcessor};
546
    use crate::mock::{MockRasterSource, MockRasterSourceParams};
547
    use futures::StreamExt;
548
    use geoengine_datatypes::primitives::{BandSelection, CacheHint, CacheTtlSeconds};
549
    use geoengine_datatypes::primitives::{
550
        Measurement, RasterQueryRectangle, SpatialPartition2D, SpatialResolution, TimeInterval,
551
    };
552
    use geoengine_datatypes::raster::{
553
        Grid2D, GridOrEmpty, MapElements, MaskedGrid2D, RasterTile2D, TileInformation,
554
        TilingSpecification,
555
    };
556
    use geoengine_datatypes::spatial_reference::SpatialReference;
557
    use geoengine_datatypes::util::test::TestDefault;
558

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

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

575
    #[test]
1✔
576
    fn serialize_params() {
1✔
577
        let s =
1✔
578
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
579

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

592
    #[test]
1✔
593
    fn serialize_params_no_data() {
1✔
594
        let s =
1✔
595
            r#"{"expression":"1*A","outputType":"F64","outputMeasurement":null,"mapNoData":false}"#;
1✔
596

1✔
597
        assert_eq!(
1✔
598
            s,
1✔
599
            serde_json::to_string(&ExpressionParams {
1✔
600
                expression: "1*A".to_owned(),
1✔
601
                output_type: RasterDataType::F64,
1✔
602
                output_measurement: None,
1✔
603
                map_no_data: false,
1✔
604
            })
1✔
605
            .unwrap()
1✔
606
        );
1✔
607
    }
1✔
608

609
    #[tokio::test]
1✔
610
    async fn basic_unary() {
1✔
611
        let tile_size_in_pixels = [3, 2].into();
1✔
612
        let tiling_specification = TilingSpecification {
1✔
613
            origin_coordinate: [0.0, 0.0].into(),
1✔
614
            tile_size_in_pixels,
1✔
615
        };
1✔
616

1✔
617
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
618

1✔
619
        let raster_a = make_raster(Some(3));
1✔
620

621
        let o = Expression {
1✔
622
            params: ExpressionParams {
1✔
623
                expression: "2 * A".to_string(),
1✔
624
                output_type: RasterDataType::I8,
1✔
625
                output_measurement: Some(Measurement::Unitless),
1✔
626
                map_no_data: false,
1✔
627
            },
1✔
628
            sources: ExpressionSources {
1✔
629
                a: raster_a,
1✔
630
                b: None,
1✔
631
                c: None,
1✔
632
                d: None,
1✔
633
                e: None,
1✔
634
                f: None,
1✔
635
                g: None,
1✔
636
                h: None,
1✔
637
            },
1✔
638
        }
1✔
639
        .boxed()
1✔
640
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
641
        .await
×
642
        .unwrap();
1✔
643

1✔
644
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
645

1✔
646
        let ctx = MockQueryContext::new(1.into());
1✔
647
        let result_stream = processor
1✔
648
            .query(
1✔
649
                RasterQueryRectangle {
1✔
650
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
651
                        (0., 3.).into(),
1✔
652
                        (2., 0.).into(),
1✔
653
                    ),
1✔
654
                    time_interval: Default::default(),
1✔
655
                    spatial_resolution: SpatialResolution::one(),
1✔
656
                    attributes: BandSelection::first(),
1✔
657
                },
1✔
658
                &ctx,
1✔
659
            )
1✔
660
            .await
×
661
            .unwrap();
1✔
662

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

665
        assert_eq!(result.len(), 1);
1✔
666

667
        assert_eq!(
1✔
668
            result[0].as_ref().unwrap().grid_array,
1✔
669
            GridOrEmpty::from(
1✔
670
                MaskedGrid2D::new(
1✔
671
                    Grid2D::new([3, 2].into(), vec![2, 4, 0, 8, 10, 12],).unwrap(),
1✔
672
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
673
                )
1✔
674
                .unwrap()
1✔
675
            )
1✔
676
        );
1✔
677
    }
678

679
    #[tokio::test]
1✔
680
    async fn unary_map_no_data() {
1✔
681
        let tile_size_in_pixels = [3, 2].into();
1✔
682
        let tiling_specification = TilingSpecification {
1✔
683
            origin_coordinate: [0.0, 0.0].into(),
1✔
684
            tile_size_in_pixels,
1✔
685
        };
1✔
686

1✔
687
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
688

1✔
689
        let raster_a = make_raster(Some(3));
1✔
690

691
        let o = Expression {
1✔
692
            params: ExpressionParams {
1✔
693
                expression: "2 * A".to_string(),
1✔
694
                output_type: RasterDataType::I8,
1✔
695
                output_measurement: Some(Measurement::Unitless),
1✔
696
                map_no_data: true,
1✔
697
            },
1✔
698
            sources: ExpressionSources {
1✔
699
                a: raster_a,
1✔
700
                b: None,
1✔
701
                c: None,
1✔
702
                d: None,
1✔
703
                e: None,
1✔
704
                f: None,
1✔
705
                g: None,
1✔
706
                h: None,
1✔
707
            },
1✔
708
        }
1✔
709
        .boxed()
1✔
710
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
711
        .await
×
712
        .unwrap();
1✔
713

1✔
714
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
715

1✔
716
        let ctx = MockQueryContext::new(1.into());
1✔
717
        let result_stream = processor
1✔
718
            .query(
1✔
719
                RasterQueryRectangle {
1✔
720
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
721
                        (0., 3.).into(),
1✔
722
                        (2., 0.).into(),
1✔
723
                    ),
1✔
724
                    time_interval: Default::default(),
1✔
725
                    spatial_resolution: SpatialResolution::one(),
1✔
726
                    attributes: BandSelection::first(),
1✔
727
                },
1✔
728
                &ctx,
1✔
729
            )
1✔
730
            .await
×
731
            .unwrap();
1✔
732

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

735
        assert_eq!(result.len(), 1);
1✔
736

737
        assert_eq!(
1✔
738
            result[0].as_ref().unwrap().grid_array,
1✔
739
            GridOrEmpty::from(
1✔
740
                MaskedGrid2D::new(
1✔
741
                    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✔
742
                    Grid2D::new([3, 2].into(), vec![true, true, false, true, true, true],).unwrap()
1✔
743
                )
1✔
744
                .unwrap()
1✔
745
            )
1✔
746
        );
1✔
747
    }
748

749
    #[tokio::test]
1✔
750
    async fn basic_binary() {
1✔
751
        let tile_size_in_pixels = [3, 2].into();
1✔
752
        let tiling_specification = TilingSpecification {
1✔
753
            origin_coordinate: [0.0, 0.0].into(),
1✔
754
            tile_size_in_pixels,
1✔
755
        };
1✔
756

1✔
757
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
758

1✔
759
        let raster_a = make_raster(None);
1✔
760
        let raster_b = make_raster(None);
1✔
761

762
        let o = Expression {
1✔
763
            params: ExpressionParams {
1✔
764
                expression: "A+B".to_string(),
1✔
765
                output_type: RasterDataType::I8,
1✔
766
                output_measurement: Some(Measurement::Unitless),
1✔
767
                map_no_data: false,
1✔
768
            },
1✔
769
            sources: ExpressionSources {
1✔
770
                a: raster_a,
1✔
771
                b: Some(raster_b),
1✔
772
                c: None,
1✔
773
                d: None,
1✔
774
                e: None,
1✔
775
                f: None,
1✔
776
                g: None,
1✔
777
                h: None,
1✔
778
            },
1✔
779
        }
1✔
780
        .boxed()
1✔
781
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
782
        .await
×
783
        .unwrap();
1✔
784

1✔
785
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
786

1✔
787
        let ctx = MockQueryContext::new(1.into());
1✔
788
        let result_stream = processor
1✔
789
            .query(
1✔
790
                RasterQueryRectangle {
1✔
791
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
792
                        (0., 3.).into(),
1✔
793
                        (2., 0.).into(),
1✔
794
                    ),
1✔
795
                    time_interval: Default::default(),
1✔
796
                    spatial_resolution: SpatialResolution::one(),
1✔
797
                    attributes: BandSelection::first(),
1✔
798
                },
1✔
799
                &ctx,
1✔
800
            )
1✔
801
            .await
×
802
            .unwrap();
1✔
803

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

806
        assert_eq!(result.len(), 1);
1✔
807

808
        assert_eq!(
1✔
809
            result[0].as_ref().unwrap().grid_array,
1✔
810
            Grid2D::new([3, 2].into(), vec![2, 4, 6, 8, 10, 12],)
1✔
811
                .unwrap()
1✔
812
                .into()
1✔
813
        );
1✔
814
    }
815

816
    #[tokio::test]
1✔
817
    async fn basic_coalesce() {
1✔
818
        let tile_size_in_pixels = [3, 2].into();
1✔
819
        let tiling_specification = TilingSpecification {
1✔
820
            origin_coordinate: [0.0, 0.0].into(),
1✔
821
            tile_size_in_pixels,
1✔
822
        };
1✔
823

1✔
824
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
825

1✔
826
        let raster_a = make_raster(Some(3));
1✔
827
        let raster_b = make_raster(None);
1✔
828

829
        let o = Expression {
1✔
830
            params: ExpressionParams {
1✔
831
                expression: "if A IS NODATA {
1✔
832
                       B * 2
1✔
833
                   } else if A == 6 {
1✔
834
                       NODATA
1✔
835
                   } else {
1✔
836
                       A
1✔
837
                   }"
1✔
838
                .to_string(),
1✔
839
                output_type: RasterDataType::I8,
1✔
840
                output_measurement: Some(Measurement::Unitless),
1✔
841
                map_no_data: true,
1✔
842
            },
1✔
843
            sources: ExpressionSources {
1✔
844
                a: raster_a,
1✔
845
                b: Some(raster_b),
1✔
846
                c: None,
1✔
847
                d: None,
1✔
848
                e: None,
1✔
849
                f: None,
1✔
850
                g: None,
1✔
851
                h: None,
1✔
852
            },
1✔
853
        }
1✔
854
        .boxed()
1✔
855
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
856
        .await
×
857
        .unwrap();
1✔
858

1✔
859
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
860

1✔
861
        let ctx = MockQueryContext::new(1.into());
1✔
862
        let result_stream = processor
1✔
863
            .query(
1✔
864
                RasterQueryRectangle {
1✔
865
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
866
                        (0., 3.).into(),
1✔
867
                        (2., 0.).into(),
1✔
868
                    ),
1✔
869
                    time_interval: Default::default(),
1✔
870
                    spatial_resolution: SpatialResolution::one(),
1✔
871
                    attributes: BandSelection::first(),
1✔
872
                },
1✔
873
                &ctx,
1✔
874
            )
1✔
875
            .await
×
876
            .unwrap();
1✔
877

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

880
        assert_eq!(result.len(), 1);
1✔
881

882
        assert_eq!(
1✔
883
            result[0].as_ref().unwrap().grid_array,
1✔
884
            GridOrEmpty::from(
1✔
885
                MaskedGrid2D::new(
1✔
886
                    Grid2D::new([3, 2].into(), vec![1, 2, 6, 4, 5, 0],).unwrap(),
1✔
887
                    Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false],).unwrap()
1✔
888
                )
1✔
889
                .unwrap()
1✔
890
            )
1✔
891
        );
1✔
892
    }
893

894
    #[tokio::test]
1✔
895
    async fn basic_ternary() {
1✔
896
        let no_data_value = 3;
1✔
897
        let no_data_value_option = Some(no_data_value);
1✔
898

1✔
899
        let tile_size_in_pixels = [3, 2].into();
1✔
900
        let tiling_specification = TilingSpecification {
1✔
901
            origin_coordinate: [0.0, 0.0].into(),
1✔
902
            tile_size_in_pixels,
1✔
903
        };
1✔
904

1✔
905
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
906

1✔
907
        let raster_a = make_raster(no_data_value_option);
1✔
908
        let raster_b = make_raster(no_data_value_option);
1✔
909
        let raster_c = make_raster(no_data_value_option);
1✔
910

911
        let o = Expression {
1✔
912
            params: ExpressionParams {
1✔
913
                expression: "A+B+C".to_string(),
1✔
914
                output_type: RasterDataType::I8,
1✔
915
                output_measurement: Some(Measurement::Unitless),
1✔
916
                map_no_data: false,
1✔
917
            },
1✔
918
            sources: ExpressionSources {
1✔
919
                a: raster_a,
1✔
920
                b: Some(raster_b),
1✔
921
                c: Some(raster_c),
1✔
922
                d: None,
1✔
923
                e: None,
1✔
924
                f: None,
1✔
925
                g: None,
1✔
926
                h: None,
1✔
927
            },
1✔
928
        }
1✔
929
        .boxed()
1✔
930
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
931
        .await
×
932
        .unwrap();
1✔
933

1✔
934
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
935

1✔
936
        let ctx = MockQueryContext::new(1.into());
1✔
937
        let result_stream = processor
1✔
938
            .query(
1✔
939
                RasterQueryRectangle {
1✔
940
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
941
                        (0., 3.).into(),
1✔
942
                        (2., 0.).into(),
1✔
943
                    ),
1✔
944
                    time_interval: Default::default(),
1✔
945
                    spatial_resolution: SpatialResolution::one(),
1✔
946
                    attributes: BandSelection::first(),
1✔
947
                },
1✔
948
                &ctx,
1✔
949
            )
1✔
950
            .await
×
951
            .unwrap();
1✔
952

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

955
        assert_eq!(result.len(), 1);
1✔
956

957
        let first_result = result[0].as_ref().unwrap();
1✔
958

1✔
959
        assert!(!first_result.is_empty());
1✔
960

961
        let grid = match &first_result.grid_array {
1✔
962
            GridOrEmpty::Grid(g) => g,
1✔
963
            GridOrEmpty::Empty(_) => panic!(),
×
964
        };
965

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

1✔
968
        assert_eq!(
1✔
969
            res,
1✔
970
            [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✔
971
        );
1✔
972
    }
973

974
    #[tokio::test]
1✔
975
    async fn octave_inputs() {
1✔
976
        let no_data_value = 0;
1✔
977
        let no_data_value_option = Some(no_data_value);
1✔
978

1✔
979
        let tile_size_in_pixels = [3, 2].into();
1✔
980
        let tiling_specification = TilingSpecification {
1✔
981
            origin_coordinate: [0.0, 0.0].into(),
1✔
982
            tile_size_in_pixels,
1✔
983
        };
1✔
984

1✔
985
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
986

1✔
987
        let raster_a = make_raster(no_data_value_option);
1✔
988
        let raster_b = make_raster(no_data_value_option);
1✔
989
        let raster_c = make_raster(no_data_value_option);
1✔
990
        let raster_d = make_raster(no_data_value_option);
1✔
991
        let raster_e = make_raster(no_data_value_option);
1✔
992
        let raster_f = make_raster(no_data_value_option);
1✔
993
        let raster_g = make_raster(no_data_value_option);
1✔
994
        let raster_h = make_raster(no_data_value_option);
1✔
995

996
        let o = Expression {
1✔
997
            params: ExpressionParams {
1✔
998
                expression: "A+B+C+D+E+F+G+H".to_string(),
1✔
999
                output_type: RasterDataType::I8,
1✔
1000
                output_measurement: Some(Measurement::Unitless),
1✔
1001
                map_no_data: false,
1✔
1002
            },
1✔
1003
            sources: ExpressionSources {
1✔
1004
                a: raster_a,
1✔
1005
                b: Some(raster_b),
1✔
1006
                c: Some(raster_c),
1✔
1007
                d: Some(raster_d),
1✔
1008
                e: Some(raster_e),
1✔
1009
                f: Some(raster_f),
1✔
1010
                g: Some(raster_g),
1✔
1011
                h: Some(raster_h),
1✔
1012
            },
1✔
1013
        }
1✔
1014
        .boxed()
1✔
1015
        .initialize(WorkflowOperatorPath::initialize_root(), &ctx)
1✔
1016
        .await
×
1017
        .unwrap();
1✔
1018

1✔
1019
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1020

1✔
1021
        let ctx = MockQueryContext::new(1.into());
1✔
1022
        let result_stream = processor
1✔
1023
            .query(
1✔
1024
                RasterQueryRectangle {
1✔
1025
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1026
                        (0., 3.).into(),
1✔
1027
                        (2., 0.).into(),
1✔
1028
                    ),
1✔
1029
                    time_interval: Default::default(),
1✔
1030
                    spatial_resolution: SpatialResolution::one(),
1✔
1031
                    attributes: BandSelection::first(),
1✔
1032
                },
1✔
1033
                &ctx,
1✔
1034
            )
1✔
1035
            .await
×
1036
            .unwrap();
1✔
1037

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

1040
        assert_eq!(result.len(), 1);
1✔
1041

1042
        assert_eq!(
1✔
1043
            result[0].as_ref().unwrap().grid_array,
1✔
1044
            Grid2D::new([3, 2].into(), vec![8, 16, 24, 32, 40, 48],)
1✔
1045
                .unwrap()
1✔
1046
                .into()
1✔
1047
        );
1✔
1048
    }
1049

1050
    #[tokio::test]
1✔
1051
    async fn test_functions() {
1✔
1052
        let no_data_value = 0;
1✔
1053
        let tile_size_in_pixels = [3, 2].into();
1✔
1054
        let tiling_specification = TilingSpecification {
1✔
1055
            origin_coordinate: [0.0, 0.0].into(),
1✔
1056
            tile_size_in_pixels,
1✔
1057
        };
1✔
1058

1✔
1059
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1060

1✔
1061
        let raster_a = make_raster(Some(no_data_value));
1✔
1062

1063
        let o = Expression {
1✔
1064
            params: ExpressionParams {
1✔
1065
                expression: "min(A * pi(), 10)".to_string(),
1✔
1066
                output_type: RasterDataType::I8,
1✔
1067
                output_measurement: Some(Measurement::Unitless),
1✔
1068
                map_no_data: false,
1✔
1069
            },
1✔
1070
            sources: ExpressionSources {
1✔
1071
                a: raster_a,
1✔
1072
                b: None,
1✔
1073
                c: None,
1✔
1074
                d: None,
1✔
1075
                e: None,
1✔
1076
                f: None,
1✔
1077
                g: None,
1✔
1078
                h: None,
1✔
1079
            },
1✔
1080
        }
1✔
1081
        .boxed()
1✔
1082
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1083
        .await
×
1084
        .unwrap();
1✔
1085

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

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

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

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

1109
        assert_eq!(
1✔
1110
            result[0].as_ref().unwrap().grid_array,
1✔
1111
            Grid2D::new([3, 2].into(), vec![3, 6, 9, 10, 10, 10],)
1✔
1112
                .unwrap()
1✔
1113
                .into()
1✔
1114
        );
1✔
1115
    }
1116

1117
    fn make_raster(no_data_value: Option<i8>) -> Box<dyn RasterOperator> {
18✔
1118
        make_raster_with_cache_hint(no_data_value, CacheHint::no_cache())
18✔
1119
    }
18✔
1120

1121
    fn make_raster_with_cache_hint(
24✔
1122
        no_data_value: Option<i8>,
24✔
1123
        cache_hint: CacheHint,
24✔
1124
    ) -> Box<dyn RasterOperator> {
24✔
1125
        let raster = Grid2D::<i8>::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6]).unwrap();
24✔
1126

1127
        let real_raster = if let Some(no_data_value) = no_data_value {
24✔
1128
            MaskedGrid2D::from(raster)
21✔
1129
                .map_elements(|e: Option<i8>| e.filter(|v| *v != no_data_value))
126✔
1130
                .into()
21✔
1131
        } else {
1132
            GridOrEmpty::from(raster)
3✔
1133
        };
1134

1135
        let raster_tile = RasterTile2D::new_with_tile_info(
24✔
1136
            TimeInterval::default(),
24✔
1137
            TileInformation {
24✔
1138
                global_tile_position: [-1, 0].into(),
24✔
1139
                tile_size_in_pixels: [3, 2].into(),
24✔
1140
                global_geo_transform: TestDefault::test_default(),
24✔
1141
            },
24✔
1142
            0,
24✔
1143
            real_raster,
24✔
1144
            cache_hint,
24✔
1145
        );
24✔
1146

24✔
1147
        MockRasterSource {
24✔
1148
            params: MockRasterSourceParams {
24✔
1149
                data: vec![raster_tile],
24✔
1150
                result_descriptor: RasterResultDescriptor {
24✔
1151
                    data_type: RasterDataType::I8,
24✔
1152
                    spatial_reference: SpatialReference::epsg_4326().into(),
24✔
1153
                    time: None,
24✔
1154
                    bbox: None,
24✔
1155
                    resolution: None,
24✔
1156
                    bands: RasterBandDescriptors::new_single_band(),
24✔
1157
                },
24✔
1158
            },
24✔
1159
        }
24✔
1160
        .boxed()
24✔
1161
    }
24✔
1162

1163
    #[tokio::test]
1✔
1164
    async fn it_attaches_cache_hint_1() {
1✔
1165
        let no_data_value = 0;
1✔
1166
        let tile_size_in_pixels = [3, 2].into();
1✔
1167
        let tiling_specification = TilingSpecification {
1✔
1168
            origin_coordinate: [0.0, 0.0].into(),
1✔
1169
            tile_size_in_pixels,
1✔
1170
        };
1✔
1171

1✔
1172
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1173

1✔
1174
        let cache_hint = CacheHint::seconds(1234);
1✔
1175
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint);
1✔
1176

1177
        let o = Expression {
1✔
1178
            params: ExpressionParams {
1✔
1179
                expression: "min(A * pi(), 10)".to_string(),
1✔
1180
                output_type: RasterDataType::I8,
1✔
1181
                output_measurement: Some(Measurement::Unitless),
1✔
1182
                map_no_data: false,
1✔
1183
            },
1✔
1184
            sources: ExpressionSources {
1✔
1185
                a: raster_a,
1✔
1186
                b: None,
1✔
1187
                c: None,
1✔
1188
                d: None,
1✔
1189
                e: None,
1✔
1190
                f: None,
1✔
1191
                g: None,
1✔
1192
                h: None,
1✔
1193
            },
1✔
1194
        }
1✔
1195
        .boxed()
1✔
1196
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1197
        .await
×
1198
        .unwrap();
1✔
1199

1✔
1200
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1201

1✔
1202
        let ctx = MockQueryContext::new(1.into());
1✔
1203
        let result_stream = processor
1✔
1204
            .query(
1✔
1205
                RasterQueryRectangle {
1✔
1206
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1207
                        (0., 3.).into(),
1✔
1208
                        (2., 0.).into(),
1✔
1209
                    ),
1✔
1210
                    time_interval: Default::default(),
1✔
1211
                    spatial_resolution: SpatialResolution::one(),
1✔
1212
                    attributes: BandSelection::first(),
1✔
1213
                },
1✔
1214
                &ctx,
1✔
1215
            )
1✔
1216
            .await
×
1217
            .unwrap();
1✔
1218

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

1221
        assert_eq!(result.len(), 1);
1✔
1222

1223
        assert!(
1✔
1224
            result[0].as_ref().unwrap().cache_hint.total_ttl_seconds() > CacheTtlSeconds::new(0)
1✔
1225
                && result[0].as_ref().unwrap().cache_hint.total_ttl_seconds()
1✔
1226
                    <= cache_hint.total_ttl_seconds()
1✔
1227
        );
1228
    }
1229

1230
    #[tokio::test]
1✔
1231
    async fn it_attaches_cache_hint_2() {
1✔
1232
        let no_data_value = 0;
1✔
1233
        let tile_size_in_pixels = [3, 2].into();
1✔
1234
        let tiling_specification = TilingSpecification {
1✔
1235
            origin_coordinate: [0.0, 0.0].into(),
1✔
1236
            tile_size_in_pixels,
1✔
1237
        };
1✔
1238

1✔
1239
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1240

1✔
1241
        let cache_hint_a = CacheHint::seconds(1234);
1✔
1242
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint_a);
1✔
1243

1✔
1244
        let cache_hint_b = CacheHint::seconds(4567);
1✔
1245
        let raster_b = make_raster_with_cache_hint(Some(no_data_value), cache_hint_b);
1✔
1246

1247
        let o = Expression {
1✔
1248
            params: ExpressionParams {
1✔
1249
                expression: "A + B".to_string(),
1✔
1250
                output_type: RasterDataType::I8,
1✔
1251
                output_measurement: Some(Measurement::Unitless),
1✔
1252
                map_no_data: false,
1✔
1253
            },
1✔
1254
            sources: ExpressionSources {
1✔
1255
                a: raster_a,
1✔
1256
                b: Some(raster_b),
1✔
1257
                c: None,
1✔
1258
                d: None,
1✔
1259
                e: None,
1✔
1260
                f: None,
1✔
1261
                g: None,
1✔
1262
                h: None,
1✔
1263
            },
1✔
1264
        }
1✔
1265
        .boxed()
1✔
1266
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1267
        .await
×
1268
        .unwrap();
1✔
1269

1✔
1270
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1271

1✔
1272
        let ctx = MockQueryContext::new(1.into());
1✔
1273
        let result_stream = processor
1✔
1274
            .query(
1✔
1275
                RasterQueryRectangle {
1✔
1276
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1277
                        (0., 3.).into(),
1✔
1278
                        (2., 0.).into(),
1✔
1279
                    ),
1✔
1280
                    time_interval: Default::default(),
1✔
1281
                    spatial_resolution: SpatialResolution::one(),
1✔
1282
                    attributes: BandSelection::first(),
1✔
1283
                },
1✔
1284
                &ctx,
1✔
1285
            )
1✔
1286
            .await
×
1287
            .unwrap();
1✔
1288

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

1291
        assert_eq!(result.len(), 1);
1✔
1292

1293
        assert_eq!(
1✔
1294
            result[0].as_ref().unwrap().cache_hint.expires(),
1✔
1295
            cache_hint_a.merged(&cache_hint_b).expires()
1✔
1296
        );
1✔
1297
    }
1298

1299
    #[tokio::test]
1✔
1300
    async fn it_attaches_cache_hint_3() {
1✔
1301
        let no_data_value = 0;
1✔
1302
        let tile_size_in_pixels = [3, 2].into();
1✔
1303
        let tiling_specification = TilingSpecification {
1✔
1304
            origin_coordinate: [0.0, 0.0].into(),
1✔
1305
            tile_size_in_pixels,
1✔
1306
        };
1✔
1307

1✔
1308
        let ectx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1309

1✔
1310
        let cache_hint_a = CacheHint::seconds(1234);
1✔
1311
        let raster_a = make_raster_with_cache_hint(Some(no_data_value), cache_hint_a);
1✔
1312

1✔
1313
        let cache_hint_b = CacheHint::seconds(4567);
1✔
1314
        let raster_b = make_raster_with_cache_hint(Some(no_data_value), cache_hint_b);
1✔
1315

1✔
1316
        let cache_hint_c = CacheHint::seconds(7891);
1✔
1317
        let raster_c = make_raster_with_cache_hint(Some(no_data_value), cache_hint_c);
1✔
1318

1319
        let o = Expression {
1✔
1320
            params: ExpressionParams {
1✔
1321
                expression: "A + B".to_string(),
1✔
1322
                output_type: RasterDataType::I8,
1✔
1323
                output_measurement: Some(Measurement::Unitless),
1✔
1324
                map_no_data: false,
1✔
1325
            },
1✔
1326
            sources: ExpressionSources {
1✔
1327
                a: raster_a,
1✔
1328
                b: Some(raster_b),
1✔
1329
                c: Some(raster_c),
1✔
1330
                d: None,
1✔
1331
                e: None,
1✔
1332
                f: None,
1✔
1333
                g: None,
1✔
1334
                h: None,
1✔
1335
            },
1✔
1336
        }
1✔
1337
        .boxed()
1✔
1338
        .initialize(WorkflowOperatorPath::initialize_root(), &ectx)
1✔
1339
        .await
×
1340
        .unwrap();
1✔
1341

1✔
1342
        let processor = o.query_processor().unwrap().get_i8().unwrap();
1✔
1343

1✔
1344
        let ctx = MockQueryContext::new(1.into());
1✔
1345
        let result_stream = processor
1✔
1346
            .query(
1✔
1347
                RasterQueryRectangle {
1✔
1348
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1349
                        (0., 3.).into(),
1✔
1350
                        (2., 0.).into(),
1✔
1351
                    ),
1✔
1352
                    time_interval: Default::default(),
1✔
1353
                    spatial_resolution: SpatialResolution::one(),
1✔
1354
                    attributes: BandSelection::first(),
1✔
1355
                },
1✔
1356
                &ctx,
1✔
1357
            )
1✔
1358
            .await
×
1359
            .unwrap();
1✔
1360

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

1363
        assert_eq!(result.len(), 1);
1✔
1364

1365
        assert_eq!(
1✔
1366
            result[0].as_ref().unwrap().cache_hint.expires(),
1✔
1367
            cache_hint_a
1✔
1368
                .merged(&cache_hint_b)
1✔
1369
                .merged(&cache_hint_c)
1✔
1370
                .expires()
1✔
1371
        );
1✔
1372
    }
1373
}
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