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

geo-engine / geoengine / 7006148507

27 Nov 2023 02:07PM UTC coverage: 89.647% (+0.1%) from 89.498%
7006148507

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%)

19 existing lines in 10 files now uncovered.

113015 of 126066 relevant lines covered (89.65%)

59901.75 hits per line

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

93.16
/operators/src/processing/meteosat/temperature.rs
1
use std::sync::Arc;
2

3
use crate::engine::{
4
    CanonicOperatorName, ExecutionContext, InitializedRasterOperator, InitializedSources, Operator,
5
    OperatorName, QueryContext, QueryProcessor, RasterBandDescriptor, RasterBandDescriptors,
6
    RasterOperator, RasterQueryProcessor, RasterResultDescriptor, SingleRasterSource,
7
    TypedRasterQueryProcessor, WorkflowOperatorPath,
8
};
9
use crate::util::Result;
10
use async_trait::async_trait;
11
use rayon::ThreadPool;
12

13
use snafu::ensure;
14
use TypedRasterQueryProcessor::F32 as QueryProcessorOut;
15

16
use crate::error::Error;
17
use futures::stream::BoxStream;
18
use futures::{StreamExt, TryStreamExt};
19
use geoengine_datatypes::primitives::{
20
    BandSelection, ClassificationMeasurement, ContinuousMeasurement, Measurement,
21
    RasterQueryRectangle, SpatialPartition2D,
22
};
23
use geoengine_datatypes::raster::{
24
    MapElementsParallel, Pixel, RasterDataType, RasterPropertiesKey, RasterTile2D,
25
};
26
use serde::{Deserialize, Serialize};
27

28
// Output type is always f32
29
type PixelOut = f32;
30
use crate::processing::meteosat::satellite::{Channel, Satellite};
31
use crate::processing::meteosat::{
32
    new_channel_key, new_offset_key, new_satellite_key, new_slope_key,
33
};
34
use RasterDataType::F32 as RasterOut;
35

36
/// Parameters for the `Temperature` operator.
37
/// * `force_satellite` forces the use of the satellite with the given name.
38
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
14✔
39
#[serde(rename_all = "camelCase")]
40
pub struct TemperatureParams {
41
    force_satellite: Option<u8>,
42
}
43

44
/// The temperature operator approximates BT from
45
/// the raw MSG rasters.
46
pub type Temperature = Operator<TemperatureParams, SingleRasterSource>;
47

48
impl OperatorName for Temperature {
49
    const TYPE_NAME: &'static str = "Temperature";
50
}
51

52
pub struct InitializedTemperature {
53
    name: CanonicOperatorName,
54
    result_descriptor: RasterResultDescriptor,
55
    source: Box<dyn InitializedRasterOperator>,
56
    params: TemperatureParams,
57
}
58

59
#[typetag::serde]
×
60
#[async_trait]
61
impl RasterOperator for Temperature {
62
    async fn _initialize(
14✔
63
        self: Box<Self>,
14✔
64
        path: WorkflowOperatorPath,
14✔
65
        context: &dyn ExecutionContext,
14✔
66
    ) -> Result<Box<dyn InitializedRasterOperator>> {
14✔
67
        let name = CanonicOperatorName::from(&self);
14✔
68

69
        let initialized_sources = self.sources.initialize_sources(path, context).await?;
14✔
70
        let input = initialized_sources.raster;
14✔
71

14✔
72
        let in_desc = input.result_descriptor();
14✔
73

14✔
74
        // TODO: implement multi-band functionality and remove this check
14✔
75
        ensure!(
14✔
76
            in_desc.bands.len() == 1,
14✔
NEW
77
            crate::error::OperatorDoesNotSupportMultiBandsSourcesYet {
×
NEW
78
                operator: Temperature::TYPE_NAME
×
NEW
79
            }
×
80
        );
81

82
        match &in_desc.bands[0].measurement {
14✔
83
            Measurement::Continuous(ContinuousMeasurement {
84
                measurement: m,
12✔
85
                unit: _,
12✔
86
            }) if m != "raw" => {
12✔
87
                return Err(Error::InvalidMeasurement {
1✔
88
                    expected: "raw".into(),
1✔
89
                    found: m.clone(),
1✔
90
                })
1✔
91
            }
92
            Measurement::Classification(ClassificationMeasurement {
93
                measurement: m,
1✔
94
                classes: _,
1✔
95
            }) => {
1✔
96
                return Err(Error::InvalidMeasurement {
1✔
97
                    expected: "raw".into(),
1✔
98
                    found: m.clone(),
1✔
99
                })
1✔
100
            }
101
            Measurement::Unitless => {
102
                return Err(Error::InvalidMeasurement {
1✔
103
                    expected: "raw".into(),
1✔
104
                    found: "unitless".into(),
1✔
105
                })
1✔
106
            }
107
            // OK Case
108
            Measurement::Continuous(ContinuousMeasurement {
109
                measurement: _,
110
                unit: _,
111
            }) => {}
11✔
112
        }
11✔
113

11✔
114
        let out_desc = RasterResultDescriptor {
11✔
115
            spatial_reference: in_desc.spatial_reference,
11✔
116
            data_type: RasterOut,
11✔
117
            time: in_desc.time,
11✔
118
            bbox: in_desc.bbox,
11✔
119
            resolution: in_desc.resolution,
11✔
120
            bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
11✔
121
                in_desc.bands[0].name.clone(),
11✔
122
                Measurement::Continuous(ContinuousMeasurement {
11✔
123
                    measurement: "temperature".into(),
11✔
124
                    unit: Some("k".into()),
11✔
125
                }),
11✔
126
            )])
11✔
127
            .unwrap(),
11✔
128
        };
11✔
129

11✔
130
        let initialized_operator = InitializedTemperature {
11✔
131
            name,
11✔
132
            result_descriptor: out_desc,
11✔
133
            source: input,
11✔
134
            params: self.params,
11✔
135
        };
11✔
136

11✔
137
        Ok(initialized_operator.boxed())
11✔
138
    }
28✔
139

140
    span_fn!(Temperature);
×
141
}
142

143
impl InitializedRasterOperator for InitializedTemperature {
144
    fn result_descriptor(&self) -> &RasterResultDescriptor {
×
145
        &self.result_descriptor
×
146
    }
×
147

148
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor, Error> {
11✔
149
        let q = self.source.query_processor()?;
11✔
150

151
        Ok(match q {
11✔
152
            TypedRasterQueryProcessor::U8(p) => {
10✔
153
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
10✔
154
            }
155
            TypedRasterQueryProcessor::U16(p) => {
1✔
156
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
1✔
157
            }
158
            TypedRasterQueryProcessor::U32(p) => {
×
159
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
160
            }
161
            TypedRasterQueryProcessor::U64(p) => {
×
162
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
163
            }
164
            TypedRasterQueryProcessor::I8(p) => {
×
165
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
166
            }
167
            TypedRasterQueryProcessor::I16(p) => {
×
168
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
169
            }
170
            TypedRasterQueryProcessor::I32(p) => {
×
171
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
172
            }
173
            TypedRasterQueryProcessor::I64(p) => {
×
174
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
175
            }
176
            TypedRasterQueryProcessor::F32(p) => {
×
177
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
178
            }
179
            TypedRasterQueryProcessor::F64(p) => {
×
180
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
181
            }
182
        })
183
    }
11✔
184

185
    fn canonic_name(&self) -> CanonicOperatorName {
×
186
        self.name.clone()
×
187
    }
×
188
}
189

190
struct TemperatureProcessor<Q, P>
191
where
192
    Q: RasterQueryProcessor<RasterType = P>,
193
{
194
    source: Q,
195
    params: TemperatureParams,
196
    satellite_key: RasterPropertiesKey,
197
    channel_key: RasterPropertiesKey,
198
    offset_key: RasterPropertiesKey,
199
    slope_key: RasterPropertiesKey,
200
}
201

202
impl<Q, P> TemperatureProcessor<Q, P>
203
where
204
    Q: RasterQueryProcessor<RasterType = P>,
205
    P: Pixel,
206
{
207
    pub fn new(source: Q, params: TemperatureParams) -> Self {
11✔
208
        Self {
11✔
209
            source,
11✔
210
            params,
11✔
211
            satellite_key: new_satellite_key(),
11✔
212
            channel_key: new_channel_key(),
11✔
213
            offset_key: new_offset_key(),
11✔
214
            slope_key: new_slope_key(),
11✔
215
        }
11✔
216
    }
11✔
217

218
    fn satellite(&self, tile: &RasterTile2D<P>) -> Result<&'static Satellite> {
11✔
219
        let id = match self.params.force_satellite {
11✔
220
            Some(id) => id,
2✔
221
            _ => tile.properties.number_property(&self.satellite_key)?,
9✔
222
        };
223
        Satellite::satellite_by_msg_id(id)
10✔
224
    }
11✔
225

226
    fn channel<'a>(&self, tile: &RasterTile2D<P>, satellite: &'a Satellite) -> Result<&'a Channel> {
8✔
227
        let channel_id = tile
8✔
228
            .properties
8✔
229
            .number_property::<usize>(&self.channel_key)?
8✔
230
            - 1;
231
        if (3..=10).contains(&channel_id) {
7✔
232
            satellite.channel(channel_id)
6✔
233
        } else {
234
            Err(Error::InvalidChannel {
1✔
235
                channel: channel_id,
1✔
236
            })
1✔
237
        }
238
    }
8✔
239

240
    async fn process_tile_async(
11✔
241
        &self,
11✔
242
        tile: RasterTile2D<P>,
11✔
243
        pool: Arc<ThreadPool>,
11✔
244
    ) -> Result<RasterTile2D<PixelOut>> {
11✔
245
        let satellite = self.satellite(&tile)?;
11✔
246
        let channel = self.channel(&tile, satellite)?;
8✔
247
        let offset = tile.properties.number_property::<f64>(&self.offset_key)?;
6✔
248
        let slope = tile.properties.number_property::<f64>(&self.slope_key)?;
5✔
249

250
        let temp_tile = crate::util::spawn_blocking_with_thread_pool(pool.clone(), move || {
4✔
251
            let lut = create_lookup_table(channel, offset, slope, &pool);
4✔
252

4✔
253
            let map_fn = move |pixel_option: Option<P>| {
19✔
254
                pixel_option.and_then(|p| {
19✔
255
                    let lut_idx: u64 = p.as_();
15✔
256
                    lut.get(lut_idx as usize).copied()
15✔
257
                })
19✔
258
            };
19✔
259

260
            tile.map_elements_parallel(map_fn)
4✔
261
        })
4✔
262
        .await?;
4✔
263

264
        Ok(temp_tile)
4✔
265
    }
11✔
266
}
267

268
fn create_lookup_table(channel: &Channel, offset: f64, slope: f64, _pool: &ThreadPool) -> Vec<f32> {
4✔
269
    // this should propably be done with SIMD not a threadpool
4✔
270
    (0..1024)
4✔
271
        .map(|i| {
4,096✔
272
            let radiance = offset + f64::from(i) * slope;
4,096✔
273
            channel.calculate_temperature_from_radiance(radiance) as f32
4,096✔
274
        })
4,096✔
275
        .collect::<Vec<f32>>()
4✔
276
}
4✔
277

278
#[async_trait]
279
impl<Q, P> QueryProcessor for TemperatureProcessor<Q, P>
280
where
281
    Q: QueryProcessor<
282
        Output = RasterTile2D<P>,
283
        SpatialBounds = SpatialPartition2D,
284
        Selection = BandSelection,
285
    >,
286
    P: Pixel,
287
{
288
    type Output = RasterTile2D<PixelOut>;
289
    type SpatialBounds = SpatialPartition2D;
290
    type Selection = BandSelection;
291

292
    async fn _query<'a>(
11✔
293
        &'a self,
11✔
294
        query: RasterQueryRectangle,
11✔
295
        ctx: &'a dyn QueryContext,
11✔
296
    ) -> Result<BoxStream<'a, Result<Self::Output>>> {
11✔
297
        let src = self.source.query(query, ctx).await?;
11✔
298
        let rs = src.and_then(move |tile| self.process_tile_async(tile, ctx.thread_pool().clone()));
11✔
299
        Ok(rs.boxed())
11✔
300
    }
22✔
301
}
302

303
#[cfg(test)]
304
mod tests {
305
    use crate::engine::{MockExecutionContext, RasterOperator, SingleRasterSource};
306
    use crate::processing::meteosat::temperature::{Temperature, TemperatureParams};
307
    use crate::processing::meteosat::test_util;
308
    use geoengine_datatypes::primitives::{
309
        ClassificationMeasurement, ContinuousMeasurement, Measurement,
310
    };
311
    use geoengine_datatypes::raster::{EmptyGrid2D, Grid2D, MaskedGrid2D, TilingSpecification};
312
    use std::collections::HashMap;
313

314
    // #[tokio::test]
315
    // async fn test_msg_raster() {
316
    //     let mut ctx = MockExecutionContext::test_default();
317
    //     let src = test_util::_create_gdal_src(&mut ctx);
318
    //
319
    //     let result = test_util::process(
320
    //         move || {
321
    //             RasterOperator::boxed(Temperature {
322
    //                 params: TemperatureParams::default(),
323
    //                 sources: SingleRasterSource {
324
    //                     raster: src.boxed(),
325
    //                 },
326
    //             })
327
    //         },
328
    //         test_util::_create_gdal_query(),
329
    //         &ctx,
330
    //     )
331
    //     .await;
332
    //     assert!(result.as_ref().is_ok());
333
    // }
334

335
    #[tokio::test]
1✔
336
    async fn test_empty_ok() {
1✔
337
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
338
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
339

340
        let res = test_util::process(
1✔
341
            || {
1✔
342
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
343
                let src = test_util::create_mock_source::<u8>(
1✔
344
                    props,
1✔
345
                    Some(EmptyGrid2D::new([3, 2].into()).into()),
1✔
346
                    None,
1✔
347
                );
1✔
348

1✔
349
                RasterOperator::boxed(Temperature {
1✔
350
                    params: TemperatureParams::default(),
1✔
351
                    sources: SingleRasterSource {
1✔
352
                        raster: src.boxed(),
1✔
353
                    },
1✔
354
                })
1✔
355
            },
1✔
356
            test_util::create_mock_query(),
1✔
357
            &ctx,
1✔
358
        )
1✔
359
        .await
1✔
360
        .unwrap();
1✔
361

1✔
362
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
363
            &res.grid_array,
1✔
364
            &EmptyGrid2D::new([3, 2].into()).into()
1✔
365
        ));
1✔
366
    }
367

368
    #[tokio::test]
1✔
369
    async fn test_ok() {
1✔
370
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
371
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
372

373
        let res = test_util::process(
1✔
374
            || {
1✔
375
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
376
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
377

1✔
378
                RasterOperator::boxed(Temperature {
1✔
379
                    params: TemperatureParams::default(),
1✔
380
                    sources: SingleRasterSource {
1✔
381
                        raster: src.boxed(),
1✔
382
                    },
1✔
383
                })
1✔
384
            },
1✔
385
            test_util::create_mock_query(),
1✔
386
            &ctx,
1✔
387
        )
1✔
388
        .await
1✔
389
        .unwrap();
1✔
390

1✔
391
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
392
            &res.grid_array,
1✔
393
            &MaskedGrid2D::new(
1✔
394
                Grid2D::new(
1✔
395
                    [3, 2].into(),
1✔
396
                    vec![300.341_43, 318.617_65, 330.365_14, 339.233_64, 346.443_94, 0.,],
1✔
397
                )
1✔
398
                .unwrap(),
1✔
399
                Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false,],).unwrap(),
1✔
400
            )
1✔
401
            .unwrap()
1✔
402
            .into()
1✔
403
        ));
1✔
404

405
        // TODO: add assert to check mask
406
    }
407

408
    #[tokio::test]
1✔
409
    async fn test_ok_force_satellite() {
1✔
410
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
411
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
412

413
        let res = test_util::process(
1✔
414
            || {
1✔
415
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
416
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
417

1✔
418
                RasterOperator::boxed(Temperature {
1✔
419
                    params: TemperatureParams {
1✔
420
                        force_satellite: Some(4),
1✔
421
                    },
1✔
422
                    sources: SingleRasterSource {
1✔
423
                        raster: src.boxed(),
1✔
424
                    },
1✔
425
                })
1✔
426
            },
1✔
427
            test_util::create_mock_query(),
1✔
428
            &ctx,
1✔
429
        )
1✔
430
        .await
1✔
431
        .unwrap();
1✔
432

1✔
433
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
434
            &res.grid_array,
1✔
435
            &MaskedGrid2D::new(
1✔
436
                Grid2D::new(
1✔
437
                    [3, 2].into(),
1✔
438
                    vec![300.9428, 319.250_15, 331.019_04, 339.9044, 347.128_78, 0.],
1✔
439
                )
1✔
440
                .unwrap(),
1✔
441
                Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false,],).unwrap(),
1✔
442
            )
1✔
443
            .unwrap()
1✔
444
            .into()
1✔
445
        ));
1✔
446
    }
447

448
    #[tokio::test]
1✔
449
    async fn test_ok_illegal_input_to_masked() {
1✔
450
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
451
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
452

453
        let res = test_util::process(
1✔
454
            || {
1✔
455
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
456
                let src = test_util::create_mock_source::<u16>(
1✔
457
                    props,
1✔
458
                    Some(
1✔
459
                        MaskedGrid2D::new(
1✔
460
                            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 1024, 0]).unwrap(),
1✔
461
                            Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false])
1✔
462
                                .unwrap(),
1✔
463
                        )
1✔
464
                        .unwrap()
1✔
465
                        .into(),
1✔
466
                    ),
1✔
467
                    None,
1✔
468
                );
1✔
469

1✔
470
                RasterOperator::boxed(Temperature {
1✔
471
                    params: TemperatureParams::default(),
1✔
472
                    sources: SingleRasterSource {
1✔
473
                        raster: src.boxed(),
1✔
474
                    },
1✔
475
                })
1✔
476
            },
1✔
477
            test_util::create_mock_query(),
1✔
478
            &ctx,
1✔
479
        )
1✔
480
        .await;
1✔
481
        assert!(res.is_ok());
1✔
482
        let res = res.unwrap();
1✔
483
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
484
            &res.grid_array,
1✔
485
            &MaskedGrid2D::new(
1✔
486
                Grid2D::new(
1✔
487
                    [3, 2].into(),
1✔
488
                    vec![300.341_43, 318.617_65, 330.365_14, 339.233_64, 0., 0.],
1✔
489
                )
1✔
490
                .unwrap(),
1✔
491
                Grid2D::new([3, 2].into(), vec![true, true, true, true, false, false,],).unwrap(),
1✔
492
            )
1✔
493
            .unwrap()
1✔
494
            .into()
1✔
495
        ));
1✔
496
    }
497

498
    #[tokio::test]
1✔
499
    async fn test_invalid_force_satellite() {
1✔
500
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
501
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
502

503
        let res = test_util::process(
1✔
504
            || {
1✔
505
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
506
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
507

1✔
508
                RasterOperator::boxed(Temperature {
1✔
509
                    params: TemperatureParams {
1✔
510
                        force_satellite: Some(13),
1✔
511
                    },
1✔
512
                    sources: SingleRasterSource {
1✔
513
                        raster: src.boxed(),
1✔
514
                    },
1✔
515
                })
1✔
516
            },
1✔
517
            test_util::create_mock_query(),
1✔
518
            &ctx,
1✔
519
        )
1✔
520
        .await;
×
521
        assert!(res.is_err());
1✔
522
    }
523

524
    #[tokio::test]
1✔
525
    async fn test_missing_satellite() {
1✔
526
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
527
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
528

529
        let res = test_util::process(
1✔
530
            || {
1✔
531
                let props = test_util::create_properties(Some(4), None, Some(0.0), Some(1.0));
1✔
532
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
533

1✔
534
                RasterOperator::boxed(Temperature {
1✔
535
                    params: TemperatureParams::default(),
1✔
536
                    sources: SingleRasterSource {
1✔
537
                        raster: src.boxed(),
1✔
538
                    },
1✔
539
                })
1✔
540
            },
1✔
541
            test_util::create_mock_query(),
1✔
542
            &ctx,
1✔
543
        )
1✔
544
        .await;
×
545
        assert!(res.is_err());
1✔
546
    }
547

548
    #[tokio::test]
1✔
549
    async fn test_invalid_satellite() {
1✔
550
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
551
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
552

553
        let res = test_util::process(
1✔
554
            || {
1✔
555
                let props = test_util::create_properties(Some(4), Some(42), Some(0.0), Some(1.0));
1✔
556
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
557

1✔
558
                RasterOperator::boxed(Temperature {
1✔
559
                    params: TemperatureParams::default(),
1✔
560
                    sources: SingleRasterSource {
1✔
561
                        raster: src.boxed(),
1✔
562
                    },
1✔
563
                })
1✔
564
            },
1✔
565
            test_util::create_mock_query(),
1✔
566
            &ctx,
1✔
567
        )
1✔
568
        .await;
×
569
        assert!(res.is_err());
1✔
570
    }
571

572
    #[tokio::test]
1✔
573
    async fn test_missing_channel() {
1✔
574
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
575
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
576

577
        let res = test_util::process(
1✔
578
            || {
1✔
579
                let props = test_util::create_properties(None, Some(1), Some(0.0), Some(1.0));
1✔
580
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
581

1✔
582
                RasterOperator::boxed(Temperature {
1✔
583
                    params: TemperatureParams::default(),
1✔
584
                    sources: SingleRasterSource {
1✔
585
                        raster: src.boxed(),
1✔
586
                    },
1✔
587
                })
1✔
588
            },
1✔
589
            test_util::create_mock_query(),
1✔
590
            &ctx,
1✔
591
        )
1✔
592
        .await;
×
593
        assert!(res.is_err());
1✔
594
    }
595

596
    #[tokio::test]
1✔
597
    async fn test_invalid_channel() {
1✔
598
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
599
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
600

601
        let res = test_util::process(
1✔
602
            || {
1✔
603
                let props = test_util::create_properties(Some(1), Some(1), Some(0.0), Some(1.0));
1✔
604
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
605

1✔
606
                RasterOperator::boxed(Temperature {
1✔
607
                    params: TemperatureParams::default(),
1✔
608
                    sources: SingleRasterSource {
1✔
609
                        raster: src.boxed(),
1✔
610
                    },
1✔
611
                })
1✔
612
            },
1✔
613
            test_util::create_mock_query(),
1✔
614
            &ctx,
1✔
615
        )
1✔
616
        .await;
×
617
        assert!(res.is_err());
1✔
618
    }
619

620
    #[tokio::test]
1✔
621
    async fn test_missing_slope() {
1✔
622
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
623
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
624

625
        let res = test_util::process(
1✔
626
            || {
1✔
627
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), None);
1✔
628
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
629

1✔
630
                RasterOperator::boxed(Temperature {
1✔
631
                    params: TemperatureParams::default(),
1✔
632
                    sources: SingleRasterSource {
1✔
633
                        raster: src.boxed(),
1✔
634
                    },
1✔
635
                })
1✔
636
            },
1✔
637
            test_util::create_mock_query(),
1✔
638
            &ctx,
1✔
639
        )
1✔
640
        .await;
×
641
        assert!(res.is_err());
1✔
642
    }
643

644
    #[tokio::test]
1✔
645
    async fn test_missing_offset() {
1✔
646
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
647
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
648

649
        let res = test_util::process(
1✔
650
            || {
1✔
651
                let props = test_util::create_properties(Some(4), Some(1), None, Some(1.0));
1✔
652
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
653

1✔
654
                RasterOperator::boxed(Temperature {
1✔
655
                    params: TemperatureParams::default(),
1✔
656
                    sources: SingleRasterSource {
1✔
657
                        raster: src.boxed(),
1✔
658
                    },
1✔
659
                })
1✔
660
            },
1✔
661
            test_util::create_mock_query(),
1✔
662
            &ctx,
1✔
663
        )
1✔
664
        .await;
×
665
        assert!(res.is_err());
1✔
666
    }
667

668
    #[tokio::test]
1✔
669
    async fn test_invalid_measurement_unitless() {
1✔
670
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
671
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
672

673
        let res = test_util::process(
1✔
674
            || {
1✔
675
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
676
                let src =
1✔
677
                    test_util::create_mock_source::<u8>(props, None, Some(Measurement::Unitless));
1✔
678

1✔
679
                RasterOperator::boxed(Temperature {
1✔
680
                    params: TemperatureParams::default(),
1✔
681
                    sources: SingleRasterSource {
1✔
682
                        raster: src.boxed(),
1✔
683
                    },
1✔
684
                })
1✔
685
            },
1✔
686
            test_util::create_mock_query(),
1✔
687
            &ctx,
1✔
688
        )
1✔
689
        .await;
×
690
        assert!(res.is_err());
1✔
691
    }
692

693
    #[tokio::test]
1✔
694
    async fn test_invalid_measurement_continuous() {
1✔
695
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
696
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
697

698
        let res = test_util::process(
1✔
699
            || {
1✔
700
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
701
                let src = test_util::create_mock_source::<u8>(
1✔
702
                    props,
1✔
703
                    None,
1✔
704
                    Some(Measurement::Continuous(ContinuousMeasurement {
1✔
705
                        measurement: "invalid".into(),
1✔
706
                        unit: None,
1✔
707
                    })),
1✔
708
                );
1✔
709

1✔
710
                RasterOperator::boxed(Temperature {
1✔
711
                    params: TemperatureParams::default(),
1✔
712
                    sources: SingleRasterSource {
1✔
713
                        raster: src.boxed(),
1✔
714
                    },
1✔
715
                })
1✔
716
            },
1✔
717
            test_util::create_mock_query(),
1✔
718
            &ctx,
1✔
719
        )
1✔
720
        .await;
×
721

722
        assert!(res.is_err());
1✔
723
    }
724

725
    #[tokio::test]
1✔
726
    async fn test_invalid_measurement_classification() {
1✔
727
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
728

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

731
        let res = test_util::process(
1✔
732
            || {
1✔
733
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
734
                let src = test_util::create_mock_source::<u8>(
1✔
735
                    props,
1✔
736
                    None,
1✔
737
                    Some(Measurement::Classification(ClassificationMeasurement {
1✔
738
                        measurement: "invalid".into(),
1✔
739
                        classes: HashMap::new(),
1✔
740
                    })),
1✔
741
                );
1✔
742

1✔
743
                RasterOperator::boxed(Temperature {
1✔
744
                    params: TemperatureParams::default(),
1✔
745
                    sources: SingleRasterSource {
1✔
746
                        raster: src.boxed(),
1✔
747
                    },
1✔
748
                })
1✔
749
            },
1✔
750
            test_util::create_mock_query(),
1✔
751
            &ctx,
1✔
752
        )
1✔
753
        .await;
×
754
        assert!(res.is_err());
1✔
755
    }
756
}
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