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

geo-engine / geoengine / 19148930607

06 Nov 2025 08:28PM UTC coverage: 88.827%. First build
19148930607

Pull #1083

github

web-flow
Merge 287fd2af8 into 113de40ca
Pull Request #1083: feat: new gdal source workflow optimization

6168 of 7008 new or added lines in 71 files covered. (88.01%)

116144 of 130753 relevant lines covered (88.83%)

496539.53 hits per line

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

90.11
/operators/src/processing/meteosat/temperature.rs
1
use crate::engine::{
2
    CanonicOperatorName, ExecutionContext, InitializedRasterOperator, InitializedSources, Operator,
3
    OperatorName, QueryProcessor, RasterBandDescriptor, RasterBandDescriptors, RasterOperator,
4
    RasterQueryProcessor, RasterResultDescriptor, SingleRasterSource, TypedRasterQueryProcessor,
5
    WorkflowOperatorPath,
6
};
7
use crate::error::Error;
8
use crate::optimization::OptimizationError;
9
use crate::util::Result;
10
use TypedRasterQueryProcessor::F32 as QueryProcessorOut;
11
use async_trait::async_trait;
12
use futures::{StreamExt, TryStreamExt};
13
use geoengine_datatypes::primitives::{
14
    BandSelection, ClassificationMeasurement, ContinuousMeasurement, Measurement,
15
    RasterQueryRectangle, SpatialResolution,
16
};
17
use geoengine_datatypes::raster::{
18
    GridBoundingBox2D, MapElementsParallel, Pixel, RasterDataType, RasterPropertiesKey,
19
    RasterTile2D,
20
};
21
use rayon::ThreadPool;
22
use serde::{Deserialize, Serialize};
23
use std::sync::Arc;
24

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

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

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

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

49
pub struct InitializedTemperature {
50
    name: CanonicOperatorName,
51
    path: WorkflowOperatorPath,
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(
61
        self: Box<Self>,
62
        path: WorkflowOperatorPath,
63
        context: &dyn ExecutionContext,
64
    ) -> Result<Box<dyn InitializedRasterOperator>> {
14✔
65
        let name = CanonicOperatorName::from(&self);
66

67
        let initialized_sources = self
68
            .sources
69
            .initialize_sources(path.clone(), context)
70
            .await?;
71
        let input = initialized_sources.raster;
72

73
        let in_desc = input.result_descriptor();
74

75
        for band in in_desc.bands.iter() {
76
            match &band.measurement {
77
                Measurement::Continuous(ContinuousMeasurement {
78
                    measurement: m,
79
                    unit: _,
80
                }) if m != "raw" => {
81
                    return Err(Error::InvalidMeasurement {
82
                        expected: "raw".into(),
83
                        found: m.clone(),
84
                    });
85
                }
86
                Measurement::Classification(ClassificationMeasurement {
87
                    measurement: m,
88
                    classes: _,
89
                }) => {
90
                    return Err(Error::InvalidMeasurement {
91
                        expected: "raw".into(),
92
                        found: m.clone(),
93
                    });
94
                }
95
                Measurement::Unitless => {
96
                    return Err(Error::InvalidMeasurement {
97
                        expected: "raw".into(),
98
                        found: "unitless".into(),
99
                    });
100
                }
101
                // OK Case
102
                Measurement::Continuous(ContinuousMeasurement {
103
                    measurement: _,
104
                    unit: _,
105
                }) => {}
106
            }
107
        }
108

109
        let out_desc = RasterResultDescriptor {
110
            spatial_reference: in_desc.spatial_reference,
111
            data_type: RasterOut,
112
            time: in_desc.time,
113
            spatial_grid: in_desc.spatial_grid,
114
            bands: RasterBandDescriptors::new(
115
                in_desc
116
                    .bands
117
                    .iter()
118
                    .map(|b| RasterBandDescriptor {
119
                        name: b.name.clone(),
11✔
120
                        measurement: Measurement::Continuous(ContinuousMeasurement {
11✔
121
                            measurement: "temperature".into(),
11✔
122
                            unit: Some("k".into()),
11✔
123
                        }),
11✔
124
                    })
11✔
125
                    .collect::<Vec<_>>(),
126
            )?,
127
        };
128

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

137
        Ok(initialized_operator.boxed())
138
    }
14✔
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✔
158
            TypedRasterQueryProcessor::U32(p) => QueryProcessorOut(Box::new(
×
159
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
160
            )),
×
161
            TypedRasterQueryProcessor::U64(p) => QueryProcessorOut(Box::new(
×
162
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
163
            )),
×
164
            TypedRasterQueryProcessor::I8(p) => QueryProcessorOut(Box::new(
×
165
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
166
            )),
×
167
            TypedRasterQueryProcessor::I16(p) => QueryProcessorOut(Box::new(
×
168
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
169
            )),
×
170
            TypedRasterQueryProcessor::I32(p) => QueryProcessorOut(Box::new(
×
171
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
172
            )),
×
173
            TypedRasterQueryProcessor::I64(p) => QueryProcessorOut(Box::new(
×
174
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
175
            )),
×
176
            TypedRasterQueryProcessor::F32(p) => QueryProcessorOut(Box::new(
×
177
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
178
            )),
×
179
            TypedRasterQueryProcessor::F64(p) => QueryProcessorOut(Box::new(
×
180
                TemperatureProcessor::new(p, self.result_descriptor.clone(), self.params.clone()),
×
181
            )),
×
182
        })
183
    }
11✔
184

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

189
    fn name(&self) -> &'static str {
×
190
        Temperature::TYPE_NAME
×
191
    }
×
192

193
    fn path(&self) -> WorkflowOperatorPath {
×
194
        self.path.clone()
×
195
    }
×
196

NEW
197
    fn optimize(
×
NEW
198
        &self,
×
NEW
199
        target_resolution: SpatialResolution,
×
NEW
200
    ) -> Result<Box<dyn RasterOperator>, OptimizationError> {
×
201
        Ok(Temperature {
NEW
202
            params: self.params.clone(),
×
203
            sources: SingleRasterSource {
NEW
204
                raster: self.source.optimize(target_resolution)?,
×
205
            },
206
        }
NEW
207
        .boxed())
×
NEW
208
    }
×
209
}
210

211
struct TemperatureProcessor<Q, P>
212
where
213
    Q: RasterQueryProcessor<RasterType = P>,
214
{
215
    source: Q,
216
    result_descriptor: RasterResultDescriptor,
217
    params: TemperatureParams,
218
    satellite_key: RasterPropertiesKey,
219
    channel_key: RasterPropertiesKey,
220
    offset_key: RasterPropertiesKey,
221
    slope_key: RasterPropertiesKey,
222
}
223

224
impl<Q, P> TemperatureProcessor<Q, P>
225
where
226
    Q: RasterQueryProcessor<RasterType = P>,
227
    P: Pixel,
228
{
229
    pub fn new(
11✔
230
        source: Q,
11✔
231
        result_descriptor: RasterResultDescriptor,
11✔
232
        params: TemperatureParams,
11✔
233
    ) -> Self {
11✔
234
        Self {
11✔
235
            source,
11✔
236
            result_descriptor,
11✔
237
            params,
11✔
238
            satellite_key: new_satellite_key(),
11✔
239
            channel_key: new_channel_key(),
11✔
240
            offset_key: new_offset_key(),
11✔
241
            slope_key: new_slope_key(),
11✔
242
        }
11✔
243
    }
11✔
244

245
    fn satellite(&self, tile: &RasterTile2D<P>) -> Result<&'static Satellite> {
11✔
246
        let id = match self.params.force_satellite {
11✔
247
            Some(id) => id,
2✔
248
            _ => tile.properties.number_property(&self.satellite_key)?,
9✔
249
        };
250
        Satellite::satellite_by_msg_id(id)
10✔
251
    }
11✔
252

253
    fn channel<'a>(&self, tile: &RasterTile2D<P>, satellite: &'a Satellite) -> Result<&'a Channel> {
8✔
254
        let channel_id = tile
8✔
255
            .properties
8✔
256
            .number_property::<usize>(&self.channel_key)?
8✔
257
            - 1;
258
        if (3..=10).contains(&channel_id) {
7✔
259
            satellite.channel(channel_id)
6✔
260
        } else {
261
            Err(Error::InvalidChannel {
1✔
262
                channel: channel_id,
1✔
263
            })
1✔
264
        }
265
    }
8✔
266

267
    async fn process_tile_async(
11✔
268
        &self,
11✔
269
        tile: RasterTile2D<P>,
11✔
270
        pool: Arc<ThreadPool>,
11✔
271
    ) -> Result<RasterTile2D<PixelOut>> {
11✔
272
        let satellite = self.satellite(&tile)?;
11✔
273
        let channel = self.channel(&tile, satellite)?;
8✔
274
        let offset = tile.properties.number_property::<f64>(&self.offset_key)?;
6✔
275
        let slope = tile.properties.number_property::<f64>(&self.slope_key)?;
5✔
276

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

280
            let map_fn = move |pixel_option: Option<P>| {
19✔
281
                pixel_option.and_then(|p| {
19✔
282
                    let lut_idx: u64 = p.as_();
15✔
283
                    lut.get(lut_idx as usize).copied()
15✔
284
                })
15✔
285
            };
19✔
286

287
            tile.map_elements_parallel(map_fn)
4✔
288
        })
4✔
289
        .await?;
4✔
290

291
        Ok(temp_tile)
4✔
292
    }
11✔
293
}
294

295
fn create_lookup_table(channel: &Channel, offset: f64, slope: f64, _pool: &ThreadPool) -> Vec<f32> {
4✔
296
    // this should propably be done with SIMD not a threadpool
297
    (0..1024)
4✔
298
        .map(|i| {
4,096✔
299
            let radiance = offset + f64::from(i) * slope;
4,096✔
300
            channel.calculate_temperature_from_radiance(radiance) as f32
4,096✔
301
        })
4,096✔
302
        .collect::<Vec<f32>>()
4✔
303
}
4✔
304

305
#[async_trait]
306
impl<Q, P> QueryProcessor for TemperatureProcessor<Q, P>
307
where
308
    P: Pixel,
309
    Q: RasterQueryProcessor<RasterType = P>,
310
{
311
    type Output = RasterTile2D<PixelOut>;
312
    type ResultDescription = RasterResultDescriptor;
313
    type Selection = BandSelection;
314
    type SpatialBounds = GridBoundingBox2D;
315

316
    async fn _query<'a>(
317
        &'a self,
318
        query: RasterQueryRectangle,
319
        ctx: &'a dyn crate::engine::QueryContext,
320
    ) -> Result<futures::stream::BoxStream<crate::util::Result<Self::Output>>> {
11✔
321
        let src = self
322
            .source
323
            .raster_query(query, ctx)
324
            .await?
325
            .and_then(move |tile| self.process_tile_async(tile, ctx.thread_pool().clone()));
11✔
326
        Ok(src.boxed())
327
    }
11✔
328

329
    fn result_descriptor(&self) -> &Self::ResultDescription {
22✔
330
        &self.result_descriptor
22✔
331
    }
22✔
332
}
333

334
#[async_trait]
335
impl<Q, P> RasterQueryProcessor for TemperatureProcessor<Q, P>
336
where
337
    Q: RasterQueryProcessor<RasterType = P>,
338
    P: Pixel,
339
{
340
    type RasterType = PixelOut;
341

342
    async fn _time_query<'a>(
343
        &'a self,
344
        query: geoengine_datatypes::primitives::TimeInterval,
345
        ctx: &'a dyn crate::engine::QueryContext,
346
    ) -> Result<futures::stream::BoxStream<'a, Result<geoengine_datatypes::primitives::TimeInterval>>>
347
    {
×
348
        self.source.time_query(query, ctx).await
349
    }
×
350
}
351

352
#[cfg(test)]
353
mod tests {
354
    use crate::engine::{MockExecutionContext, RasterOperator, SingleRasterSource};
355
    use crate::processing::meteosat::temperature::{Temperature, TemperatureParams};
356
    use crate::processing::meteosat::test_util;
357
    use geoengine_datatypes::primitives::{
358
        ClassificationMeasurement, ContinuousMeasurement, Measurement,
359
    };
360
    use geoengine_datatypes::raster::{EmptyGrid2D, Grid2D, MaskedGrid2D, TilingSpecification};
361
    use std::collections::BTreeMap;
362

363
    // #[tokio::test]
364
    // async fn test_msg_raster() {
365
    //     let mut ctx = MockExecutionContext::test_default();
366
    //     let src = test_util::_create_gdal_src(&mut ctx);
367
    //
368
    //     let result = test_util::process(
369
    //         move || {
370
    //             RasterOperator::boxed(Temperature {
371
    //                 params: TemperatureParams::default(),
372
    //                 sources: SingleRasterSource {
373
    //                     raster: src.boxed(),
374
    //                 },
375
    //             })
376
    //         },
377
    //         test_util::_create_gdal_query(),
378
    //         &ctx,
379
    //     )
380
    //     .await;
381
    //     assert!(result.as_ref().is_ok());
382
    // }
383

384
    #[tokio::test]
385
    async fn test_empty_ok() {
1✔
386
        let tiling_specification = TilingSpecification::new([3, 2].into());
1✔
387
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
388

389
        let res = test_util::process(
1✔
390
            || {
1✔
391
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
392
                let src = test_util::create_mock_source::<u8>(
1✔
393
                    props,
1✔
394
                    Some(EmptyGrid2D::new([3, 2].into()).into()),
1✔
395
                    None,
1✔
396
                );
397

398
                RasterOperator::boxed(Temperature {
1✔
399
                    params: TemperatureParams::default(),
1✔
400
                    sources: SingleRasterSource {
1✔
401
                        raster: src.boxed(),
1✔
402
                    },
1✔
403
                })
1✔
404
            },
1✔
405
            test_util::create_mock_query(),
1✔
406
            &ctx,
1✔
407
        )
408
        .await
1✔
409
        .unwrap();
1✔
410

411
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
412
            &res.grid_array,
1✔
413
            &EmptyGrid2D::new([3, 2].into()).into()
1✔
414
        ));
1✔
415
    }
1✔
416

417
    #[tokio::test]
418
    async fn test_ok() {
1✔
419
        let tiling_specification = TilingSpecification::new([3, 2].into());
1✔
420
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
421

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

427
                RasterOperator::boxed(Temperature {
1✔
428
                    params: TemperatureParams::default(),
1✔
429
                    sources: SingleRasterSource {
1✔
430
                        raster: src.boxed(),
1✔
431
                    },
1✔
432
                })
1✔
433
            },
1✔
434
            test_util::create_mock_query(),
1✔
435
            &ctx,
1✔
436
        )
437
        .await
1✔
438
        .unwrap();
1✔
439

440
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
441
            &res.grid_array,
1✔
442
            &MaskedGrid2D::new(
1✔
443
                Grid2D::new(
1✔
444
                    [3, 2].into(),
1✔
445
                    vec![
1✔
446
                        300.341_43, 318.617_65, 330.365_14, 339.233_64, 346.443_94, 0.,
1✔
447
                    ],
1✔
448
                )
1✔
449
                .unwrap(),
1✔
450
                Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false,],).unwrap(),
1✔
451
            )
1✔
452
            .unwrap()
1✔
453
            .into()
1✔
454
        ));
1✔
455

456
        // TODO: add assert to check mask
457
    }
1✔
458

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

464
        let res = test_util::process(
1✔
465
            || {
1✔
466
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
467
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
468

469
                RasterOperator::boxed(Temperature {
1✔
470
                    params: TemperatureParams {
1✔
471
                        force_satellite: Some(4),
1✔
472
                    },
1✔
473
                    sources: SingleRasterSource {
1✔
474
                        raster: src.boxed(),
1✔
475
                    },
1✔
476
                })
1✔
477
            },
1✔
478
            test_util::create_mock_query(),
1✔
479
            &ctx,
1✔
480
        )
481
        .await
1✔
482
        .unwrap();
1✔
483

484
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
485
            &res.grid_array,
1✔
486
            &MaskedGrid2D::new(
1✔
487
                Grid2D::new(
1✔
488
                    [3, 2].into(),
1✔
489
                    vec![300.9428, 319.250_15, 331.019_04, 339.9044, 347.128_78, 0.],
1✔
490
                )
1✔
491
                .unwrap(),
1✔
492
                Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false,],).unwrap(),
1✔
493
            )
1✔
494
            .unwrap()
1✔
495
            .into()
1✔
496
        ));
1✔
497
    }
1✔
498

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

504
        let res = test_util::process(
1✔
505
            || {
1✔
506
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
507
                let src = test_util::create_mock_source::<u16>(
1✔
508
                    props,
1✔
509
                    Some(
1✔
510
                        MaskedGrid2D::new(
1✔
511
                            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 1024, 0]).unwrap(),
1✔
512
                            Grid2D::new([3, 2].into(), vec![true, true, true, true, true, false])
1✔
513
                                .unwrap(),
1✔
514
                        )
1✔
515
                        .unwrap()
1✔
516
                        .into(),
1✔
517
                    ),
1✔
518
                    None,
1✔
519
                );
520

521
                RasterOperator::boxed(Temperature {
1✔
522
                    params: TemperatureParams::default(),
1✔
523
                    sources: SingleRasterSource {
1✔
524
                        raster: src.boxed(),
1✔
525
                    },
1✔
526
                })
1✔
527
            },
1✔
528
            test_util::create_mock_query(),
1✔
529
            &ctx,
1✔
530
        )
531
        .await;
1✔
532
        assert!(res.is_ok());
1✔
533
        let res = res.unwrap();
1✔
534
        assert!(geoengine_datatypes::util::test::grid_or_empty_grid_eq(
1✔
535
            &res.grid_array,
1✔
536
            &MaskedGrid2D::new(
1✔
537
                Grid2D::new(
1✔
538
                    [3, 2].into(),
1✔
539
                    vec![300.341_43, 318.617_65, 330.365_14, 339.233_64, 0., 0.],
1✔
540
                )
1✔
541
                .unwrap(),
1✔
542
                Grid2D::new([3, 2].into(), vec![true, true, true, true, false, false,],).unwrap(),
1✔
543
            )
1✔
544
            .unwrap()
1✔
545
            .into()
1✔
546
        ));
1✔
547
    }
1✔
548

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

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

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

575
    #[tokio::test]
576
    async fn test_missing_satellite() {
1✔
577
        let tiling_specification = TilingSpecification::new([3, 2].into());
1✔
578
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
579

580
        let res = test_util::process(
1✔
581
            || {
1✔
582
                let props = test_util::create_properties(Some(4), None, Some(0.0), Some(1.0));
1✔
583
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
584

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

599
    #[tokio::test]
600
    async fn test_invalid_satellite() {
1✔
601
        let tiling_specification = TilingSpecification::new([3, 2].into());
1✔
602
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
603

604
        let res = test_util::process(
1✔
605
            || {
1✔
606
                let props = test_util::create_properties(Some(4), Some(42), Some(0.0), Some(1.0));
1✔
607
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
608

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

623
    #[tokio::test]
624
    async fn test_missing_channel() {
1✔
625
        let tiling_specification = TilingSpecification::new([3, 2].into());
1✔
626
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
627

628
        let res = test_util::process(
1✔
629
            || {
1✔
630
                let props = test_util::create_properties(None, Some(1), Some(0.0), Some(1.0));
1✔
631
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
632

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

647
    #[tokio::test]
648
    async fn test_invalid_channel() {
1✔
649
        let tiling_specification = TilingSpecification::new([3, 2].into());
1✔
650
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
651

652
        let res = test_util::process(
1✔
653
            || {
1✔
654
                let props = test_util::create_properties(Some(1), Some(1), Some(0.0), Some(1.0));
1✔
655
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
656

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

671
    #[tokio::test]
672
    async fn test_missing_slope() {
1✔
673
        let tiling_specification = TilingSpecification::new([3, 2].into());
1✔
674
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
675

676
        let res = test_util::process(
1✔
677
            || {
1✔
678
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), None);
1✔
679
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
680

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

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

700
        let res = test_util::process(
1✔
701
            || {
1✔
702
                let props = test_util::create_properties(Some(4), Some(1), None, Some(1.0));
1✔
703
                let src = test_util::create_mock_source::<u8>(props, None, None);
1✔
704

705
                RasterOperator::boxed(Temperature {
1✔
706
                    params: TemperatureParams::default(),
1✔
707
                    sources: SingleRasterSource {
1✔
708
                        raster: src.boxed(),
1✔
709
                    },
1✔
710
                })
1✔
711
            },
1✔
712
            test_util::create_mock_query(),
1✔
713
            &ctx,
1✔
714
        )
715
        .await;
1✔
716
        assert!(res.is_err());
1✔
717
    }
1✔
718

719
    #[tokio::test]
720
    async fn test_invalid_measurement_unitless() {
1✔
721
        let tiling_specification = TilingSpecification::new([3, 2].into());
1✔
722
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
723

724
        let res = test_util::process(
1✔
725
            || {
1✔
726
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
727
                let src =
1✔
728
                    test_util::create_mock_source::<u8>(props, None, Some(Measurement::Unitless));
1✔
729

730
                RasterOperator::boxed(Temperature {
1✔
731
                    params: TemperatureParams::default(),
1✔
732
                    sources: SingleRasterSource {
1✔
733
                        raster: src.boxed(),
1✔
734
                    },
1✔
735
                })
1✔
736
            },
1✔
737
            test_util::create_mock_query(),
1✔
738
            &ctx,
1✔
739
        )
740
        .await;
1✔
741
        assert!(res.is_err());
1✔
742
    }
1✔
743

744
    #[tokio::test]
745
    async fn test_invalid_measurement_continuous() {
1✔
746
        let tiling_specification = TilingSpecification::new([3, 2].into());
1✔
747
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
748

749
        let res = test_util::process(
1✔
750
            || {
1✔
751
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
752
                let src = test_util::create_mock_source::<u8>(
1✔
753
                    props,
1✔
754
                    None,
1✔
755
                    Some(Measurement::Continuous(ContinuousMeasurement {
1✔
756
                        measurement: "invalid".into(),
1✔
757
                        unit: None,
1✔
758
                    })),
1✔
759
                );
760

761
                RasterOperator::boxed(Temperature {
1✔
762
                    params: TemperatureParams::default(),
1✔
763
                    sources: SingleRasterSource {
1✔
764
                        raster: src.boxed(),
1✔
765
                    },
1✔
766
                })
1✔
767
            },
1✔
768
            test_util::create_mock_query(),
1✔
769
            &ctx,
1✔
770
        )
771
        .await;
1✔
772

773
        assert!(res.is_err());
1✔
774
    }
1✔
775

776
    #[tokio::test]
777
    async fn test_invalid_measurement_classification() {
1✔
778
        let tiling_specification = TilingSpecification::new([3, 2].into());
1✔
779

780
        let ctx = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
781

782
        let res = test_util::process(
1✔
783
            || {
1✔
784
                let props = test_util::create_properties(Some(4), Some(1), Some(0.0), Some(1.0));
1✔
785
                let src = test_util::create_mock_source::<u8>(
1✔
786
                    props,
1✔
787
                    None,
1✔
788
                    Some(Measurement::Classification(ClassificationMeasurement {
1✔
789
                        measurement: "invalid".into(),
1✔
790
                        classes: BTreeMap::new(),
1✔
791
                    })),
1✔
792
                );
793

794
                RasterOperator::boxed(Temperature {
1✔
795
                    params: TemperatureParams::default(),
1✔
796
                    sources: SingleRasterSource {
1✔
797
                        raster: src.boxed(),
1✔
798
                    },
1✔
799
                })
1✔
800
            },
1✔
801
            test_util::create_mock_query(),
1✔
802
            &ctx,
1✔
803
        )
804
        .await;
1✔
805
        assert!(res.is_err());
1✔
806
    }
1✔
807
}
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