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

geo-engine / geoengine / 5006008836

pending completion
5006008836

push

github

GitHub
Merge #785 #787

936 of 936 new or added lines in 50 files covered. (100.0%)

96010 of 107707 relevant lines covered (89.14%)

72676.46 hits per line

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

93.61
/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, RasterOperator, RasterQueryProcessor,
6
    RasterResultDescriptor, SingleRasterSource, TypedRasterQueryProcessor, WorkflowOperatorPath,
7
};
8
use crate::util::Result;
9
use async_trait::async_trait;
10
use rayon::ThreadPool;
11

12
use TypedRasterQueryProcessor::F32 as QueryProcessorOut;
13

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

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

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

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

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

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

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

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

14✔
70
        let in_desc = input.result_descriptor();
14✔
71

72
        match &in_desc.measurement {
12✔
73
            Measurement::Continuous(ContinuousMeasurement {
12✔
74
                measurement: m,
12✔
75
                unit: _,
12✔
76
            }) if m != "raw" => {
12✔
77
                return Err(Error::InvalidMeasurement {
1✔
78
                    expected: "raw".into(),
1✔
79
                    found: m.clone(),
1✔
80
                })
1✔
81
            }
82
            Measurement::Classification(ClassificationMeasurement {
83
                measurement: m,
1✔
84
                classes: _,
1✔
85
            }) => {
1✔
86
                return Err(Error::InvalidMeasurement {
1✔
87
                    expected: "raw".into(),
1✔
88
                    found: m.clone(),
1✔
89
                })
1✔
90
            }
91
            Measurement::Unitless => {
92
                return Err(Error::InvalidMeasurement {
1✔
93
                    expected: "raw".into(),
1✔
94
                    found: "unitless".into(),
1✔
95
                })
1✔
96
            }
97
            // OK Case
98
            Measurement::Continuous(ContinuousMeasurement {
99
                measurement: _,
100
                unit: _,
101
            }) => {}
11✔
102
        }
11✔
103

11✔
104
        let out_desc = RasterResultDescriptor {
11✔
105
            spatial_reference: in_desc.spatial_reference,
11✔
106
            data_type: RasterOut,
11✔
107
            measurement: Measurement::Continuous(ContinuousMeasurement {
11✔
108
                measurement: "temperature".into(),
11✔
109
                unit: Some("k".into()),
11✔
110
            }),
11✔
111
            time: in_desc.time,
11✔
112
            bbox: in_desc.bbox,
11✔
113
            resolution: in_desc.resolution,
11✔
114
        };
11✔
115

11✔
116
        let initialized_operator = InitializedTemperature {
11✔
117
            name,
11✔
118
            result_descriptor: out_desc,
11✔
119
            source: input,
11✔
120
            params: self.params,
11✔
121
        };
11✔
122

11✔
123
        Ok(initialized_operator.boxed())
11✔
124
    }
28✔
125

126
    span_fn!(Temperature);
×
127
}
128

129
impl InitializedRasterOperator for InitializedTemperature {
130
    fn result_descriptor(&self) -> &RasterResultDescriptor {
×
131
        &self.result_descriptor
×
132
    }
×
133

134
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor, Error> {
11✔
135
        let q = self.source.query_processor()?;
11✔
136

137
        Ok(match q {
11✔
138
            TypedRasterQueryProcessor::U8(p) => {
10✔
139
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
10✔
140
            }
141
            TypedRasterQueryProcessor::U16(p) => {
1✔
142
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
1✔
143
            }
144
            TypedRasterQueryProcessor::U32(p) => {
×
145
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
146
            }
147
            TypedRasterQueryProcessor::U64(p) => {
×
148
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
149
            }
150
            TypedRasterQueryProcessor::I8(p) => {
×
151
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
152
            }
153
            TypedRasterQueryProcessor::I16(p) => {
×
154
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
155
            }
156
            TypedRasterQueryProcessor::I32(p) => {
×
157
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
158
            }
159
            TypedRasterQueryProcessor::I64(p) => {
×
160
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
161
            }
162
            TypedRasterQueryProcessor::F32(p) => {
×
163
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
164
            }
165
            TypedRasterQueryProcessor::F64(p) => {
×
166
                QueryProcessorOut(Box::new(TemperatureProcessor::new(p, self.params.clone())))
×
167
            }
168
        })
169
    }
11✔
170

171
    fn canonic_name(&self) -> CanonicOperatorName {
×
172
        self.name.clone()
×
173
    }
×
174
}
175

176
struct TemperatureProcessor<Q, P>
177
where
178
    Q: RasterQueryProcessor<RasterType = P>,
179
{
180
    source: Q,
181
    params: TemperatureParams,
182
    satellite_key: RasterPropertiesKey,
183
    channel_key: RasterPropertiesKey,
184
    offset_key: RasterPropertiesKey,
185
    slope_key: RasterPropertiesKey,
186
}
187

188
impl<Q, P> TemperatureProcessor<Q, P>
189
where
190
    Q: RasterQueryProcessor<RasterType = P>,
191
    P: Pixel,
192
{
193
    pub fn new(source: Q, params: TemperatureParams) -> Self {
11✔
194
        Self {
11✔
195
            source,
11✔
196
            params,
11✔
197
            satellite_key: new_satellite_key(),
11✔
198
            channel_key: new_channel_key(),
11✔
199
            offset_key: new_offset_key(),
11✔
200
            slope_key: new_slope_key(),
11✔
201
        }
11✔
202
    }
11✔
203

204
    fn satellite(&self, tile: &RasterTile2D<P>) -> Result<&'static Satellite> {
11✔
205
        let id = match self.params.force_satellite {
11✔
206
            Some(id) => id,
2✔
207
            _ => tile.properties.number_property(&self.satellite_key)?,
9✔
208
        };
209
        Satellite::satellite_by_msg_id(id)
10✔
210
    }
11✔
211

212
    fn channel<'a>(&self, tile: &RasterTile2D<P>, satellite: &'a Satellite) -> Result<&'a Channel> {
8✔
213
        let channel_id = tile
8✔
214
            .properties
8✔
215
            .number_property::<usize>(&self.channel_key)?
8✔
216
            - 1;
217
        if (3..=10).contains(&channel_id) {
7✔
218
            satellite.channel(channel_id)
6✔
219
        } else {
220
            Err(Error::InvalidChannel {
1✔
221
                channel: channel_id,
1✔
222
            })
1✔
223
        }
224
    }
8✔
225

226
    async fn process_tile_async(
11✔
227
        &self,
11✔
228
        tile: RasterTile2D<P>,
11✔
229
        pool: Arc<ThreadPool>,
11✔
230
    ) -> Result<RasterTile2D<PixelOut>> {
11✔
231
        let satellite = self.satellite(&tile)?;
11✔
232
        let channel = self.channel(&tile, satellite)?;
8✔
233
        let offset = tile.properties.number_property::<f64>(&self.offset_key)?;
6✔
234
        let slope = tile.properties.number_property::<f64>(&self.slope_key)?;
5✔
235

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

4✔
239
            let map_fn = move |pixel_option: Option<P>| {
19✔
240
                pixel_option.and_then(|p| {
19✔
241
                    let lut_idx: u64 = p.as_();
15✔
242
                    lut.get(lut_idx as usize).copied()
15✔
243
                })
19✔
244
            };
19✔
245

246
            tile.map_elements_parallel(map_fn)
4✔
247
        })
4✔
248
        .await?;
4✔
249

250
        Ok(temp_tile)
4✔
251
    }
11✔
252
}
253

254
fn create_lookup_table(channel: &Channel, offset: f64, slope: f64, _pool: &ThreadPool) -> Vec<f32> {
4✔
255
    // this should propably be done with SIMD not a threadpool
4✔
256
    (0..1024)
4✔
257
        .into_iter()
4✔
258
        .map(|i| {
4,096✔
259
            let radiance = offset + f64::from(i) * slope;
4,096✔
260
            channel.calculate_temperature_from_radiance(radiance) as f32
4,096✔
261
        })
4,096✔
262
        .collect::<Vec<f32>>()
4✔
263
}
4✔
264

265
#[async_trait]
266
impl<Q, P> QueryProcessor for TemperatureProcessor<Q, P>
267
where
268
    Q: QueryProcessor<Output = RasterTile2D<P>, SpatialBounds = SpatialPartition2D>,
269
    P: Pixel,
270
{
271
    type Output = RasterTile2D<PixelOut>;
272
    type SpatialBounds = SpatialPartition2D;
273

274
    async fn _query<'a>(
11✔
275
        &'a self,
11✔
276
        query: RasterQueryRectangle,
11✔
277
        ctx: &'a dyn QueryContext,
11✔
278
    ) -> Result<BoxStream<'a, Result<Self::Output>>> {
11✔
279
        let src = self.source.query(query, ctx).await?;
11✔
280
        let rs = src.and_then(move |tile| self.process_tile_async(tile, ctx.thread_pool().clone()));
11✔
281
        Ok(rs.boxed())
11✔
282
    }
22✔
283
}
284

285
#[cfg(test)]
286
mod tests {
287
    use crate::engine::{MockExecutionContext, RasterOperator, SingleRasterSource};
288
    use crate::processing::meteosat::temperature::{Temperature, TemperatureParams};
289
    use crate::processing::meteosat::test_util;
290
    use geoengine_datatypes::primitives::{
291
        ClassificationMeasurement, ContinuousMeasurement, Measurement,
292
    };
293
    use geoengine_datatypes::raster::{EmptyGrid2D, Grid2D, MaskedGrid2D, TilingSpecification};
294
    use std::collections::HashMap;
295

296
    // #[tokio::test]
297
    // async fn test_msg_raster() {
298
    //     let mut ctx = MockExecutionContext::test_default();
299
    //     let src = test_util::_create_gdal_src(&mut ctx);
300
    //
301
    //     let result = test_util::process(
302
    //         move || {
303
    //             RasterOperator::boxed(Temperature {
304
    //                 params: TemperatureParams::default(),
305
    //                 sources: SingleRasterSource {
306
    //                     raster: src.boxed(),
307
    //                 },
308
    //             })
309
    //         },
310
    //         test_util::_create_gdal_query(),
311
    //         &ctx,
312
    //     )
313
    //     .await;
314
    //     assert!(result.as_ref().is_ok());
315
    // }
316

317
    #[tokio::test]
1✔
318
    async fn test_empty_ok() {
1✔
319
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
320
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
321

322
        let res = test_util::process(
1✔
323
            || {
1✔
324
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
325
                let src = test_util::create_mock_source::<u8>(
1✔
326
                    props,
1✔
327
                    Some(EmptyGrid2D::new([3, 2].into()).into()),
1✔
328
                    None,
1✔
329
                );
1✔
330

1✔
331
                RasterOperator::boxed(Temperature {
1✔
332
                    params: TemperatureParams::default(),
1✔
333
                    sources: SingleRasterSource {
1✔
334
                        raster: src.boxed(),
1✔
335
                    },
1✔
336
                })
1✔
337
            },
1✔
338
            test_util::create_mock_query(),
1✔
339
            &ctx,
1✔
340
        )
1✔
341
        .await
1✔
342
        .unwrap();
1✔
343

1✔
344
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
345
            &res.grid_array,
1✔
346
            &EmptyGrid2D::new([3, 2].into()).into()
1✔
347
        ));
1✔
348
    }
349

350
    #[tokio::test]
1✔
351
    async fn test_ok() {
1✔
352
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
353
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
354

355
        let res = test_util::process(
1✔
356
            || {
1✔
357
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
358
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
359

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

1✔
373
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
374
            &res.grid_array,
1✔
375
            &MaskedGrid2D::new(
1✔
376
                Grid2D::new(
1✔
377
                    [3, 2].into(),
1✔
378
                    vec![300.341_43, 318.617_65, 330.365_14, 339.233_64, 346.443_94, 0.,],
1✔
379
                )
1✔
380
                .unwrap(),
1✔
381
                Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false,],).unwrap(),
1✔
382
            )
1✔
383
            .unwrap()
1✔
384
            .into()
1✔
385
        ));
1✔
386

387
        // TODO: add assert to check mask
388
    }
389

390
    #[tokio::test]
1✔
391
    async fn test_ok_force_satellite() {
1✔
392
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
393
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
394

395
        let res = test_util::process(
1✔
396
            || {
1✔
397
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
398
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
399

1✔
400
                RasterOperator::boxed(Temperature {
1✔
401
                    params: TemperatureParams {
1✔
402
                        force_satellite: Some(4),
1✔
403
                    },
1✔
404
                    sources: SingleRasterSource {
1✔
405
                        raster: src.boxed(),
1✔
406
                    },
1✔
407
                })
1✔
408
            },
1✔
409
            test_util::create_mock_query(),
1✔
410
            &ctx,
1✔
411
        )
1✔
412
        .await
1✔
413
        .unwrap();
1✔
414

1✔
415
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
416
            &res.grid_array,
1✔
417
            &MaskedGrid2D::new(
1✔
418
                Grid2D::new(
1✔
419
                    [3, 2].into(),
1✔
420
                    vec![300.9428, 319.250_15, 331.019_04, 339.9044, 347.128_78, 0.],
1✔
421
                )
1✔
422
                .unwrap(),
1✔
423
                Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false,],).unwrap(),
1✔
424
            )
1✔
425
            .unwrap()
1✔
426
            .into()
1✔
427
        ));
1✔
428
    }
429

430
    #[tokio::test]
1✔
431
    async fn test_ok_illegal_input_to_masked() {
1✔
432
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
433
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
434

435
        let res = test_util::process(
1✔
436
            || {
1✔
437
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
438
                let src = test_util::create_mock_source::<u16>(
1✔
439
                    props,
1✔
440
                    Some(
1✔
441
                        MaskedGrid2D::new(
1✔
442
                            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 1024, 0]).unwrap(),
1✔
443
                            Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false])
1✔
444
                                .unwrap(),
1✔
445
                        )
1✔
446
                        .unwrap()
1✔
447
                        .into(),
1✔
448
                    ),
1✔
449
                    None,
1✔
450
                );
1✔
451

1✔
452
                RasterOperator::boxed(Temperature {
1✔
453
                    params: TemperatureParams::default(),
1✔
454
                    sources: SingleRasterSource {
1✔
455
                        raster: src.boxed(),
1✔
456
                    },
1✔
457
                })
1✔
458
            },
1✔
459
            test_util::create_mock_query(),
1✔
460
            &ctx,
1✔
461
        )
1✔
462
        .await;
1✔
463
        assert!(res.is_ok());
1✔
464
        let res = res.unwrap();
1✔
465
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
466
            &res.grid_array,
1✔
467
            &MaskedGrid2D::new(
1✔
468
                Grid2D::new(
1✔
469
                    [3, 2].into(),
1✔
470
                    vec![300.341_43, 318.617_65, 330.365_14, 339.233_64, 0., 0.],
1✔
471
                )
1✔
472
                .unwrap(),
1✔
473
                Grid2D::new([3, 2].into(), vec![true, true, true, true, false, false,],).unwrap(),
1✔
474
            )
1✔
475
            .unwrap()
1✔
476
            .into()
1✔
477
        ));
1✔
478
    }
479

480
    #[tokio::test]
1✔
481
    async fn test_invalid_force_satellite() {
1✔
482
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
483
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
484

485
        let res = test_util::process(
1✔
486
            || {
1✔
487
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
488
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
489

1✔
490
                RasterOperator::boxed(Temperature {
1✔
491
                    params: TemperatureParams {
1✔
492
                        force_satellite: Some(13),
1✔
493
                    },
1✔
494
                    sources: SingleRasterSource {
1✔
495
                        raster: src.boxed(),
1✔
496
                    },
1✔
497
                })
1✔
498
            },
1✔
499
            test_util::create_mock_query(),
1✔
500
            &ctx,
1✔
501
        )
1✔
502
        .await;
×
503
        assert!(res.is_err());
1✔
504
    }
505

506
    #[tokio::test]
1✔
507
    async fn test_missing_satellite() {
1✔
508
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
509
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
510

511
        let res = test_util::process(
1✔
512
            || {
1✔
513
                let props = test_util::create_properties(Some(4), None, Some(0.0), Some(1.0));
1✔
514
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
515

1✔
516
                RasterOperator::boxed(Temperature {
1✔
517
                    params: TemperatureParams::default(),
1✔
518
                    sources: SingleRasterSource {
1✔
519
                        raster: src.boxed(),
1✔
520
                    },
1✔
521
                })
1✔
522
            },
1✔
523
            test_util::create_mock_query(),
1✔
524
            &ctx,
1✔
525
        )
1✔
526
        .await;
×
527
        assert!(res.is_err());
1✔
528
    }
529

530
    #[tokio::test]
1✔
531
    async fn test_invalid_satellite() {
1✔
532
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
533
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
534

535
        let res = test_util::process(
1✔
536
            || {
1✔
537
                let props = test_util::create_properties(Some(4), Some(42), Some(0.0), Some(1.0));
1✔
538
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
539

1✔
540
                RasterOperator::boxed(Temperature {
1✔
541
                    params: TemperatureParams::default(),
1✔
542
                    sources: SingleRasterSource {
1✔
543
                        raster: src.boxed(),
1✔
544
                    },
1✔
545
                })
1✔
546
            },
1✔
547
            test_util::create_mock_query(),
1✔
548
            &ctx,
1✔
549
        )
1✔
550
        .await;
×
551
        assert!(res.is_err());
1✔
552
    }
553

554
    #[tokio::test]
1✔
555
    async fn test_missing_channel() {
1✔
556
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
557
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
558

559
        let res = test_util::process(
1✔
560
            || {
1✔
561
                let props = test_util::create_properties(None, Some(1), Some(0.0), Some(1.0));
1✔
562
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
563

1✔
564
                RasterOperator::boxed(Temperature {
1✔
565
                    params: TemperatureParams::default(),
1✔
566
                    sources: SingleRasterSource {
1✔
567
                        raster: src.boxed(),
1✔
568
                    },
1✔
569
                })
1✔
570
            },
1✔
571
            test_util::create_mock_query(),
1✔
572
            &ctx,
1✔
573
        )
1✔
574
        .await;
×
575
        assert!(res.is_err());
1✔
576
    }
577

578
    #[tokio::test]
1✔
579
    async fn test_invalid_channel() {
1✔
580
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
581
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
582

583
        let res = test_util::process(
1✔
584
            || {
1✔
585
                let props = test_util::create_properties(Some(1), Some(1), Some(0.0), Some(1.0));
1✔
586
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
587

1✔
588
                RasterOperator::boxed(Temperature {
1✔
589
                    params: TemperatureParams::default(),
1✔
590
                    sources: SingleRasterSource {
1✔
591
                        raster: src.boxed(),
1✔
592
                    },
1✔
593
                })
1✔
594
            },
1✔
595
            test_util::create_mock_query(),
1✔
596
            &ctx,
1✔
597
        )
1✔
598
        .await;
×
599
        assert!(res.is_err());
1✔
600
    }
601

602
    #[tokio::test]
1✔
603
    async fn test_missing_slope() {
1✔
604
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
605
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
606

607
        let res = test_util::process(
1✔
608
            || {
1✔
609
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), None);
1✔
610
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
611

1✔
612
                RasterOperator::boxed(Temperature {
1✔
613
                    params: TemperatureParams::default(),
1✔
614
                    sources: SingleRasterSource {
1✔
615
                        raster: src.boxed(),
1✔
616
                    },
1✔
617
                })
1✔
618
            },
1✔
619
            test_util::create_mock_query(),
1✔
620
            &ctx,
1✔
621
        )
1✔
622
        .await;
×
623
        assert!(res.is_err());
1✔
624
    }
625

626
    #[tokio::test]
1✔
627
    async fn test_missing_offset() {
1✔
628
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
629
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
630

631
        let res = test_util::process(
1✔
632
            || {
1✔
633
                let props = test_util::create_properties(Some(4), Some(1), None, Some(1.0));
1✔
634
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
635

1✔
636
                RasterOperator::boxed(Temperature {
1✔
637
                    params: TemperatureParams::default(),
1✔
638
                    sources: SingleRasterSource {
1✔
639
                        raster: src.boxed(),
1✔
640
                    },
1✔
641
                })
1✔
642
            },
1✔
643
            test_util::create_mock_query(),
1✔
644
            &ctx,
1✔
645
        )
1✔
646
        .await;
×
647
        assert!(res.is_err());
1✔
648
    }
649

650
    #[tokio::test]
1✔
651
    async fn test_invalid_measurement_unitless() {
1✔
652
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
653
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
654

655
        let res = test_util::process(
1✔
656
            || {
1✔
657
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
658
                let src =
1✔
659
                    test_util::create_mock_source::<u8>(props, None, Some(Measurement::Unitless));
1✔
660

1✔
661
                RasterOperator::boxed(Temperature {
1✔
662
                    params: TemperatureParams::default(),
1✔
663
                    sources: SingleRasterSource {
1✔
664
                        raster: src.boxed(),
1✔
665
                    },
1✔
666
                })
1✔
667
            },
1✔
668
            test_util::create_mock_query(),
1✔
669
            &ctx,
1✔
670
        )
1✔
671
        .await;
×
672
        assert!(res.is_err());
1✔
673
    }
674

675
    #[tokio::test]
1✔
676
    async fn test_invalid_measurement_continuous() {
1✔
677
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
678
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
679

680
        let res = test_util::process(
1✔
681
            || {
1✔
682
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
683
                let src = test_util::create_mock_source::<u8>(
1✔
684
                    props,
1✔
685
                    None,
1✔
686
                    Some(Measurement::Continuous(ContinuousMeasurement {
1✔
687
                        measurement: "invalid".into(),
1✔
688
                        unit: None,
1✔
689
                    })),
1✔
690
                );
1✔
691

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

704
        assert!(res.is_err());
1✔
705
    }
706

707
    #[tokio::test]
1✔
708
    async fn test_invalid_measurement_classification() {
1✔
709
        let tiling_specification = TilingSpecification::new([0.0, 0.0].into(), [3, 2].into());
1✔
710

1✔
711
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
712

713
        let res = test_util::process(
1✔
714
            || {
1✔
715
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
716
                let src = test_util::create_mock_source::<u8>(
1✔
717
                    props,
1✔
718
                    None,
1✔
719
                    Some(Measurement::Classification(ClassificationMeasurement {
1✔
720
                        measurement: "invalid".into(),
1✔
721
                        classes: HashMap::new(),
1✔
722
                    })),
1✔
723
                );
1✔
724

1✔
725
                RasterOperator::boxed(Temperature {
1✔
726
                    params: TemperatureParams::default(),
1✔
727
                    sources: SingleRasterSource {
1✔
728
                        raster: src.boxed(),
1✔
729
                    },
1✔
730
                })
1✔
731
            },
1✔
732
            test_util::create_mock_query(),
1✔
733
            &ctx,
1✔
734
        )
1✔
735
        .await;
×
736
        assert!(res.is_err());
1✔
737
    }
738
}
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