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

geo-engine / geoengine / 3929938005

pending completion
3929938005

push

github

GitHub
Merge #713

84930 of 96741 relevant lines covered (87.79%)

79640.1 hits per line

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

95.47
/operators/src/plot/temporal_raster_mean_plot.rs
1
use crate::engine::{
2
    CreateSpan, ExecutionContext, InitializedPlotOperator, InitializedRasterOperator, Operator,
3
    OperatorName, PlotOperator, PlotQueryProcessor, PlotResultDescriptor, QueryContext,
4
    QueryProcessor, RasterQueryProcessor, SingleRasterSource, TypedPlotQueryProcessor,
5
};
6
use crate::util::math::average_floor;
7
use crate::util::Result;
8
use async_trait::async_trait;
9
use futures::stream::BoxStream;
10
use futures::StreamExt;
11
use geoengine_datatypes::plots::{AreaLineChart, Plot, PlotData};
12
use geoengine_datatypes::primitives::{
13
    Measurement, TimeInstance, TimeInterval, VectorQueryRectangle,
14
};
15
use geoengine_datatypes::raster::{Pixel, RasterTile2D};
16
use serde::{Deserialize, Serialize};
17
use std::collections::BTreeMap;
18
use tracing::{span, Level};
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
        context: &dyn ExecutionContext,
3✔
61
    ) -> Result<Box<dyn InitializedPlotOperator>> {
3✔
62
        let raster = self.sources.raster.initialize(context).await?;
3✔
63

64
        let in_desc = raster.result_descriptor().clone();
3✔
65

3✔
66
        let initialized_operator = InitializedMeanRasterPixelValuesOverTime {
3✔
67
            result_descriptor: in_desc.into(),
3✔
68
            raster,
3✔
69
            state: self.params,
3✔
70
        };
3✔
71

3✔
72
        Ok(initialized_operator.boxed())
3✔
73
    }
6✔
74

75
    span_fn!(MeanRasterPixelValuesOverTime);
×
76
}
77

78
/// The initialization of `MeanRasterPixelValuesOverTime`
79
pub struct InitializedMeanRasterPixelValuesOverTime {
80
    result_descriptor: PlotResultDescriptor,
81
    raster: Box<dyn InitializedRasterOperator>,
82
    state: MeanRasterPixelValuesOverTimeParams,
83
}
84

85
impl InitializedPlotOperator for InitializedMeanRasterPixelValuesOverTime {
86
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
3✔
87
        let input_processor = self.raster.query_processor()?;
3✔
88
        let time_position = self.state.time_position;
3✔
89
        let measurement = self.raster.result_descriptor().measurement.clone();
3✔
90
        let draw_area = self.state.area;
3✔
91

92
        let processor = call_on_generic_raster_processor!(input_processor, raster => {
3✔
93
            MeanRasterPixelValuesOverTimeQueryProcessor { raster, time_position, measurement, draw_area }.boxed()
3✔
94
        });
95

96
        Ok(TypedPlotQueryProcessor::JsonVega(processor))
3✔
97
    }
3✔
98

99
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
100
        &self.result_descriptor
×
101
    }
×
102
}
103

104
/// A query processor that calculates the `TemporalRasterMeanPlot` about its input.
105
pub struct MeanRasterPixelValuesOverTimeQueryProcessor<P: Pixel> {
106
    raster: Box<dyn RasterQueryProcessor<RasterType = P>>,
107
    time_position: MeanRasterPixelValuesOverTimePosition,
108
    measurement: Measurement,
109
    draw_area: bool,
110
}
111

112
#[async_trait]
113
impl<P: Pixel> PlotQueryProcessor for MeanRasterPixelValuesOverTimeQueryProcessor<P> {
114
    type OutputFormat = PlotData;
115

116
    fn plot_type(&self) -> &'static str {
×
117
        MEAN_RASTER_PIXEL_VALUES_OVER_TIME_NAME
×
118
    }
×
119

120
    async fn plot_query<'a>(
3✔
121
        &'a self,
3✔
122
        query: VectorQueryRectangle,
3✔
123
        ctx: &'a dyn QueryContext,
3✔
124
    ) -> Result<Self::OutputFormat> {
3✔
125
        let means = Self::calculate_means(
3✔
126
            self.raster.query(query.into(), ctx).await?,
3✔
127
            self.time_position,
3✔
128
        )
129
        .await?;
81✔
130

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

133
        let plot_data = plot.to_vega_embeddable(false)?;
3✔
134

135
        Ok(plot_data)
3✔
136
    }
6✔
137
}
138

139
impl<P: Pixel> MeanRasterPixelValuesOverTimeQueryProcessor<P> {
140
    async fn calculate_means(
3✔
141
        mut tile_stream: BoxStream<'_, Result<RasterTile2D<P>>>,
3✔
142
        position: MeanRasterPixelValuesOverTimePosition,
3✔
143
    ) -> Result<BTreeMap<TimeInstance, MeanCalculator>> {
3✔
144
        let mut means: BTreeMap<TimeInstance, MeanCalculator> = BTreeMap::new();
3✔
145

146
        while let Some(tile) = tile_stream.next().await {
43,223✔
147
            let tile = tile?;
43,220✔
148

149
            match tile.grid_array {
43,220✔
150
                geoengine_datatypes::raster::GridOrEmpty::Grid(g) => {
20✔
151
                    let time = Self::time_interval_projection(tile.time, position);
20✔
152
                    let mean = means.entry(time).or_default();
20✔
153
                    mean.add(g.masked_element_deref_iterator());
20✔
154
                }
20✔
155
                geoengine_datatypes::raster::GridOrEmpty::Empty(_) => (),
43,200✔
156
            }
157
        }
158

159
        Ok(means)
3✔
160
    }
3✔
161

162
    #[inline]
163
    fn time_interval_projection(
20✔
164
        time_interval: TimeInterval,
20✔
165
        time_position: MeanRasterPixelValuesOverTimePosition,
20✔
166
    ) -> TimeInstance {
20✔
167
        match time_position {
20✔
168
            MeanRasterPixelValuesOverTimePosition::Start => time_interval.start(),
19✔
169
            MeanRasterPixelValuesOverTimePosition::Center => TimeInstance::from_millis_unchecked(
1✔
170
                average_floor(time_interval.start().inner(), time_interval.end().inner()),
1✔
171
            ),
1✔
172
            MeanRasterPixelValuesOverTimePosition::End => time_interval.end(),
×
173
        }
174
    }
20✔
175

176
    fn generate_plot(
3✔
177
        means: BTreeMap<TimeInstance, MeanCalculator>,
3✔
178
        measurement: Measurement,
3✔
179
        draw_area: bool,
3✔
180
    ) -> Result<AreaLineChart> {
3✔
181
        let mut timestamps = Vec::with_capacity(means.len());
3✔
182
        let mut values = Vec::with_capacity(means.len());
3✔
183

184
        for (timestamp, mean_calculator) in means {
9✔
185
            timestamps.push(timestamp);
6✔
186
            values.push(mean_calculator.mean());
6✔
187
        }
6✔
188

189
        AreaLineChart::new(timestamps, values, measurement, draw_area).map_err(Into::into)
3✔
190
    }
3✔
191
}
192

193
struct MeanCalculator {
194
    mean: f64,
195
    n: usize,
196
}
197

198
impl Default for MeanCalculator {
199
    fn default() -> Self {
6✔
200
        Self { mean: 0., n: 0 }
6✔
201
    }
6✔
202
}
203

204
impl MeanCalculator {
205
    #[inline]
206
    fn add<P: Pixel, I: Iterator<Item = Option<P>>>(&mut self, values: I) {
20✔
207
        values.flatten().for_each(|value| {
10,024✔
208
            self.add_single_value(value);
10,024✔
209
        });
10,024✔
210
    }
20✔
211

212
    #[inline]
213
    fn add_single_value<P: Pixel>(&mut self, value: P) {
10,024✔
214
        let value: f64 = value.as_();
10,024✔
215

10,024✔
216
        if value.is_nan() {
10,024✔
217
            return;
×
218
        }
10,024✔
219

10,024✔
220
        self.n += 1;
10,024✔
221
        let delta = value - self.mean;
10,024✔
222
        self.mean += delta / (self.n as f64);
10,024✔
223
    }
10,024✔
224

225
    #[inline]
226
    fn mean(&self) -> f64 {
6✔
227
        self.mean
6✔
228
    }
6✔
229
}
230

231
#[cfg(test)]
232
mod tests {
233
    use super::*;
234

235
    use crate::{
236
        engine::{
237
            ChunkByteSize, MockExecutionContext, MockQueryContext, RasterOperator,
238
            RasterResultDescriptor,
239
        },
240
        source::GdalSource,
241
    };
242
    use crate::{
243
        mock::{MockRasterSource, MockRasterSourceParams},
244
        source::GdalSourceParameters,
245
    };
246
    use geoengine_datatypes::{dataset::DatasetId, plots::PlotMetaData, primitives::DateTime};
247
    use geoengine_datatypes::{
248
        primitives::{BoundingBox2D, Measurement, SpatialResolution, TimeInterval},
249
        util::Identifier,
250
    };
251
    use geoengine_datatypes::{raster::TilingSpecification, spatial_reference::SpatialReference};
252
    use geoengine_datatypes::{
253
        raster::{Grid2D, RasterDataType, TileInformation},
254
        util::test::TestDefault,
255
    };
256
    use serde_json::{json, Value};
257

258
    #[test]
1✔
259
    fn serialization() {
1✔
260
        let temporal_raster_mean_plot = MeanRasterPixelValuesOverTime {
1✔
261
            params: MeanRasterPixelValuesOverTimeParams {
1✔
262
                time_position: MeanRasterPixelValuesOverTimePosition::Start,
1✔
263
                area: true,
1✔
264
            },
1✔
265
            sources: SingleRasterSource {
1✔
266
                raster: GdalSource {
1✔
267
                    params: GdalSourceParameters {
1✔
268
                        data: DatasetId::new().into(),
1✔
269
                    },
1✔
270
                }
1✔
271
                .boxed(),
1✔
272
            },
1✔
273
        };
1✔
274

1✔
275
        let serialized = json!({
1✔
276
            "type": "MeanRasterPixelValuesOverTime",
1✔
277
            "params": {
1✔
278
                "timePosition": "start",
1✔
279
                "area": true,
1✔
280
            },
1✔
281
            "sources": {
1✔
282
                "raster": {
1✔
283
                    "type": "GdalSource",
1✔
284
                    "params": {
1✔
285
                        "data": {
1✔
286
                            "type": "internal",
1✔
287
                            "datasetId": "a626c880-1c41-489b-9e19-9596d129859c"
1✔
288
                        }
1✔
289
                    }
1✔
290
                }
1✔
291
            },
1✔
292
            "vectorSources": [],
1✔
293
        })
1✔
294
        .to_string();
1✔
295

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

1✔
298
        let deserialized: MeanRasterPixelValuesOverTime =
1✔
299
            serde_json::from_str(&serialized).unwrap();
1✔
300

1✔
301
        assert_eq!(deserialized.params, temporal_raster_mean_plot.params);
1✔
302
    }
1✔
303

304
    #[tokio::test]
1✔
305
    async fn single_raster() {
1✔
306
        let tile_size_in_pixels = [3, 2].into();
1✔
307
        let tiling_specification = TilingSpecification {
1✔
308
            origin_coordinate: [0.0, 0.0].into(),
1✔
309
            tile_size_in_pixels,
1✔
310
        };
1✔
311
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
312

1✔
313
        let temporal_raster_mean_plot = MeanRasterPixelValuesOverTime {
1✔
314
            params: MeanRasterPixelValuesOverTimeParams {
1✔
315
                time_position: MeanRasterPixelValuesOverTimePosition::Center,
1✔
316
                area: true,
1✔
317
            },
1✔
318
            sources: SingleRasterSource {
1✔
319
                raster: generate_mock_raster_source(
1✔
320
                    vec![TimeInterval::new(
1✔
321
                        TimeInstance::from(DateTime::new_utc(1990, 1, 1, 0, 0, 0)),
1✔
322
                        TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0)),
1✔
323
                    )
1✔
324
                    .unwrap()],
1✔
325
                    vec![vec![1, 2, 3, 4, 5, 6]],
1✔
326
                ),
1✔
327
            },
1✔
328
        };
1✔
329

330
        let temporal_raster_mean_plot = temporal_raster_mean_plot
1✔
331
            .boxed()
1✔
332
            .initialize(&execution_context)
1✔
333
            .await
×
334
            .unwrap();
1✔
335

1✔
336
        let processor = temporal_raster_mean_plot
1✔
337
            .query_processor()
1✔
338
            .unwrap()
1✔
339
            .json_vega()
1✔
340
            .unwrap();
1✔
341

342
        let result = processor
1✔
343
            .plot_query(
1✔
344
                VectorQueryRectangle {
1✔
345
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
346
                        .unwrap(),
1✔
347
                    time_interval: TimeInterval::default(),
1✔
348
                    spatial_resolution: SpatialResolution::one(),
1✔
349
                },
1✔
350
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
351
            )
1✔
352
            .await
×
353
            .unwrap();
1✔
354

355
        assert!(matches!(result.metadata, PlotMetaData::None));
1✔
356

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

1✔
359
        assert_eq!(
1✔
360
            vega_json,
1✔
361
            json!({
1✔
362
                "$schema": "https://vega.github.io/schema/vega-lite/v4.17.0.json",
1✔
363
                "data": {
1✔
364
                    "values": [{
1✔
365
                        "x": "1995-01-01T00:00:00+00:00",
1✔
366
                        "y": 3.5
1✔
367
                    }]
1✔
368
                },
1✔
369
                "description": "Area Plot",
1✔
370
                "encoding": {
1✔
371
                    "x": {
1✔
372
                        "field": "x",
1✔
373
                        "title": "Time",
1✔
374
                        "type": "temporal"
1✔
375
                    },
1✔
376
                    "y": {
1✔
377
                        "field": "y",
1✔
378
                        "title": "",
1✔
379
                        "type": "quantitative"
1✔
380
                    }
1✔
381
                },
1✔
382
                "mark": {
1✔
383
                    "type": "area",
1✔
384
                    "line": true,
1✔
385
                    "point": true
1✔
386
                }
1✔
387
            })
1✔
388
        );
1✔
389
    }
390

391
    fn generate_mock_raster_source(
2✔
392
        time_intervals: Vec<TimeInterval>,
2✔
393
        values_vec: Vec<Vec<u8>>,
2✔
394
    ) -> Box<dyn RasterOperator> {
2✔
395
        assert_eq!(time_intervals.len(), values_vec.len());
2✔
396
        assert!(values_vec.iter().all(|v| v.len() == 6));
4✔
397

398
        let mut tiles = Vec::with_capacity(time_intervals.len());
2✔
399
        for (time_interval, values) in time_intervals.into_iter().zip(values_vec) {
4✔
400
            tiles.push(RasterTile2D::new_with_tile_info(
4✔
401
                time_interval,
4✔
402
                TileInformation {
4✔
403
                    global_geo_transform: TestDefault::test_default(),
4✔
404
                    global_tile_position: [0, 0].into(),
4✔
405
                    tile_size_in_pixels: [3, 2].into(),
4✔
406
                },
4✔
407
                Grid2D::new([3, 2].into(), values).unwrap().into(),
4✔
408
            ));
4✔
409
        }
4✔
410

411
        MockRasterSource {
2✔
412
            params: MockRasterSourceParams {
2✔
413
                data: tiles,
2✔
414
                result_descriptor: RasterResultDescriptor {
2✔
415
                    data_type: RasterDataType::U8,
2✔
416
                    spatial_reference: SpatialReference::epsg_4326().into(),
2✔
417
                    measurement: Measurement::Unitless,
2✔
418
                    time: None,
2✔
419
                    bbox: None,
2✔
420
                    resolution: None,
2✔
421
                },
2✔
422
            },
2✔
423
        }
2✔
424
        .boxed()
2✔
425
    }
2✔
426

427
    #[tokio::test]
1✔
428
    async fn raster_series() {
1✔
429
        let tile_size_in_pixels = [3, 2].into();
1✔
430
        let tiling_specification = TilingSpecification {
1✔
431
            origin_coordinate: [0.0, 0.0].into(),
1✔
432
            tile_size_in_pixels,
1✔
433
        };
1✔
434
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
435

1✔
436
        let temporal_raster_mean_plot = MeanRasterPixelValuesOverTime {
1✔
437
            params: MeanRasterPixelValuesOverTimeParams {
1✔
438
                time_position: MeanRasterPixelValuesOverTimePosition::Start,
1✔
439
                area: true,
1✔
440
            },
1✔
441

1✔
442
            sources: SingleRasterSource {
1✔
443
                raster: generate_mock_raster_source(
1✔
444
                    vec![
1✔
445
                        TimeInterval::new(
1✔
446
                            TimeInstance::from(DateTime::new_utc(1990, 1, 1, 0, 0, 0)),
1✔
447
                            TimeInstance::from(DateTime::new_utc(1995, 1, 1, 0, 0, 0)),
1✔
448
                        )
1✔
449
                        .unwrap(),
1✔
450
                        TimeInterval::new(
1✔
451
                            TimeInstance::from(DateTime::new_utc(1995, 1, 1, 0, 0, 0)),
1✔
452
                            TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0)),
1✔
453
                        )
1✔
454
                        .unwrap(),
1✔
455
                        TimeInterval::new(
1✔
456
                            TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0)),
1✔
457
                            TimeInstance::from(DateTime::new_utc(2005, 1, 1, 0, 0, 0)),
1✔
458
                        )
1✔
459
                        .unwrap(),
1✔
460
                    ],
1✔
461
                    vec![
1✔
462
                        vec![1, 2, 3, 4, 5, 6],
1✔
463
                        vec![9, 9, 8, 8, 8, 9],
1✔
464
                        vec![3, 4, 5, 6, 7, 8],
1✔
465
                    ],
1✔
466
                ),
1✔
467
            },
1✔
468
        };
1✔
469

470
        let temporal_raster_mean_plot = temporal_raster_mean_plot
1✔
471
            .boxed()
1✔
472
            .initialize(&execution_context)
1✔
473
            .await
×
474
            .unwrap();
1✔
475

1✔
476
        let processor = temporal_raster_mean_plot
1✔
477
            .query_processor()
1✔
478
            .unwrap()
1✔
479
            .json_vega()
1✔
480
            .unwrap();
1✔
481

482
        let result = processor
1✔
483
            .plot_query(
1✔
484
                VectorQueryRectangle {
1✔
485
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
486
                        .unwrap(),
1✔
487
                    time_interval: TimeInterval::default(),
1✔
488
                    spatial_resolution: SpatialResolution::one(),
1✔
489
                },
1✔
490
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
491
            )
1✔
492
            .await
×
493
            .unwrap();
1✔
494

1✔
495
        assert_eq!(
1✔
496
            result,
1✔
497
            AreaLineChart::new(
1✔
498
                vec![
1✔
499
                    TimeInstance::from(DateTime::new_utc(1990, 1, 1, 0, 0, 0)),
1✔
500
                    TimeInstance::from(DateTime::new_utc(1995, 1, 1, 0, 0, 0)),
1✔
501
                    TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0))
1✔
502
                ],
1✔
503
                vec![3.5, 8.5, 5.5],
1✔
504
                Measurement::Unitless,
1✔
505
                true,
1✔
506
            )
1✔
507
            .unwrap()
1✔
508
            .to_vega_embeddable(false)
1✔
509
            .unwrap()
1✔
510
        );
1✔
511
    }
512
}
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