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

geo-engine / geoengine / 7276350647

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

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

11 existing lines in 3 files now uncovered.

115920 of 129090 relevant lines covered (89.8%)

58689.64 hits per line

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

91.95
/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✔
77
            crate::error::OperatorDoesNotSupportMultiBandsSourcesYet {
×
78
                operator: Temperature::TYPE_NAME
×
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) => QueryProcessorOut(Box::new(
10✔
153
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
10✔
154
            )),
10✔
155
            TypedRasterQueryProcessor::U16(p) => QueryProcessorOut(Box::new(
1✔
156
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
1✔
157
            )),
1✔
NEW
158
            TypedRasterQueryProcessor::U32(p) => QueryProcessorOut(Box::new(
×
NEW
159
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
NEW
160
            )),
×
NEW
161
            TypedRasterQueryProcessor::U64(p) => QueryProcessorOut(Box::new(
×
NEW
162
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
NEW
163
            )),
×
NEW
164
            TypedRasterQueryProcessor::I8(p) => QueryProcessorOut(Box::new(
×
NEW
165
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
NEW
166
            )),
×
NEW
167
            TypedRasterQueryProcessor::I16(p) => QueryProcessorOut(Box::new(
×
NEW
168
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
NEW
169
            )),
×
NEW
170
            TypedRasterQueryProcessor::I32(p) => QueryProcessorOut(Box::new(
×
NEW
171
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
NEW
172
            )),
×
NEW
173
            TypedRasterQueryProcessor::I64(p) => QueryProcessorOut(Box::new(
×
NEW
174
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
NEW
175
            )),
×
NEW
176
            TypedRasterQueryProcessor::F32(p) => QueryProcessorOut(Box::new(
×
NEW
177
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
NEW
178
            )),
×
NEW
179
            TypedRasterQueryProcessor::F64(p) => QueryProcessorOut(Box::new(
×
NEW
180
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
NEW
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
    result_descriptor: RasterResultDescriptor,
196
    params: TemperatureParams,
197
    satellite_key: RasterPropertiesKey,
198
    channel_key: RasterPropertiesKey,
199
    offset_key: RasterPropertiesKey,
200
    slope_key: RasterPropertiesKey,
201
}
202

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

224
    fn satellite(&self, tile: &RasterTile2D<P>) -> Result<&'static Satellite> {
11✔
225
        let id = match self.params.force_satellite {
11✔
226
            Some(id) => id,
2✔
227
            _ => tile.properties.number_property(&self.satellite_key)?,
9✔
228
        };
229
        Satellite::satellite_by_msg_id(id)
10✔
230
    }
11✔
231

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

246
    async fn process_tile_async(
11✔
247
        &self,
11✔
248
        tile: RasterTile2D<P>,
11✔
249
        pool: Arc<ThreadPool>,
11✔
250
    ) -> Result<RasterTile2D<PixelOut>> {
11✔
251
        let satellite = self.satellite(&tile)?;
11✔
252
        let channel = self.channel(&tile, satellite)?;
8✔
253
        let offset = tile.properties.number_property::<f64>(&self.offset_key)?;
6✔
254
        let slope = tile.properties.number_property::<f64>(&self.slope_key)?;
5✔
255

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

4✔
259
            let map_fn = move |pixel_option: Option<P>| {
19✔
260
                pixel_option.and_then(|p| {
19✔
261
                    let lut_idx: u64 = p.as_();
15✔
262
                    lut.get(lut_idx as usize).copied()
15✔
263
                })
19✔
264
            };
19✔
265

266
            tile.map_elements_parallel(map_fn)
4✔
267
        })
4✔
268
        .await?;
4✔
269

270
        Ok(temp_tile)
4✔
271
    }
11✔
272
}
273

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

284
#[async_trait]
285
impl<Q, P> QueryProcessor for TemperatureProcessor<Q, P>
286
where
287
    Q: QueryProcessor<
288
        Output = RasterTile2D<P>,
289
        SpatialBounds = SpatialPartition2D,
290
        Selection = BandSelection,
291
        ResultDescription = RasterResultDescriptor,
292
    >,
293
    P: Pixel,
294
{
295
    type Output = RasterTile2D<PixelOut>;
296
    type SpatialBounds = SpatialPartition2D;
297
    type Selection = BandSelection;
298
    type ResultDescription = RasterResultDescriptor;
299

300
    async fn _query<'a>(
11✔
301
        &'a self,
11✔
302
        query: RasterQueryRectangle,
11✔
303
        ctx: &'a dyn QueryContext,
11✔
304
    ) -> Result<BoxStream<'a, Result<Self::Output>>> {
11✔
305
        let src = self.source.query(query, ctx).await?;
11✔
306
        let rs = src.and_then(move |tile| self.process_tile_async(tile, ctx.thread_pool().clone()));
11✔
307
        Ok(rs.boxed())
11✔
308
    }
22✔
309

310
    fn result_descriptor(&self) -> &Self::ResultDescription {
22✔
311
        &self.result_descriptor
22✔
312
    }
22✔
313
}
314

315
#[cfg(test)]
316
mod tests {
317
    use crate::engine::{MockExecutionContext, RasterOperator, SingleRasterSource};
318
    use crate::processing::meteosat::temperature::{Temperature, TemperatureParams};
319
    use crate::processing::meteosat::test_util;
320
    use geoengine_datatypes::primitives::{
321
        ClassificationMeasurement, ContinuousMeasurement, Measurement,
322
    };
323
    use geoengine_datatypes::raster::{EmptyGrid2D, Grid2D, MaskedGrid2D, TilingSpecification};
324
    use std::collections::HashMap;
325

326
    // #[tokio::test]
327
    // async fn test_msg_raster() {
328
    //     let mut ctx = MockExecutionContext::test_default();
329
    //     let src = test_util::_create_gdal_src(&mut ctx);
330
    //
331
    //     let result = test_util::process(
332
    //         move || {
333
    //             RasterOperator::boxed(Temperature {
334
    //                 params: TemperatureParams::default(),
335
    //                 sources: SingleRasterSource {
336
    //                     raster: src.boxed(),
337
    //                 },
338
    //             })
339
    //         },
340
    //         test_util::_create_gdal_query(),
341
    //         &ctx,
342
    //     )
343
    //     .await;
344
    //     assert!(result.as_ref().is_ok());
345
    // }
346

347
    #[tokio::test]
1✔
348
    async fn test_empty_ok() {
1✔
349
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
350
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
351

352
        let res = test_util::process(
1✔
353
            || {
1✔
354
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
355
                let src = test_util::create_mock_source::<u8>(
1✔
356
                    props,
1✔
357
                    Some(EmptyGrid2D::new([3, 2].into()).into()),
1✔
358
                    None,
1✔
359
                );
1✔
360

1✔
361
                RasterOperator::boxed(Temperature {
1✔
362
                    params: TemperatureParams::default(),
1✔
363
                    sources: SingleRasterSource {
1✔
364
                        raster: src.boxed(),
1✔
365
                    },
1✔
366
                })
1✔
367
            },
1✔
368
            test_util::create_mock_query(),
1✔
369
            &ctx,
1✔
370
        )
1✔
371
        .await
1✔
372
        .unwrap();
1✔
373

1✔
374
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
375
            &res.grid_array,
1✔
376
            &EmptyGrid2D::new([3, 2].into()).into()
1✔
377
        ));
1✔
378
    }
379

380
    #[tokio::test]
1✔
381
    async fn test_ok() {
1✔
382
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
383
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
384

385
        let res = test_util::process(
1✔
386
            || {
1✔
387
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
388
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
389

1✔
390
                RasterOperator::boxed(Temperature {
1✔
391
                    params: TemperatureParams::default(),
1✔
392
                    sources: SingleRasterSource {
1✔
393
                        raster: src.boxed(),
1✔
394
                    },
1✔
395
                })
1✔
396
            },
1✔
397
            test_util::create_mock_query(),
1✔
398
            &ctx,
1✔
399
        )
1✔
400
        .await
1✔
401
        .unwrap();
1✔
402

1✔
403
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
404
            &res.grid_array,
1✔
405
            &MaskedGrid2D::new(
1✔
406
                Grid2D::new(
1✔
407
                    [3, 2].into(),
1✔
408
                    vec![300.341_43, 318.617_65, 330.365_14, 339.233_64, 346.443_94, 0.,],
1✔
409
                )
1✔
410
                .unwrap(),
1✔
411
                Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false,],).unwrap(),
1✔
412
            )
1✔
413
            .unwrap()
1✔
414
            .into()
1✔
415
        ));
1✔
416

417
        // TODO: add assert to check mask
418
    }
419

420
    #[tokio::test]
1✔
421
    async fn test_ok_force_satellite() {
1✔
422
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
423
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
424

425
        let res = test_util::process(
1✔
426
            || {
1✔
427
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
428
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
429

1✔
430
                RasterOperator::boxed(Temperature {
1✔
431
                    params: TemperatureParams {
1✔
432
                        force_satellite: Some(4),
1✔
433
                    },
1✔
434
                    sources: SingleRasterSource {
1✔
435
                        raster: src.boxed(),
1✔
436
                    },
1✔
437
                })
1✔
438
            },
1✔
439
            test_util::create_mock_query(),
1✔
440
            &ctx,
1✔
441
        )
1✔
442
        .await
1✔
443
        .unwrap();
1✔
444

1✔
445
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
446
            &res.grid_array,
1✔
447
            &MaskedGrid2D::new(
1✔
448
                Grid2D::new(
1✔
449
                    [3, 2].into(),
1✔
450
                    vec![300.9428, 319.250_15, 331.019_04, 339.9044, 347.128_78, 0.],
1✔
451
                )
1✔
452
                .unwrap(),
1✔
453
                Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false,],).unwrap(),
1✔
454
            )
1✔
455
            .unwrap()
1✔
456
            .into()
1✔
457
        ));
1✔
458
    }
459

460
    #[tokio::test]
1✔
461
    async fn test_ok_illegal_input_to_masked() {
1✔
462
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
463
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
464

465
        let res = test_util::process(
1✔
466
            || {
1✔
467
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
468
                let src = test_util::create_mock_source::<u16>(
1✔
469
                    props,
1✔
470
                    Some(
1✔
471
                        MaskedGrid2D::new(
1✔
472
                            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 1024, 0]).unwrap(),
1✔
473
                            Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false])
1✔
474
                                .unwrap(),
1✔
475
                        )
1✔
476
                        .unwrap()
1✔
477
                        .into(),
1✔
478
                    ),
1✔
479
                    None,
1✔
480
                );
1✔
481

1✔
482
                RasterOperator::boxed(Temperature {
1✔
483
                    params: TemperatureParams::default(),
1✔
484
                    sources: SingleRasterSource {
1✔
485
                        raster: src.boxed(),
1✔
486
                    },
1✔
487
                })
1✔
488
            },
1✔
489
            test_util::create_mock_query(),
1✔
490
            &ctx,
1✔
491
        )
1✔
492
        .await;
1✔
493
        assert!(res.is_ok());
1✔
494
        let res = res.unwrap();
1✔
495
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
496
            &res.grid_array,
1✔
497
            &MaskedGrid2D::new(
1✔
498
                Grid2D::new(
1✔
499
                    [3, 2].into(),
1✔
500
                    vec![300.341_43, 318.617_65, 330.365_14, 339.233_64, 0., 0.],
1✔
501
                )
1✔
502
                .unwrap(),
1✔
503
                Grid2D::new([3, 2].into(), vec![true, true, true, true, false, false,],).unwrap(),
1✔
504
            )
1✔
505
            .unwrap()
1✔
506
            .into()
1✔
507
        ));
1✔
508
    }
509

510
    #[tokio::test]
1✔
511
    async fn test_invalid_force_satellite() {
1✔
512
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
513
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
514

515
        let res = test_util::process(
1✔
516
            || {
1✔
517
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
518
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
519

1✔
520
                RasterOperator::boxed(Temperature {
1✔
521
                    params: TemperatureParams {
1✔
522
                        force_satellite: Some(13),
1✔
523
                    },
1✔
524
                    sources: SingleRasterSource {
1✔
525
                        raster: src.boxed(),
1✔
526
                    },
1✔
527
                })
1✔
528
            },
1✔
529
            test_util::create_mock_query(),
1✔
530
            &ctx,
1✔
531
        )
1✔
532
        .await;
×
533
        assert!(res.is_err());
1✔
534
    }
535

536
    #[tokio::test]
1✔
537
    async fn test_missing_satellite() {
1✔
538
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
539
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
540

541
        let res = test_util::process(
1✔
542
            || {
1✔
543
                let props = test_util::create_properties(Some(4), None, Some(0.0), Some(1.0));
1✔
544
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
545

1✔
546
                RasterOperator::boxed(Temperature {
1✔
547
                    params: TemperatureParams::default(),
1✔
548
                    sources: SingleRasterSource {
1✔
549
                        raster: src.boxed(),
1✔
550
                    },
1✔
551
                })
1✔
552
            },
1✔
553
            test_util::create_mock_query(),
1✔
554
            &ctx,
1✔
555
        )
1✔
556
        .await;
×
557
        assert!(res.is_err());
1✔
558
    }
559

560
    #[tokio::test]
1✔
561
    async fn test_invalid_satellite() {
1✔
562
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
563
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
564

565
        let res = test_util::process(
1✔
566
            || {
1✔
567
                let props = test_util::create_properties(Some(4), Some(42), Some(0.0), Some(1.0));
1✔
568
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
569

1✔
570
                RasterOperator::boxed(Temperature {
1✔
571
                    params: TemperatureParams::default(),
1✔
572
                    sources: SingleRasterSource {
1✔
573
                        raster: src.boxed(),
1✔
574
                    },
1✔
575
                })
1✔
576
            },
1✔
577
            test_util::create_mock_query(),
1✔
578
            &ctx,
1✔
579
        )
1✔
580
        .await;
×
581
        assert!(res.is_err());
1✔
582
    }
583

584
    #[tokio::test]
1✔
585
    async fn test_missing_channel() {
1✔
586
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
587
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
588

589
        let res = test_util::process(
1✔
590
            || {
1✔
591
                let props = test_util::create_properties(None, Some(1), Some(0.0), Some(1.0));
1✔
592
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
593

1✔
594
                RasterOperator::boxed(Temperature {
1✔
595
                    params: TemperatureParams::default(),
1✔
596
                    sources: SingleRasterSource {
1✔
597
                        raster: src.boxed(),
1✔
598
                    },
1✔
599
                })
1✔
600
            },
1✔
601
            test_util::create_mock_query(),
1✔
602
            &ctx,
1✔
603
        )
1✔
604
        .await;
×
605
        assert!(res.is_err());
1✔
606
    }
607

608
    #[tokio::test]
1✔
609
    async fn test_invalid_channel() {
1✔
610
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
611
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
612

613
        let res = test_util::process(
1✔
614
            || {
1✔
615
                let props = test_util::create_properties(Some(1), Some(1), Some(0.0), Some(1.0));
1✔
616
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
617

1✔
618
                RasterOperator::boxed(Temperature {
1✔
619
                    params: TemperatureParams::default(),
1✔
620
                    sources: SingleRasterSource {
1✔
621
                        raster: src.boxed(),
1✔
622
                    },
1✔
623
                })
1✔
624
            },
1✔
625
            test_util::create_mock_query(),
1✔
626
            &ctx,
1✔
627
        )
1✔
628
        .await;
×
629
        assert!(res.is_err());
1✔
630
    }
631

632
    #[tokio::test]
1✔
633
    async fn test_missing_slope() {
1✔
634
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
635
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
636

637
        let res = test_util::process(
1✔
638
            || {
1✔
639
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), None);
1✔
640
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
641

1✔
642
                RasterOperator::boxed(Temperature {
1✔
643
                    params: TemperatureParams::default(),
1✔
644
                    sources: SingleRasterSource {
1✔
645
                        raster: src.boxed(),
1✔
646
                    },
1✔
647
                })
1✔
648
            },
1✔
649
            test_util::create_mock_query(),
1✔
650
            &ctx,
1✔
651
        )
1✔
652
        .await;
×
653
        assert!(res.is_err());
1✔
654
    }
655

656
    #[tokio::test]
1✔
657
    async fn test_missing_offset() {
1✔
658
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
659
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
660

661
        let res = test_util::process(
1✔
662
            || {
1✔
663
                let props = test_util::create_properties(Some(4), Some(1), None, Some(1.0));
1✔
664
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
665

1✔
666
                RasterOperator::boxed(Temperature {
1✔
667
                    params: TemperatureParams::default(),
1✔
668
                    sources: SingleRasterSource {
1✔
669
                        raster: src.boxed(),
1✔
670
                    },
1✔
671
                })
1✔
672
            },
1✔
673
            test_util::create_mock_query(),
1✔
674
            &ctx,
1✔
675
        )
1✔
676
        .await;
×
677
        assert!(res.is_err());
1✔
678
    }
679

680
    #[tokio::test]
1✔
681
    async fn test_invalid_measurement_unitless() {
1✔
682
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
683
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
684

685
        let res = test_util::process(
1✔
686
            || {
1✔
687
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
688
                let src =
1✔
689
                    test_util::create_mock_source::<u8>(props, None, Some(Measurement::Unitless));
1✔
690

1✔
691
                RasterOperator::boxed(Temperature {
1✔
692
                    params: TemperatureParams::default(),
1✔
693
                    sources: SingleRasterSource {
1✔
694
                        raster: src.boxed(),
1✔
695
                    },
1✔
696
                })
1✔
697
            },
1✔
698
            test_util::create_mock_query(),
1✔
699
            &ctx,
1✔
700
        )
1✔
701
        .await;
×
702
        assert!(res.is_err());
1✔
703
    }
704

705
    #[tokio::test]
1✔
706
    async fn test_invalid_measurement_continuous() {
1✔
707
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
708
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
709

710
        let res = test_util::process(
1✔
711
            || {
1✔
712
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
713
                let src = test_util::create_mock_source::<u8>(
1✔
714
                    props,
1✔
715
                    None,
1✔
716
                    Some(Measurement::Continuous(ContinuousMeasurement {
1✔
717
                        measurement: "invalid".into(),
1✔
718
                        unit: None,
1✔
719
                    })),
1✔
720
                );
1✔
721

1✔
722
                RasterOperator::boxed(Temperature {
1✔
723
                    params: TemperatureParams::default(),
1✔
724
                    sources: SingleRasterSource {
1✔
725
                        raster: src.boxed(),
1✔
726
                    },
1✔
727
                })
1✔
728
            },
1✔
729
            test_util::create_mock_query(),
1✔
730
            &ctx,
1✔
731
        )
1✔
732
        .await;
×
733

734
        assert!(res.is_err());
1✔
735
    }
736

737
    #[tokio::test]
1✔
738
    async fn test_invalid_measurement_classification() {
1✔
739
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
740

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

743
        let res = test_util::process(
1✔
744
            || {
1✔
745
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
746
                let src = test_util::create_mock_source::<u8>(
1✔
747
                    props,
1✔
748
                    None,
1✔
749
                    Some(Measurement::Classification(ClassificationMeasurement {
1✔
750
                        measurement: "invalid".into(),
1✔
751
                        classes: HashMap::new(),
1✔
752
                    })),
1✔
753
                );
1✔
754

1✔
755
                RasterOperator::boxed(Temperature {
1✔
756
                    params: TemperatureParams::default(),
1✔
757
                    sources: SingleRasterSource {
1✔
758
                        raster: src.boxed(),
1✔
759
                    },
1✔
760
                })
1✔
761
            },
1✔
762
            test_util::create_mock_query(),
1✔
763
            &ctx,
1✔
764
        )
1✔
765
        .await;
×
766
        assert!(res.is_err());
1✔
767
    }
768
}
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