• 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

94.74
/operators/src/plot/temporal_raster_mean_plot.rs
1
use crate::engine::{
2
    CanonicOperatorName, ExecutionContext, InitializedPlotOperator, InitializedRasterOperator,
3
    InitializedSources, Operator, OperatorName, PlotOperator, PlotQueryProcessor,
4
    PlotResultDescriptor, QueryContext, QueryProcessor, RasterQueryProcessor, SingleRasterSource,
5
    TypedPlotQueryProcessor, WorkflowOperatorPath,
6
};
7
use crate::util::math::average_floor;
8
use crate::util::Result;
9
use async_trait::async_trait;
10
use futures::stream::BoxStream;
11
use futures::StreamExt;
12
use geoengine_datatypes::plots::{AreaLineChart, Plot, PlotData};
13
use geoengine_datatypes::primitives::{
14
    Measurement, TimeInstance, TimeInterval, VectorQueryRectangle,
15
};
16
use geoengine_datatypes::raster::{Pixel, RasterTile2D};
17
use serde::{Deserialize, Serialize};
18
use std::collections::BTreeMap;
19

20
pub const MEAN_RASTER_PIXEL_VALUES_OVER_TIME_NAME: &str = "Mean Raster Pixel Values over Time";
21

22
/// A plot that shows the mean values of rasters over time as an area plot.
23
pub type MeanRasterPixelValuesOverTime =
24
    Operator<MeanRasterPixelValuesOverTimeParams, SingleRasterSource>;
25

26
impl OperatorName for MeanRasterPixelValuesOverTime {
27
    const TYPE_NAME: &'static str = "MeanRasterPixelValuesOverTime";
28
}
29

30
/// The parameter spec for `MeanRasterPixelValuesOverTime`
31
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10✔
32
#[serde(rename_all = "camelCase")]
33
pub struct MeanRasterPixelValuesOverTimeParams {
34
    /// Where should the x-axis (time) tick be positioned?
35
    /// At either time start, time end or in the center.
36
    pub time_position: MeanRasterPixelValuesOverTimePosition,
37

38
    /// Whether to fill the area under the curve.
39
    #[serde(default = "default_true")]
40
    pub area: bool,
41
}
42

43
const fn default_true() -> bool {
×
44
    true
×
45
}
×
46

47
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4✔
48
#[serde(rename_all = "camelCase")]
49
pub enum MeanRasterPixelValuesOverTimePosition {
50
    Start,
51
    Center,
52
    End,
53
}
54

55
#[typetag::serde]
1✔
56
#[async_trait]
57
impl PlotOperator for MeanRasterPixelValuesOverTime {
58
    async fn _initialize(
3✔
59
        self: Box<Self>,
3✔
60
        path: WorkflowOperatorPath,
3✔
61
        context: &dyn ExecutionContext,
3✔
62
    ) -> Result<Box<dyn InitializedPlotOperator>> {
3✔
63
        let name = CanonicOperatorName::from(&self);
3✔
64

65
        let initalized_sources = self.sources.initialize_sources(path, context).await?;
3✔
66
        let raster = initalized_sources.raster;
3✔
67

3✔
68
        let in_desc = raster.result_descriptor().clone();
3✔
69

3✔
70
        let initialized_operator = InitializedMeanRasterPixelValuesOverTime {
3✔
71
            name,
3✔
72
            result_descriptor: in_desc.into(),
3✔
73
            raster,
3✔
74
            state: self.params,
3✔
75
        };
3✔
76

3✔
77
        Ok(initialized_operator.boxed())
3✔
78
    }
6✔
79

80
    span_fn!(MeanRasterPixelValuesOverTime);
×
81
}
82

83
/// The initialization of `MeanRasterPixelValuesOverTime`
84
pub struct InitializedMeanRasterPixelValuesOverTime {
85
    name: CanonicOperatorName,
86
    result_descriptor: PlotResultDescriptor,
87
    raster: Box<dyn InitializedRasterOperator>,
88
    state: MeanRasterPixelValuesOverTimeParams,
89
}
90

91
impl InitializedPlotOperator for InitializedMeanRasterPixelValuesOverTime {
92
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
3✔
93
        let input_processor = self.raster.query_processor()?;
3✔
94
        let time_position = self.state.time_position;
3✔
95
        let measurement = self.raster.result_descriptor().measurement.clone();
3✔
96
        let draw_area = self.state.area;
3✔
97

98
        let processor = call_on_generic_raster_processor!(input_processor, raster => {
3✔
99
            MeanRasterPixelValuesOverTimeQueryProcessor { raster, time_position, measurement, draw_area }.boxed()
3✔
100
        });
101

102
        Ok(TypedPlotQueryProcessor::JsonVega(processor))
3✔
103
    }
3✔
104

105
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
106
        &self.result_descriptor
×
107
    }
×
108

109
    fn canonic_name(&self) -> CanonicOperatorName {
×
110
        self.name.clone()
×
111
    }
×
112
}
113

114
/// A query processor that calculates the `TemporalRasterMeanPlot` about its input.
115
pub struct MeanRasterPixelValuesOverTimeQueryProcessor<P: Pixel> {
116
    raster: Box<dyn RasterQueryProcessor<RasterType = P>>,
117
    time_position: MeanRasterPixelValuesOverTimePosition,
118
    measurement: Measurement,
119
    draw_area: bool,
120
}
121

122
#[async_trait]
123
impl<P: Pixel> PlotQueryProcessor for MeanRasterPixelValuesOverTimeQueryProcessor<P> {
124
    type OutputFormat = PlotData;
125

126
    fn plot_type(&self) -> &'static str {
×
127
        MEAN_RASTER_PIXEL_VALUES_OVER_TIME_NAME
×
128
    }
×
129

130
    async fn plot_query<'a>(
3✔
131
        &'a self,
3✔
132
        query: VectorQueryRectangle,
3✔
133
        ctx: &'a dyn QueryContext,
3✔
134
    ) -> Result<Self::OutputFormat> {
3✔
135
        let means = Self::calculate_means(
3✔
136
            self.raster.query(query.into(), ctx).await?,
3✔
137
            self.time_position,
3✔
138
        )
139
        .await?;
90✔
140

141
        let plot = Self::generate_plot(means, self.measurement.clone(), self.draw_area)?;
3✔
142

143
        let plot_data = plot.to_vega_embeddable(false)?;
3✔
144

145
        Ok(plot_data)
3✔
146
    }
6✔
147
}
148

149
impl<P: Pixel> MeanRasterPixelValuesOverTimeQueryProcessor<P> {
150
    async fn calculate_means(
3✔
151
        mut tile_stream: BoxStream<'_, Result<RasterTile2D<P>>>,
3✔
152
        position: MeanRasterPixelValuesOverTimePosition,
3✔
153
    ) -> Result<BTreeMap<TimeInstance, MeanCalculator>> {
3✔
154
        let mut means: BTreeMap<TimeInstance, MeanCalculator> = BTreeMap::new();
3✔
155

156
        while let Some(tile) = tile_stream.next().await {
44,187✔
157
            let tile = tile?;
44,184✔
158

159
            match tile.grid_array {
44,184✔
160
                geoengine_datatypes::raster::GridOrEmpty::Grid(g) => {
20✔
161
                    let time = Self::time_interval_projection(tile.time, position);
20✔
162
                    let mean = means.entry(time).or_default();
20✔
163
                    mean.add(g.masked_element_deref_iterator());
20✔
164
                }
20✔
165
                geoengine_datatypes::raster::GridOrEmpty::Empty(_) => (),
44,164✔
166
            }
167
        }
168

169
        Ok(means)
3✔
170
    }
3✔
171

172
    #[inline]
173
    fn time_interval_projection(
20✔
174
        time_interval: TimeInterval,
20✔
175
        time_position: MeanRasterPixelValuesOverTimePosition,
20✔
176
    ) -> TimeInstance {
20✔
177
        match time_position {
20✔
178
            MeanRasterPixelValuesOverTimePosition::Start => time_interval.start(),
19✔
179
            MeanRasterPixelValuesOverTimePosition::Center => TimeInstance::from_millis_unchecked(
1✔
180
                average_floor(time_interval.start().inner(), time_interval.end().inner()),
1✔
181
            ),
1✔
182
            MeanRasterPixelValuesOverTimePosition::End => time_interval.end(),
×
183
        }
184
    }
20✔
185

186
    fn generate_plot(
3✔
187
        means: BTreeMap<TimeInstance, MeanCalculator>,
3✔
188
        measurement: Measurement,
3✔
189
        draw_area: bool,
3✔
190
    ) -> Result<AreaLineChart> {
3✔
191
        let mut timestamps = Vec::with_capacity(means.len());
3✔
192
        let mut values = Vec::with_capacity(means.len());
3✔
193

194
        for (timestamp, mean_calculator) in means {
9✔
195
            timestamps.push(timestamp);
6✔
196
            values.push(mean_calculator.mean());
6✔
197
        }
6✔
198

199
        AreaLineChart::new(timestamps, values, measurement, draw_area).map_err(Into::into)
3✔
200
    }
3✔
201
}
202

203
struct MeanCalculator {
204
    mean: f64,
205
    n: usize,
206
}
207

208
impl Default for MeanCalculator {
209
    fn default() -> Self {
6✔
210
        Self { mean: 0., n: 0 }
6✔
211
    }
6✔
212
}
213

214
impl MeanCalculator {
215
    #[inline]
216
    fn add<P: Pixel, I: Iterator<Item = Option<P>>>(&mut self, values: I) {
20✔
217
        values.flatten().for_each(|value| {
10,024✔
218
            self.add_single_value(value);
10,024✔
219
        });
10,024✔
220
    }
20✔
221

222
    #[inline]
223
    fn add_single_value<P: Pixel>(&mut self, value: P) {
10,024✔
224
        let value: f64 = value.as_();
10,024✔
225

10,024✔
226
        if value.is_nan() {
10,024✔
227
            return;
×
228
        }
10,024✔
229

10,024✔
230
        self.n += 1;
10,024✔
231
        let delta = value - self.mean;
10,024✔
232
        self.mean += delta / (self.n as f64);
10,024✔
233
    }
10,024✔
234

235
    #[inline]
236
    fn mean(&self) -> f64 {
6✔
237
        self.mean
6✔
238
    }
6✔
239
}
240

241
#[cfg(test)]
242
mod tests {
243
    use super::*;
244

245
    use crate::{
246
        engine::{
247
            ChunkByteSize, MockExecutionContext, MockQueryContext, RasterOperator,
248
            RasterResultDescriptor,
249
        },
250
        source::GdalSource,
251
    };
252
    use crate::{
253
        mock::{MockRasterSource, MockRasterSourceParams},
254
        source::GdalSourceParameters,
255
    };
256
    use geoengine_datatypes::{dataset::DatasetId, plots::PlotMetaData, primitives::DateTime};
257
    use geoengine_datatypes::{
258
        primitives::{BoundingBox2D, Measurement, SpatialResolution, TimeInterval},
259
        util::Identifier,
260
    };
261
    use geoengine_datatypes::{raster::TilingSpecification, spatial_reference::SpatialReference};
262
    use geoengine_datatypes::{
263
        raster::{Grid2D, RasterDataType, TileInformation},
264
        util::test::TestDefault,
265
    };
266
    use serde_json::{json, Value};
267

268
    #[test]
1✔
269
    fn serialization() {
1✔
270
        let temporal_raster_mean_plot = MeanRasterPixelValuesOverTime {
1✔
271
            params: MeanRasterPixelValuesOverTimeParams {
1✔
272
                time_position: MeanRasterPixelValuesOverTimePosition::Start,
1✔
273
                area: true,
1✔
274
            },
1✔
275
            sources: SingleRasterSource {
1✔
276
                raster: GdalSource {
1✔
277
                    params: GdalSourceParameters {
1✔
278
                        data: DatasetId::new().into(),
1✔
279
                    },
1✔
280
                }
1✔
281
                .boxed(),
1✔
282
            },
1✔
283
        };
1✔
284

1✔
285
        let serialized = json!({
1✔
286
            "type": "MeanRasterPixelValuesOverTime",
1✔
287
            "params": {
1✔
288
                "timePosition": "start",
1✔
289
                "area": true,
1✔
290
            },
1✔
291
            "sources": {
1✔
292
                "raster": {
1✔
293
                    "type": "GdalSource",
1✔
294
                    "params": {
1✔
295
                        "data": {
1✔
296
                            "type": "internal",
1✔
297
                            "datasetId": "a626c880-1c41-489b-9e19-9596d129859c"
1✔
298
                        }
1✔
299
                    }
1✔
300
                }
1✔
301
            },
1✔
302
            "vectorSources": [],
1✔
303
        })
1✔
304
        .to_string();
1✔
305

1✔
306
        serde_json::from_str::<Box<dyn PlotOperator>>(&serialized).unwrap();
1✔
307

1✔
308
        let deserialized: MeanRasterPixelValuesOverTime =
1✔
309
            serde_json::from_str(&serialized).unwrap();
1✔
310

1✔
311
        assert_eq!(deserialized.params, temporal_raster_mean_plot.params);
1✔
312
    }
1✔
313

314
    #[tokio::test]
1✔
315
    async fn single_raster() {
1✔
316
        let tile_size_in_pixels = [3, 2].into();
1✔
317
        let tiling_specification = TilingSpecification {
1✔
318
            origin_coordinate: [0.0, 0.0].into(),
1✔
319
            tile_size_in_pixels,
1✔
320
        };
1✔
321
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
322

1✔
323
        let temporal_raster_mean_plot = MeanRasterPixelValuesOverTime {
1✔
324
            params: MeanRasterPixelValuesOverTimeParams {
1✔
325
                time_position: MeanRasterPixelValuesOverTimePosition::Center,
1✔
326
                area: true,
1✔
327
            },
1✔
328
            sources: SingleRasterSource {
1✔
329
                raster: generate_mock_raster_source(
1✔
330
                    vec![TimeInterval::new(
1✔
331
                        TimeInstance::from(DateTime::new_utc(1990, 1, 1, 0, 0, 0)),
1✔
332
                        TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0)),
1✔
333
                    )
1✔
334
                    .unwrap()],
1✔
335
                    vec![vec![1, 2, 3, 4, 5, 6]],
1✔
336
                ),
1✔
337
            },
1✔
338
        };
1✔
339

340
        let temporal_raster_mean_plot = temporal_raster_mean_plot
1✔
341
            .boxed()
1✔
342
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
343
            .await
×
344
            .unwrap();
1✔
345

1✔
346
        let processor = temporal_raster_mean_plot
1✔
347
            .query_processor()
1✔
348
            .unwrap()
1✔
349
            .json_vega()
1✔
350
            .unwrap();
1✔
351

352
        let result = processor
1✔
353
            .plot_query(
1✔
354
                VectorQueryRectangle {
1✔
355
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
356
                        .unwrap(),
1✔
357
                    time_interval: TimeInterval::default(),
1✔
358
                    spatial_resolution: SpatialResolution::one(),
1✔
359
                },
1✔
360
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
361
            )
1✔
362
            .await
×
363
            .unwrap();
1✔
364

365
        assert!(matches!(result.metadata, PlotMetaData::None));
1✔
366

367
        let vega_json: Value = serde_json::from_str(&result.vega_string).unwrap();
1✔
368

1✔
369
        assert_eq!(
1✔
370
            vega_json,
1✔
371
            json!({
1✔
372
                "$schema": "https://vega.github.io/schema/vega-lite/v4.17.0.json",
1✔
373
                "data": {
1✔
374
                    "values": [{
1✔
375
                        "x": "1995-01-01T00:00:00+00:00",
1✔
376
                        "y": 3.5
1✔
377
                    }]
1✔
378
                },
1✔
379
                "description": "Area Plot",
1✔
380
                "encoding": {
1✔
381
                    "x": {
1✔
382
                        "field": "x",
1✔
383
                        "title": "Time",
1✔
384
                        "type": "temporal"
1✔
385
                    },
1✔
386
                    "y": {
1✔
387
                        "field": "y",
1✔
388
                        "title": "",
1✔
389
                        "type": "quantitative"
1✔
390
                    }
1✔
391
                },
1✔
392
                "mark": {
1✔
393
                    "type": "area",
1✔
394
                    "line": true,
1✔
395
                    "point": true
1✔
396
                }
1✔
397
            })
1✔
398
        );
1✔
399
    }
400

401
    fn generate_mock_raster_source(
2✔
402
        time_intervals: Vec<TimeInterval>,
2✔
403
        values_vec: Vec<Vec<u8>>,
2✔
404
    ) -> Box<dyn RasterOperator> {
2✔
405
        assert_eq!(time_intervals.len(), values_vec.len());
2✔
406
        assert!(values_vec.iter().all(|v| v.len() == 6));
4✔
407

408
        let mut tiles = Vec::with_capacity(time_intervals.len());
2✔
409
        for (time_interval, values) in time_intervals.into_iter().zip(values_vec) {
4✔
410
            tiles.push(RasterTile2D::new_with_tile_info(
4✔
411
                time_interval,
4✔
412
                TileInformation {
4✔
413
                    global_geo_transform: TestDefault::test_default(),
4✔
414
                    global_tile_position: [0, 0].into(),
4✔
415
                    tile_size_in_pixels: [3, 2].into(),
4✔
416
                },
4✔
417
                Grid2D::new([3, 2].into(), values).unwrap().into(),
4✔
418
            ));
4✔
419
        }
4✔
420

421
        MockRasterSource {
2✔
422
            params: MockRasterSourceParams {
2✔
423
                data: tiles,
2✔
424
                result_descriptor: RasterResultDescriptor {
2✔
425
                    data_type: RasterDataType::U8,
2✔
426
                    spatial_reference: SpatialReference::epsg_4326().into(),
2✔
427
                    measurement: Measurement::Unitless,
2✔
428
                    time: None,
2✔
429
                    bbox: None,
2✔
430
                    resolution: None,
2✔
431
                },
2✔
432
            },
2✔
433
        }
2✔
434
        .boxed()
2✔
435
    }
2✔
436

437
    #[tokio::test]
1✔
438
    async fn raster_series() {
1✔
439
        let tile_size_in_pixels = [3, 2].into();
1✔
440
        let tiling_specification = TilingSpecification {
1✔
441
            origin_coordinate: [0.0, 0.0].into(),
1✔
442
            tile_size_in_pixels,
1✔
443
        };
1✔
444
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
445

1✔
446
        let temporal_raster_mean_plot = MeanRasterPixelValuesOverTime {
1✔
447
            params: MeanRasterPixelValuesOverTimeParams {
1✔
448
                time_position: MeanRasterPixelValuesOverTimePosition::Start,
1✔
449
                area: true,
1✔
450
            },
1✔
451

1✔
452
            sources: SingleRasterSource {
1✔
453
                raster: generate_mock_raster_source(
1✔
454
                    vec![
1✔
455
                        TimeInterval::new(
1✔
456
                            TimeInstance::from(DateTime::new_utc(1990, 1, 1, 0, 0, 0)),
1✔
457
                            TimeInstance::from(DateTime::new_utc(1995, 1, 1, 0, 0, 0)),
1✔
458
                        )
1✔
459
                        .unwrap(),
1✔
460
                        TimeInterval::new(
1✔
461
                            TimeInstance::from(DateTime::new_utc(1995, 1, 1, 0, 0, 0)),
1✔
462
                            TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0)),
1✔
463
                        )
1✔
464
                        .unwrap(),
1✔
465
                        TimeInterval::new(
1✔
466
                            TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0)),
1✔
467
                            TimeInstance::from(DateTime::new_utc(2005, 1, 1, 0, 0, 0)),
1✔
468
                        )
1✔
469
                        .unwrap(),
1✔
470
                    ],
1✔
471
                    vec![
1✔
472
                        vec![1, 2, 3, 4, 5, 6],
1✔
473
                        vec![9, 9, 8, 8, 8, 9],
1✔
474
                        vec![3, 4, 5, 6, 7, 8],
1✔
475
                    ],
1✔
476
                ),
1✔
477
            },
1✔
478
        };
1✔
479

480
        let temporal_raster_mean_plot = temporal_raster_mean_plot
1✔
481
            .boxed()
1✔
482
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
483
            .await
×
484
            .unwrap();
1✔
485

1✔
486
        let processor = temporal_raster_mean_plot
1✔
487
            .query_processor()
1✔
488
            .unwrap()
1✔
489
            .json_vega()
1✔
490
            .unwrap();
1✔
491

492
        let result = processor
1✔
493
            .plot_query(
1✔
494
                VectorQueryRectangle {
1✔
495
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
496
                        .unwrap(),
1✔
497
                    time_interval: TimeInterval::default(),
1✔
498
                    spatial_resolution: SpatialResolution::one(),
1✔
499
                },
1✔
500
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
501
            )
1✔
502
            .await
×
503
            .unwrap();
1✔
504

1✔
505
        assert_eq!(
1✔
506
            result,
1✔
507
            AreaLineChart::new(
1✔
508
                vec![
1✔
509
                    TimeInstance::from(DateTime::new_utc(1990, 1, 1, 0, 0, 0)),
1✔
510
                    TimeInstance::from(DateTime::new_utc(1995, 1, 1, 0, 0, 0)),
1✔
511
                    TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0))
1✔
512
                ],
1✔
513
                vec![3.5, 8.5, 5.5],
1✔
514
                Measurement::Unitless,
1✔
515
                true,
1✔
516
            )
1✔
517
            .unwrap()
1✔
518
            .to_vega_embeddable(false)
1✔
519
            .unwrap()
1✔
520
        );
1✔
521
    }
522
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc