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

geo-engine / geoengine / 18554766227

16 Oct 2025 08:12AM UTC coverage: 88.843% (+0.3%) from 88.543%
18554766227

push

github

web-flow
build: update dependencies (#1081)

* update sqlfluff

* clippy autofix

* manual clippy fixes

* removal of unused code

* update deps

* upgrade packages

* enable cargo lints

* make sqlfluff happy

* fix chrono parsin error

* clippy

* byte_size

* fix image cmp with tiffs

* remove debug

177 of 205 new or added lines in 38 files covered. (86.34%)

41 existing lines in 20 files now uncovered.

106415 of 119779 relevant lines covered (88.84%)

84190.21 hits per line

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

97.64
/operators/src/plot/box_plot.rs
1
use async_trait::async_trait;
2
use futures::StreamExt;
3
use geoengine_datatypes::primitives::{
4
    AxisAlignedRectangle, BandSelection, BoundingBox2D, PlotQueryRectangle, RasterQueryRectangle,
5
    partitions_extent, time_interval_extent,
6
};
7
use num_traits::AsPrimitive;
8
use serde::{Deserialize, Serialize};
9

10
use geoengine_datatypes::collections::FeatureCollectionInfos;
11
use geoengine_datatypes::plots::{BoxPlotAttribute, Plot, PlotData};
12
use geoengine_datatypes::raster::GridOrEmpty;
13

14
use crate::engine::{
15
    CanonicOperatorName, ExecutionContext, InitializedPlotOperator, InitializedRasterOperator,
16
    InitializedVectorOperator, MultipleRasterOrSingleVectorSource, Operator, OperatorName,
17
    PlotOperator, PlotQueryProcessor, PlotResultDescriptor, QueryContext, QueryProcessor,
18
    TypedPlotQueryProcessor, TypedRasterQueryProcessor, TypedVectorQueryProcessor,
19
    WorkflowOperatorPath,
20
};
21
use crate::error::{self, Error};
22
use crate::util::Result;
23
use crate::util::input::MultiRasterOrVectorOperator;
24
use crate::util::statistics::PSquareQuantileEstimator;
25
use snafu::ensure;
26

27
pub const BOXPLOT_OPERATOR_NAME: &str = "BoxPlot";
28
const EXACT_CALC_BOUND: usize = 10_000;
29
const BATCH_SIZE: usize = 1_000;
30
const MAX_NUMBER_OF_RASTER_INPUTS: usize = 8;
31

32
/// A box plot about vector data attribute values
33
pub type BoxPlot = Operator<BoxPlotParams, MultipleRasterOrSingleVectorSource>;
34

35
impl OperatorName for BoxPlot {
36
    const TYPE_NAME: &'static str = "BoxPlot";
37
}
38

39
/// The parameter spec for `BoxPlot`
40
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41
#[serde(rename_all = "camelCase")]
42
pub struct BoxPlotParams {
43
    /// Name of the (numeric) attributes to compute the box plots on.
44
    #[serde(default)]
45
    pub column_names: Vec<String>,
46
}
47

48
#[typetag::serde]
×
49
#[async_trait]
50
#[allow(clippy::too_many_lines)]
51
impl PlotOperator for BoxPlot {
52
    async fn _initialize(
53
        self: Box<Self>,
54
        path: WorkflowOperatorPath,
55
        context: &dyn ExecutionContext,
56
    ) -> Result<Box<dyn InitializedPlotOperator>> {
14✔
57
        let name = CanonicOperatorName::from(&self);
58

59
        match self.sources.source {
60
            MultiRasterOrVectorOperator::Raster(raster_sources) => {
61
                ensure!(
62
                    (1..=MAX_NUMBER_OF_RASTER_INPUTS).contains(&raster_sources.len()),
63
                    error::InvalidNumberOfRasterInputs {
64
                        expected: 1..MAX_NUMBER_OF_RASTER_INPUTS,
65
                        found: raster_sources.len()
66
                    }
67
                );
68
                ensure!( self.params.column_names.is_empty() || self.params.column_names.len() == raster_sources.len(),
69
                    error::InvalidOperatorSpec {
70
                        reason: "BoxPlot on raster data must either contain a name/alias for every input ('column_names' parameter) or no names at all."
71
                            .to_string(),
72
                });
73

74
                let raster_sources = futures::future::try_join_all(
75
                    raster_sources
76
                        .into_iter()
77
                        .enumerate()
78
                        .map(|(i, op)| op.initialize(path.clone_and_append(i as u8), context)),
9✔
79
                )
80
                .await?;
81

82
                // TODO: implement multi-band functionality and remove this check
83
                ensure!(
84
                    raster_sources
85
                        .iter()
86
                        .all(|s| s.result_descriptor().bands.len() == 1),
9✔
87
                    crate::error::OperatorDoesNotSupportMultiBandsSourcesYet {
88
                        operator: BoxPlot::TYPE_NAME
89
                    }
90
                );
91

92
                let output_names = if self.params.column_names.is_empty() {
93
                    (1..=raster_sources.len())
94
                        .map(|i| format!("Raster-{i}"))
9✔
95
                        .collect::<Vec<_>>()
96
                } else {
97
                    self.params.column_names.clone()
98
                };
99

100
                if raster_sources.len() > 1 {
101
                    let srs = raster_sources[0].result_descriptor().spatial_reference;
102
                    ensure!(
103
                        raster_sources
104
                            .iter()
105
                            .all(|op| op.result_descriptor().spatial_reference == srs),
3✔
106
                        error::AllSourcesMustHaveSameSpatialReference
107
                    );
108
                }
109

110
                let in_descriptors = raster_sources
111
                    .iter()
112
                    .map(InitializedRasterOperator::result_descriptor)
113
                    .collect::<Vec<_>>();
114

115
                let time = time_interval_extent(in_descriptors.iter().map(|d| d.time));
116
                let bbox = partitions_extent(in_descriptors.iter().map(|d| d.bbox));
117

118
                Ok(InitializedBoxPlot::new(
119
                    name,
120
                    PlotResultDescriptor {
121
                        spatial_reference: in_descriptors[0].spatial_reference,
122
                        time,
123
                        // converting `SpatialPartition2D` to `BoundingBox2D` is ok here, because is makes the covered area only larger
124
                        bbox: bbox
UNCOV
125
                            .and_then(|p| BoundingBox2D::new(p.lower_left(), p.upper_right()).ok()),
×
126
                    },
127
                    output_names,
128
                    raster_sources,
129
                )
130
                .boxed())
131
            }
132
            MultiRasterOrVectorOperator::Vector(vector_source) => {
133
                ensure!( !self.params.column_names.is_empty(),
134
                    error::InvalidOperatorSpec {
135
                        reason: "BoxPlot on vector data requires the selection of at least one numeric column ('column_names' parameter)."
136
                            .to_string(),
137
                    }
138
                );
139

140
                let vector_source = vector_source
141
                    .initialize(path.clone_and_append(0), context)
142
                    .await?;
143

144
                for cn in &self.params.column_names {
145
                    match vector_source
146
                        .result_descriptor()
147
                        .column_data_type(cn.as_str())
148
                    {
149
                        Some(column) if !column.is_numeric() => {
150
                            return Err(Error::InvalidOperatorSpec {
151
                                reason: format!("Column '{cn}' is not numeric."),
152
                            });
153
                        }
154
                        Some(_) => {
155
                            // OK
156
                        }
157
                        None => {
158
                            return Err(Error::ColumnDoesNotExist {
159
                                column: cn.to_string(),
160
                            });
161
                        }
162
                    }
163
                }
164

165
                let in_desc = vector_source.result_descriptor();
166

167
                Ok(InitializedBoxPlot::new(
168
                    name,
169
                    PlotResultDescriptor {
170
                        spatial_reference: in_desc.spatial_reference,
171
                        time: in_desc.time,
172
                        bbox: in_desc.bbox,
173
                    },
174
                    self.params.column_names.clone(),
175
                    vector_source,
176
                )
177
                .boxed())
178
            }
179
        }
180
    }
14✔
181

182
    span_fn!(BoxPlot);
183
}
184

185
/// The initialization of `BoxPlot`
186
pub struct InitializedBoxPlot<Op> {
187
    name: CanonicOperatorName,
188
    result_descriptor: PlotResultDescriptor,
189
    names: Vec<String>,
190

191
    source: Op,
192
}
193

194
impl<Op> InitializedBoxPlot<Op> {
195
    pub fn new(
12✔
196
        name: CanonicOperatorName,
12✔
197
        result_descriptor: PlotResultDescriptor,
12✔
198
        names: Vec<String>,
12✔
199
        source: Op,
12✔
200
    ) -> Self {
12✔
201
        Self {
12✔
202
            name,
12✔
203
            result_descriptor,
12✔
204
            names,
12✔
205
            source,
12✔
206
        }
12✔
207
    }
12✔
208
}
209
impl InitializedPlotOperator for InitializedBoxPlot<Box<dyn InitializedVectorOperator>> {
210
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
211
        &self.result_descriptor
×
212
    }
×
213

214
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
5✔
215
        let processor = BoxPlotVectorQueryProcessor {
5✔
216
            input: self.source.query_processor()?,
5✔
217
            column_names: self.names.clone(),
5✔
218
        };
219

220
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
5✔
221
    }
5✔
222

223
    fn canonic_name(&self) -> CanonicOperatorName {
×
224
        self.name.clone()
×
225
    }
×
226
}
227

228
impl InitializedPlotOperator for InitializedBoxPlot<Vec<Box<dyn InitializedRasterOperator>>> {
229
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
230
        &self.result_descriptor
×
231
    }
×
232

233
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
7✔
234
        let input = self
7✔
235
            .source
7✔
236
            .iter()
7✔
237
            .map(InitializedRasterOperator::query_processor)
7✔
238
            .collect::<Result<Vec<_>>>()?;
7✔
239

240
        let processor = BoxPlotRasterQueryProcessor {
7✔
241
            input,
7✔
242
            names: self.names.clone(),
7✔
243
        };
7✔
244
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
7✔
245
    }
7✔
246

247
    fn canonic_name(&self) -> CanonicOperatorName {
×
248
        self.name.clone()
×
249
    }
×
250
}
251

252
/// A query processor that calculates the boxplots about its vector input.
253
pub struct BoxPlotVectorQueryProcessor {
254
    input: TypedVectorQueryProcessor,
255
    column_names: Vec<String>,
256
}
257

258
#[async_trait]
259
impl PlotQueryProcessor for BoxPlotVectorQueryProcessor {
260
    type OutputFormat = PlotData;
261

262
    fn plot_type(&self) -> &'static str {
×
263
        BOXPLOT_OPERATOR_NAME
×
264
    }
×
265

266
    async fn plot_query<'p>(
267
        &'p self,
268
        query: PlotQueryRectangle,
269
        ctx: &'p dyn QueryContext,
270
    ) -> Result<Self::OutputFormat> {
5✔
271
        let mut accums: Vec<BoxPlotAccum> = self
272
            .column_names
273
            .iter()
274
            .map(|name| BoxPlotAccum::new(name.clone()))
6✔
275
            .collect();
276

277
        call_on_generic_vector_processor!(&self.input, processor => {
278
            let mut query = processor.query(query.into(), ctx).await?;
279
            while let Some(collection) = query.next().await {
280
                let collection = collection?;
281

282
                for accum in &mut accums {
283
                    let feature_data = collection.data(&accum.name).expect("checked in param");
284
                    let iter = feature_data.float_options_iter().map(|o| match o {
20,032✔
285
                        Some(v) => v,
20,030✔
286
                        None => f64::NAN,
2✔
287
                    });
20,032✔
288
                    accum.update(iter)?;
289
                }
290
            }
291
        });
292

293
        let mut chart = geoengine_datatypes::plots::BoxPlot::new();
294
        for accum in &mut accums {
295
            if let Some(attrib) = accum.finish()? {
296
                chart.add_attribute(attrib);
297
            }
298
        }
299
        Ok(chart.to_vega_embeddable(false)?)
300
    }
5✔
301
}
302

303
/// A query processor that calculates the boxplots about its raster input.
304
pub struct BoxPlotRasterQueryProcessor {
305
    input: Vec<TypedRasterQueryProcessor>,
306
    names: Vec<String>,
307
}
308

309
impl BoxPlotRasterQueryProcessor {
310
    async fn process_raster(
9✔
311
        name: String,
9✔
312
        input: &TypedRasterQueryProcessor,
9✔
313
        query: PlotQueryRectangle,
9✔
314
        ctx: &dyn QueryContext,
9✔
315
    ) -> Result<Option<BoxPlotAttribute>> {
9✔
316
        call_on_generic_raster_processor!(input, processor => {
9✔
317

318

319
            let mut stream = processor.query(RasterQueryRectangle::from_qrect_and_bands(&query, BandSelection::first()), ctx).await?;
2✔
320
            let mut accum = BoxPlotAccum::new(name);
2✔
321

322
            while let Some(tile) = stream.next().await {
22,084✔
323
                let tile = tile?;
22,082✔
324

325
                match tile.grid_array {
22,082✔
326
                    // Ignore empty grids if no_data should not be included
327
                    GridOrEmpty::Empty(_) => {},
22,082✔
328
                    GridOrEmpty::Grid(grid) => {
×
329
                        accum.update(grid.masked_element_deref_iterator().filter_map(|pixel_option| pixel_option.map(|p| { let v: f64 = p.as_(); v})))?;
52✔
330
                    }
331
                }
332
            }
333
            accum.finish()
2✔
334
        })
335
    }
9✔
336
}
337

338
#[async_trait]
339
impl PlotQueryProcessor for BoxPlotRasterQueryProcessor {
340
    type OutputFormat = PlotData;
341

342
    fn plot_type(&self) -> &'static str {
×
343
        BOXPLOT_OPERATOR_NAME
×
344
    }
×
345

346
    async fn plot_query<'p>(
347
        &'p self,
348
        query: PlotQueryRectangle,
349
        ctx: &'p dyn QueryContext,
350
    ) -> Result<Self::OutputFormat> {
7✔
351
        let results: Vec<_> = self
352
            .input
353
            .iter()
354
            .zip(self.names.iter())
355
            .map(|(proc, name)| Self::process_raster(name.clone(), proc, query.clone(), ctx))
9✔
356
            .collect();
357

358
        let results = futures::future::join_all(results)
359
            .await
360
            .into_iter()
361
            .collect::<Result<Vec<_>>>();
362

363
        let mut chart = geoengine_datatypes::plots::BoxPlot::new();
364
        results?
365
            .into_iter()
366
            .flatten()
367
            .for_each(|a| chart.add_attribute(a));
7✔
368
        Ok(chart.to_vega_embeddable(false)?)
369
    }
7✔
370
}
371

372
//
373
// AUX Structures
374
//
375

376
enum BoxPlotAccumKind {
377
    Exact(Vec<f64>),
378
    Estimated(PSquareQuantileEstimator<f64>),
379
}
380

381
impl BoxPlotAccumKind {
382
    fn update(&mut self, values: impl Iterator<Item = f64>) -> crate::util::Result<()> {
33✔
383
        match self {
33✔
384
            Self::Exact(x) => {
24✔
385
                x.extend(values.filter(|x| x.is_finite()));
11,072✔
386

387
                if x.len() > EXACT_CALC_BOUND {
24✔
388
                    let est = PSquareQuantileEstimator::new(0.5, x.as_slice())?;
1✔
389
                    *self = Self::Estimated(est);
1✔
390
                }
23✔
391
                Ok(())
24✔
392
            }
393
            Self::Estimated(est) => {
9✔
394
                for v in values {
9,009✔
395
                    est.update(v);
9,000✔
396
                }
9,000✔
397
                Ok(())
9✔
398
            }
399
        }
400
    }
33✔
401

402
    fn median(values: &[f64]) -> f64 {
30✔
403
        if values.len().is_multiple_of(2) {
30✔
404
            let i = values.len() / 2;
21✔
405
            f64::midpoint(values[i], values[i - 1])
21✔
406
        } else {
407
            values[values.len() / 2]
9✔
408
        }
409
    }
30✔
410

411
    fn split(values: &[f64]) -> (&[f64], &[f64]) {
10✔
412
        let idx = values.len() / 2;
10✔
413

414
        let s = values.split_at(idx);
10✔
415

416
        if values.len().is_multiple_of(2) {
10✔
417
            s
5✔
418
        } else {
419
            (s.0, &s.1[1..])
5✔
420
        }
421
    }
10✔
422

423
    fn create_plot(
15✔
424
        &mut self,
15✔
425
        name: String,
15✔
426
    ) -> Result<Option<geoengine_datatypes::plots::BoxPlotAttribute>> {
15✔
427
        match self {
15✔
428
            Self::Estimated(est) => Ok(Some(BoxPlotAttribute::new(
1✔
429
                name,
1✔
430
                est.min(),
1✔
431
                est.max(),
1✔
432
                est.quantile_estimate(),
1✔
433
                est.marker2(),
1✔
434
                est.marker4(),
1✔
435
                false,
436
            )?)),
×
437
            Self::Exact(v) => {
14✔
438
                match v.len() {
14✔
439
                    0 => Ok(None),
3✔
440
                    1 => {
441
                        let x = v[0];
1✔
442
                        Ok(Some(BoxPlotAttribute::new(name, x, x, x, x, x, true)?))
1✔
443
                    }
444
                    l => {
10✔
445
                        v.sort_unstable_by(|a, b| {
68✔
446
                            a.partial_cmp(b).expect("Infinite values were filtered")
68✔
447
                        });
68✔
448
                        let min = v[0];
10✔
449
                        let max = v[l - 1];
10✔
450
                        // We compute the quartiles accodring to https://en.wikipedia.org/wiki/Quartile#Method_1
451
                        let median = Self::median(v);
10✔
452
                        let (low, high) = Self::split(v);
10✔
453
                        let q1 = Self::median(low);
10✔
454
                        let q3 = Self::median(high);
10✔
455
                        Ok(Some(BoxPlotAttribute::new(
10✔
456
                            name, min, max, median, q1, q3, true,
10✔
457
                        )?))
×
458
                    }
459
                }
460
            }
461
        }
462
    }
15✔
463
}
464

465
struct BoxPlotAccum {
466
    name: String,
467
    accum: BoxPlotAccumKind,
468
}
469

470
impl BoxPlotAccum {
471
    fn new(name: String) -> BoxPlotAccum {
15✔
472
        BoxPlotAccum {
15✔
473
            name,
15✔
474
            accum: BoxPlotAccumKind::Exact(Vec::new()),
15✔
475
        }
15✔
476
    }
15✔
477

478
    fn update(&mut self, values: impl Iterator<Item = f64>) -> crate::util::Result<()> {
15✔
479
        for chunk in &itertools::Itertools::chunks(values, BATCH_SIZE) {
33✔
480
            self.accum.update(chunk)?;
33✔
481
        }
482
        Ok(())
15✔
483
    }
15✔
484

485
    fn finish(&mut self) -> Result<Option<geoengine_datatypes::plots::BoxPlotAttribute>> {
15✔
486
        self.accum.create_plot(self.name.clone())
15✔
487
    }
15✔
488
}
489

490
#[cfg(test)]
491
mod tests {
492
    use geoengine_datatypes::primitives::{CacheHint, PlotSeriesSelection};
493
    use serde_json::json;
494

495
    use geoengine_datatypes::primitives::{
496
        BoundingBox2D, DateTime, FeatureData, NoGeometry, SpatialResolution, TimeInterval,
497
    };
498
    use geoengine_datatypes::raster::{
499
        EmptyGrid2D, Grid2D, MaskedGrid2D, RasterDataType, RasterTile2D, TileInformation,
500
        TilingSpecification,
501
    };
502
    use geoengine_datatypes::spatial_reference::SpatialReference;
503
    use geoengine_datatypes::util::test::TestDefault;
504
    use geoengine_datatypes::{collections::DataCollection, primitives::MultiPoint};
505

506
    use crate::engine::{
507
        ChunkByteSize, MockExecutionContext, MockQueryContext, RasterBandDescriptors,
508
        RasterOperator, RasterResultDescriptor, VectorOperator,
509
    };
510
    use crate::mock::{MockFeatureCollectionSource, MockRasterSource, MockRasterSourceParams};
511

512
    use super::*;
513

514
    #[test]
515
    fn serialization() {
1✔
516
        let histogram = BoxPlot {
1✔
517
            params: BoxPlotParams {
1✔
518
                column_names: vec!["foobar".to_string()],
1✔
519
            },
1✔
520
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
521
                .boxed()
1✔
522
                .into(),
1✔
523
        };
1✔
524

525
        let serialized = json!({
1✔
526
            "type": "BoxPlot",
1✔
527
            "params": {
1✔
528
                "columnNames": ["foobar"],
1✔
529
            },
530
            "sources": {
1✔
531
                "source": {
1✔
532
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
533
                    "params": {
1✔
534
                        "collections": [],
1✔
535
                        "spatialReference": "EPSG:4326",
1✔
536
                        "measurements": {},
1✔
537
                    }
538
                }
539
            }
540
        })
541
        .to_string();
1✔
542

543
        let deserialized: BoxPlot = serde_json::from_str(&serialized).unwrap();
1✔
544

545
        assert_eq!(deserialized.params, histogram.params);
1✔
546
    }
1✔
547

548
    #[test]
549
    fn serialization_alt() {
1✔
550
        let histogram = BoxPlot {
1✔
551
            params: BoxPlotParams {
1✔
552
                column_names: vec![],
1✔
553
            },
1✔
554
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
555
                .boxed()
1✔
556
                .into(),
1✔
557
        };
1✔
558

559
        let serialized = json!({
1✔
560
            "type": "BoxPlot",
1✔
561
            "params": {
1✔
562
            },
563
            "sources": {
1✔
564
                "source": {
1✔
565
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
566
                    "params": {
1✔
567
                        "collections": [],
1✔
568
                        "spatialReference": "EPSG:4326",
1✔
569
                        "measurements": {},
1✔
570
                    }
571
                }
572
            }
573
        })
574
        .to_string();
1✔
575

576
        let deserialized: BoxPlot = serde_json::from_str(&serialized).unwrap();
1✔
577

578
        assert_eq!(deserialized.params, histogram.params);
1✔
579
    }
1✔
580

581
    #[tokio::test]
582
    async fn vector_data() {
1✔
583
        let vector_source = MockFeatureCollectionSource::multiple(vec![
1✔
584
            DataCollection::from_slices(
1✔
585
                &[] as &[NoGeometry],
1✔
586
                &[TimeInterval::default(); 8],
1✔
587
                &[
1✔
588
                    ("foo", FeatureData::Int(vec![1, 1, 2, 2, 3, 3, 4, 4])),
1✔
589
                    ("bar", FeatureData::Int(vec![1, 1, 2, 2, 3, 3, 4, 4])),
1✔
590
                ],
1✔
591
            )
592
            .unwrap(),
1✔
593
            DataCollection::from_slices(
1✔
594
                &[] as &[NoGeometry],
1✔
595
                &[TimeInterval::default(); 4],
1✔
596
                &[
1✔
597
                    ("foo", FeatureData::Int(vec![5, 6, 7, 8])),
1✔
598
                    ("bar", FeatureData::Int(vec![5, 6, 7, 8])),
1✔
599
                ],
1✔
600
            )
601
            .unwrap(),
1✔
602
        ])
603
        .boxed();
1✔
604

605
        let box_plot = BoxPlot {
1✔
606
            params: BoxPlotParams {
1✔
607
                column_names: vec!["foo".to_string(), "bar".to_string()],
1✔
608
            },
1✔
609
            sources: vector_source.into(),
1✔
610
        };
1✔
611

612
        let execution_context = MockExecutionContext::test_default();
1✔
613

614
        let query_processor = box_plot
1✔
615
            .boxed()
1✔
616
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
617
            .await
1✔
618
            .unwrap()
1✔
619
            .query_processor()
1✔
620
            .unwrap()
1✔
621
            .json_vega()
1✔
622
            .unwrap();
1✔
623

624
        let result = query_processor
1✔
625
            .plot_query(
1✔
626
                PlotQueryRectangle {
1✔
627
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
628
                        .unwrap(),
1✔
629
                    time_interval: TimeInterval::default(),
1✔
630
                    spatial_resolution: SpatialResolution::one(),
1✔
631
                    attributes: PlotSeriesSelection::all(),
1✔
632
                },
1✔
633
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
634
            )
1✔
635
            .await
1✔
636
            .unwrap();
1✔
637

638
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
639
        expected.add_attribute(
1✔
640
            BoxPlotAttribute::new("foo".to_string(), 1.0, 8.0, 3.5, 2.0, 5.5, true).unwrap(),
1✔
641
        );
642
        expected.add_attribute(
1✔
643
            BoxPlotAttribute::new("bar".to_string(), 1.0, 8.0, 3.5, 2.0, 5.5, true).unwrap(),
1✔
644
        );
645

646
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
647
    }
1✔
648

649
    #[tokio::test]
650
    async fn vector_data_with_nulls() {
1✔
651
        let vector_source = MockFeatureCollectionSource::single(
1✔
652
            DataCollection::from_slices(
1✔
653
                &[] as &[NoGeometry],
1✔
654
                &[TimeInterval::default(); 7],
1✔
655
                &[(
1✔
656
                    "foo",
1✔
657
                    FeatureData::NullableFloat(vec![
1✔
658
                        Some(1.),
1✔
659
                        Some(2.),
1✔
660
                        None,
1✔
661
                        Some(4.),
1✔
662
                        None,
1✔
663
                        Some(6.),
1✔
664
                        Some(7.),
1✔
665
                    ]),
1✔
666
                )],
1✔
667
            )
668
            .unwrap(),
1✔
669
        )
670
        .boxed();
1✔
671

672
        let box_plot = BoxPlot {
1✔
673
            params: BoxPlotParams {
1✔
674
                column_names: vec!["foo".to_string()],
1✔
675
            },
1✔
676
            sources: vector_source.into(),
1✔
677
        };
1✔
678

679
        let execution_context = MockExecutionContext::test_default();
1✔
680

681
        let query_processor = box_plot
1✔
682
            .boxed()
1✔
683
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
684
            .await
1✔
685
            .unwrap()
1✔
686
            .query_processor()
1✔
687
            .unwrap()
1✔
688
            .json_vega()
1✔
689
            .unwrap();
1✔
690

691
        let result = query_processor
1✔
692
            .plot_query(
1✔
693
                PlotQueryRectangle {
1✔
694
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
695
                        .unwrap(),
1✔
696
                    time_interval: TimeInterval::default(),
1✔
697
                    spatial_resolution: SpatialResolution::one(),
1✔
698
                    attributes: PlotSeriesSelection::all(),
1✔
699
                },
1✔
700
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
701
            )
1✔
702
            .await
1✔
703
            .unwrap();
1✔
704

705
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
706
        expected.add_attribute(
1✔
707
            BoxPlotAttribute::new("foo".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
708
        );
709

710
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
711
    }
1✔
712

713
    #[tokio::test]
714
    async fn vector_data_text_column() {
1✔
715
        let vector_source = MockFeatureCollectionSource::single(
1✔
716
            DataCollection::from_slices(
1✔
717
                &[] as &[NoGeometry],
1✔
718
                &[TimeInterval::default(); 1],
1✔
719
                &[("foo", FeatureData::Text(vec!["test".to_string()]))],
1✔
720
            )
721
            .unwrap(),
1✔
722
        )
723
        .boxed();
1✔
724

725
        let box_plot = BoxPlot {
1✔
726
            params: BoxPlotParams {
1✔
727
                column_names: vec!["foo".to_string()],
1✔
728
            },
1✔
729
            sources: vector_source.into(),
1✔
730
        };
1✔
731

732
        let execution_context = MockExecutionContext::test_default();
1✔
733

734
        let init = box_plot
1✔
735
            .boxed()
1✔
736
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
737
            .await;
1✔
738

739
        assert!(init.is_err());
1✔
740
    }
1✔
741

742
    #[tokio::test]
743
    async fn vector_data_missing_column() {
1✔
744
        let vector_source = MockFeatureCollectionSource::single(
1✔
745
            DataCollection::from_slices(
1✔
746
                &[] as &[NoGeometry],
1✔
747
                &[TimeInterval::default(); 1],
1✔
748
                &[("foo", FeatureData::Text(vec!["test".to_string()]))],
1✔
749
            )
750
            .unwrap(),
1✔
751
        )
752
        .boxed();
1✔
753

754
        let box_plot = BoxPlot {
1✔
755
            params: BoxPlotParams {
1✔
756
                column_names: vec![],
1✔
757
            },
1✔
758
            sources: vector_source.into(),
1✔
759
        };
1✔
760

761
        let execution_context = MockExecutionContext::test_default();
1✔
762

763
        let init = box_plot
1✔
764
            .boxed()
1✔
765
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
766
            .await;
1✔
767

768
        assert!(init.is_err());
1✔
769
    }
1✔
770

771
    #[tokio::test]
772
    async fn vector_data_single_feature() {
1✔
773
        let vector_source = MockFeatureCollectionSource::multiple(vec![
1✔
774
            DataCollection::from_slices(
1✔
775
                &[] as &[NoGeometry],
1✔
776
                &[TimeInterval::default(); 1],
1✔
777
                &[("foo", FeatureData::Int(vec![1]))],
1✔
778
            )
779
            .unwrap(),
1✔
780
        ])
781
        .boxed();
1✔
782

783
        let box_plot = BoxPlot {
1✔
784
            params: BoxPlotParams {
1✔
785
                column_names: vec!["foo".to_string()],
1✔
786
            },
1✔
787
            sources: vector_source.into(),
1✔
788
        };
1✔
789

790
        let execution_context = MockExecutionContext::test_default();
1✔
791

792
        let query_processor = box_plot
1✔
793
            .boxed()
1✔
794
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
795
            .await
1✔
796
            .unwrap()
1✔
797
            .query_processor()
1✔
798
            .unwrap()
1✔
799
            .json_vega()
1✔
800
            .unwrap();
1✔
801

802
        let result = query_processor
1✔
803
            .plot_query(
1✔
804
                PlotQueryRectangle {
1✔
805
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
806
                        .unwrap(),
1✔
807
                    time_interval: TimeInterval::default(),
1✔
808
                    spatial_resolution: SpatialResolution::one(),
1✔
809
                    attributes: PlotSeriesSelection::all(),
1✔
810
                },
1✔
811
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
812
            )
1✔
813
            .await
1✔
814
            .unwrap();
1✔
815

816
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
817
        expected.add_attribute(
1✔
818
            BoxPlotAttribute::new("foo".to_string(), 1.0, 1.0, 1.0, 1.0, 1.0, true).unwrap(),
1✔
819
        );
820

821
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
822
    }
1✔
823

824
    #[tokio::test]
825
    async fn vector_data_empty() {
1✔
826
        let vector_source = MockFeatureCollectionSource::multiple(vec![
1✔
827
            DataCollection::from_slices(
1✔
828
                &[] as &[NoGeometry],
1✔
829
                &[] as &[TimeInterval],
1✔
830
                &[("foo", FeatureData::Int(vec![]))],
1✔
831
            )
832
            .unwrap(),
1✔
833
        ])
834
        .boxed();
1✔
835

836
        let box_plot = BoxPlot {
1✔
837
            params: BoxPlotParams {
1✔
838
                column_names: vec!["foo".to_string()],
1✔
839
            },
1✔
840
            sources: vector_source.into(),
1✔
841
        };
1✔
842

843
        let execution_context = MockExecutionContext::test_default();
1✔
844

845
        let query_processor = box_plot
1✔
846
            .boxed()
1✔
847
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
848
            .await
1✔
849
            .unwrap()
1✔
850
            .query_processor()
1✔
851
            .unwrap()
1✔
852
            .json_vega()
1✔
853
            .unwrap();
1✔
854

855
        let result = query_processor
1✔
856
            .plot_query(
1✔
857
                PlotQueryRectangle {
1✔
858
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
859
                        .unwrap(),
1✔
860
                    time_interval: TimeInterval::default(),
1✔
861
                    spatial_resolution: SpatialResolution::one(),
1✔
862
                    attributes: PlotSeriesSelection::all(),
1✔
863
                },
1✔
864
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
865
            )
1✔
866
            .await
1✔
867
            .unwrap();
1✔
868

869
        let expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
870
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
871
    }
1✔
872

873
    #[tokio::test]
874
    async fn vector_data_estimator_switch() {
1✔
875
        let mut data = Vec::<i64>::with_capacity(2 * super::EXACT_CALC_BOUND);
1✔
876

877
        for i in 1..=data.capacity() as i64 {
20,000✔
878
            data.push(i);
20,000✔
879
        }
20,000✔
880

881
        let vector_source = MockFeatureCollectionSource::multiple(vec![
1✔
882
            DataCollection::from_slices(
1✔
883
                &[] as &[NoGeometry],
1✔
884
                &[TimeInterval::default(); 2 * super::EXACT_CALC_BOUND],
1✔
885
                &[("foo", FeatureData::Int(data))],
1✔
886
            )
887
            .unwrap(),
1✔
888
        ])
889
        .boxed();
1✔
890

891
        let box_plot = BoxPlot {
1✔
892
            params: BoxPlotParams {
1✔
893
                column_names: vec!["foo".to_string()],
1✔
894
            },
1✔
895
            sources: vector_source.into(),
1✔
896
        };
1✔
897

898
        let execution_context = MockExecutionContext::test_default();
1✔
899

900
        let query_processor = box_plot
1✔
901
            .boxed()
1✔
902
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
903
            .await
1✔
904
            .unwrap()
1✔
905
            .query_processor()
1✔
906
            .unwrap()
1✔
907
            .json_vega()
1✔
908
            .unwrap();
1✔
909

910
        let result = query_processor
1✔
911
            .plot_query(
1✔
912
                PlotQueryRectangle {
1✔
913
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
914
                        .unwrap(),
1✔
915
                    time_interval: TimeInterval::default(),
1✔
916
                    spatial_resolution: SpatialResolution::one(),
1✔
917
                    attributes: PlotSeriesSelection::all(),
1✔
918
                },
1✔
919
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
920
            )
1✔
921
            .await
1✔
922
            .unwrap();
1✔
923

924
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
925
        expected.add_attribute(
1✔
926
            BoxPlotAttribute::new(
1✔
927
                "foo".to_string(),
1✔
928
                1.0,
929
                2.0 * super::EXACT_CALC_BOUND as f64,
1✔
930
                super::EXACT_CALC_BOUND as f64,
1✔
931
                0.5 * super::EXACT_CALC_BOUND as f64,
1✔
932
                1.5 * super::EXACT_CALC_BOUND as f64,
1✔
933
                false,
934
            )
935
            .unwrap(),
1✔
936
        );
937

938
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
939
    }
1✔
940

941
    #[tokio::test]
942
    async fn no_data_raster_exclude_no_data() {
1✔
943
        let tile_size_in_pixels = [3, 2].into();
1✔
944
        let tiling_specification = TilingSpecification {
1✔
945
            origin_coordinate: [0.0, 0.0].into(),
1✔
946
            tile_size_in_pixels,
1✔
947
        };
1✔
948
        let box_plot = BoxPlot {
1✔
949
            params: BoxPlotParams {
1✔
950
                column_names: vec![],
1✔
951
            },
1✔
952
            sources: MockRasterSource {
1✔
953
                params: MockRasterSourceParams {
1✔
954
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
955
                        TimeInterval::default(),
1✔
956
                        TileInformation {
1✔
957
                            global_geo_transform: TestDefault::test_default(),
1✔
958
                            global_tile_position: [0, 0].into(),
1✔
959
                            tile_size_in_pixels,
1✔
960
                        },
1✔
961
                        0,
1✔
962
                        EmptyGrid2D::<u8>::new(tile_size_in_pixels).into(),
1✔
963
                        CacheHint::default(),
1✔
964
                    )],
1✔
965
                    result_descriptor: RasterResultDescriptor {
1✔
966
                        data_type: RasterDataType::U8,
1✔
967
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
968
                        time: None,
1✔
969
                        bbox: None,
1✔
970
                        resolution: None,
1✔
971
                        bands: RasterBandDescriptors::new_single_band(),
1✔
972
                    },
1✔
973
                },
1✔
974
            }
1✔
975
            .boxed()
1✔
976
            .into(),
1✔
977
        };
1✔
978

979
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
980

981
        let query_processor = box_plot
1✔
982
            .boxed()
1✔
983
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
984
            .await
1✔
985
            .unwrap()
1✔
986
            .query_processor()
1✔
987
            .unwrap()
1✔
988
            .json_vega()
1✔
989
            .unwrap();
1✔
990

991
        let result = query_processor
1✔
992
            .plot_query(
1✔
993
                PlotQueryRectangle {
1✔
994
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
995
                        .unwrap(),
1✔
996
                    time_interval: TimeInterval::default(),
1✔
997
                    spatial_resolution: SpatialResolution::one(),
1✔
998
                    attributes: PlotSeriesSelection::all(),
1✔
999
                },
1✔
1000
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1001
            )
1✔
1002
            .await
1✔
1003
            .unwrap();
1✔
1004

1005
        let expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1006

1007
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1008
    }
1✔
1009

1010
    #[tokio::test]
1011
    async fn no_data_raster_include_no_data() {
1✔
1012
        let tile_size_in_pixels = [3, 2].into();
1✔
1013
        let tiling_specification = TilingSpecification {
1✔
1014
            origin_coordinate: [0.0, 0.0].into(),
1✔
1015
            tile_size_in_pixels,
1✔
1016
        };
1✔
1017
        let box_plot = BoxPlot {
1✔
1018
            params: BoxPlotParams {
1✔
1019
                column_names: vec![],
1✔
1020
            },
1✔
1021
            sources: MockRasterSource {
1✔
1022
                params: MockRasterSourceParams {
1✔
1023
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1024
                        TimeInterval::default(),
1✔
1025
                        TileInformation {
1✔
1026
                            global_geo_transform: TestDefault::test_default(),
1✔
1027
                            global_tile_position: [0, 0].into(),
1✔
1028
                            tile_size_in_pixels,
1✔
1029
                        },
1✔
1030
                        0,
1✔
1031
                        Grid2D::new(tile_size_in_pixels, vec![0, 0, 0, 0, 0, 0])
1✔
1032
                            .unwrap()
1✔
1033
                            .into(),
1✔
1034
                        CacheHint::default(),
1✔
1035
                    )],
1✔
1036
                    result_descriptor: RasterResultDescriptor {
1✔
1037
                        data_type: RasterDataType::U8,
1✔
1038
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1039
                        time: None,
1✔
1040
                        bbox: None,
1✔
1041
                        resolution: None,
1✔
1042
                        bands: RasterBandDescriptors::new_single_band(),
1✔
1043
                    },
1✔
1044
                },
1✔
1045
            }
1✔
1046
            .boxed()
1✔
1047
            .into(),
1✔
1048
        };
1✔
1049

1050
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1051

1052
        let query_processor = box_plot
1✔
1053
            .boxed()
1✔
1054
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1055
            .await
1✔
1056
            .unwrap()
1✔
1057
            .query_processor()
1✔
1058
            .unwrap()
1✔
1059
            .json_vega()
1✔
1060
            .unwrap();
1✔
1061

1062
        let result = query_processor
1✔
1063
            .plot_query(
1✔
1064
                PlotQueryRectangle {
1✔
1065
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
1066
                    time_interval: TimeInterval::default(),
1✔
1067
                    spatial_resolution: SpatialResolution::one(),
1✔
1068
                    attributes: PlotSeriesSelection::all(),
1✔
1069
                },
1✔
1070
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1071
            )
1✔
1072
            .await
1✔
1073
            .unwrap();
1✔
1074

1075
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1076
        expected.add_attribute(
1✔
1077
            BoxPlotAttribute::new("Raster-1".to_owned(), 0.0, 0.0, 0.0, 0.0, 0.0, true).unwrap(),
1✔
1078
        );
1079

1080
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1081
    }
1✔
1082

1083
    #[tokio::test]
1084
    async fn empty_tile_raster_exclude_no_data() {
1✔
1085
        let tile_size_in_pixels = [3, 2].into();
1✔
1086
        let tiling_specification = TilingSpecification {
1✔
1087
            origin_coordinate: [0.0, 0.0].into(),
1✔
1088
            tile_size_in_pixels,
1✔
1089
        };
1✔
1090
        let box_plot = BoxPlot {
1✔
1091
            params: BoxPlotParams {
1✔
1092
                column_names: vec![],
1✔
1093
            },
1✔
1094
            sources: MockRasterSource {
1✔
1095
                params: MockRasterSourceParams {
1✔
1096
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1097
                        TimeInterval::default(),
1✔
1098
                        TileInformation {
1✔
1099
                            global_geo_transform: TestDefault::test_default(),
1✔
1100
                            global_tile_position: [0, 0].into(),
1✔
1101
                            tile_size_in_pixels,
1✔
1102
                        },
1✔
1103
                        0,
1✔
1104
                        EmptyGrid2D::<u8>::new(tile_size_in_pixels).into(),
1✔
1105
                        CacheHint::default(),
1✔
1106
                    )],
1✔
1107
                    result_descriptor: RasterResultDescriptor {
1✔
1108
                        data_type: RasterDataType::U8,
1✔
1109
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1110
                        time: None,
1✔
1111
                        bbox: None,
1✔
1112
                        resolution: None,
1✔
1113
                        bands: RasterBandDescriptors::new_single_band(),
1✔
1114
                    },
1✔
1115
                },
1✔
1116
            }
1✔
1117
            .boxed()
1✔
1118
            .into(),
1✔
1119
        };
1✔
1120

1121
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1122

1123
        let query_processor = box_plot
1✔
1124
            .boxed()
1✔
1125
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1126
            .await
1✔
1127
            .unwrap()
1✔
1128
            .query_processor()
1✔
1129
            .unwrap()
1✔
1130
            .json_vega()
1✔
1131
            .unwrap();
1✔
1132

1133
        let result = query_processor
1✔
1134
            .plot_query(
1✔
1135
                PlotQueryRectangle {
1✔
1136
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1137
                        .unwrap(),
1✔
1138
                    time_interval: TimeInterval::default(),
1✔
1139
                    spatial_resolution: SpatialResolution::one(),
1✔
1140
                    attributes: PlotSeriesSelection::all(),
1✔
1141
                },
1✔
1142
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1143
            )
1✔
1144
            .await
1✔
1145
            .unwrap();
1✔
1146

1147
        let expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1148

1149
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1150
    }
1✔
1151

1152
    #[tokio::test]
1153
    async fn single_value_raster_stream() {
1✔
1154
        let tile_size_in_pixels = [3, 2].into();
1✔
1155
        let tiling_specification = TilingSpecification {
1✔
1156
            origin_coordinate: [0.0, 0.0].into(),
1✔
1157
            tile_size_in_pixels,
1✔
1158
        };
1✔
1159
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1160
        let histogram = BoxPlot {
1✔
1161
            params: BoxPlotParams {
1✔
1162
                column_names: vec![],
1✔
1163
            },
1✔
1164
            sources: MockRasterSource {
1✔
1165
                params: MockRasterSourceParams {
1✔
1166
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1167
                        TimeInterval::default(),
1✔
1168
                        TileInformation {
1✔
1169
                            global_geo_transform: TestDefault::test_default(),
1✔
1170
                            global_tile_position: [0, 0].into(),
1✔
1171
                            tile_size_in_pixels,
1✔
1172
                        },
1✔
1173
                        0,
1✔
1174
                        Grid2D::new(tile_size_in_pixels, vec![4; 6]).unwrap().into(),
1✔
1175
                        CacheHint::default(),
1✔
1176
                    )],
1✔
1177
                    result_descriptor: RasterResultDescriptor {
1✔
1178
                        data_type: RasterDataType::U8,
1✔
1179
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1180
                        time: None,
1✔
1181
                        bbox: None,
1✔
1182
                        resolution: None,
1✔
1183
                        bands: RasterBandDescriptors::new_single_band(),
1✔
1184
                    },
1✔
1185
                },
1✔
1186
            }
1✔
1187
            .boxed()
1✔
1188
            .into(),
1✔
1189
        };
1✔
1190

1191
        let query_processor = histogram
1✔
1192
            .boxed()
1✔
1193
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1194
            .await
1✔
1195
            .unwrap()
1✔
1196
            .query_processor()
1✔
1197
            .unwrap()
1✔
1198
            .json_vega()
1✔
1199
            .unwrap();
1✔
1200

1201
        let result = query_processor
1✔
1202
            .plot_query(
1✔
1203
                PlotQueryRectangle {
1✔
1204
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1205
                        .unwrap(),
1✔
1206
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1207
                        2013, 12, 1, 12, 0, 0,
1✔
1208
                    ))
1✔
1209
                    .unwrap(),
1✔
1210
                    spatial_resolution: SpatialResolution::one(),
1✔
1211
                    attributes: PlotSeriesSelection::all(),
1✔
1212
                },
1✔
1213
                &MockQueryContext::test_default(),
1✔
1214
            )
1✔
1215
            .await
1✔
1216
            .unwrap();
1✔
1217

1218
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1219
        expected.add_attribute(
1✔
1220
            BoxPlotAttribute::new("Raster-1".to_owned(), 4.0, 4.0, 4.0, 4.0, 4.0, true).unwrap(),
1✔
1221
        );
1222

1223
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1224
    }
1✔
1225

1226
    #[tokio::test]
1227
    async fn raster_with_no_data_exclude_no_data() {
1✔
1228
        let tile_size_in_pixels = [4, 2].into();
1✔
1229
        let tiling_specification = TilingSpecification {
1✔
1230
            origin_coordinate: [0.0, 0.0].into(),
1✔
1231
            tile_size_in_pixels,
1✔
1232
        };
1✔
1233
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1234

1235
        let histogram = BoxPlot {
1✔
1236
            params: BoxPlotParams {
1✔
1237
                column_names: vec![],
1✔
1238
            },
1✔
1239
            sources: MockRasterSource {
1✔
1240
                params: MockRasterSourceParams {
1✔
1241
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1242
                        TimeInterval::default(),
1✔
1243
                        TileInformation {
1✔
1244
                            global_geo_transform: TestDefault::test_default(),
1✔
1245
                            global_tile_position: [0, 0].into(),
1✔
1246
                            tile_size_in_pixels,
1✔
1247
                        },
1✔
1248
                        0,
1✔
1249
                        MaskedGrid2D::new(
1✔
1250
                            Grid2D::new(tile_size_in_pixels, vec![1, 2, 0, 4, 0, 6, 7, 0]).unwrap(),
1✔
1251
                            Grid2D::new(
1✔
1252
                                tile_size_in_pixels,
1✔
1253
                                vec![true, true, false, true, false, true, true, false],
1✔
1254
                            )
1✔
1255
                            .unwrap(),
1✔
1256
                        )
1✔
1257
                        .unwrap()
1✔
1258
                        .into(),
1✔
1259
                        CacheHint::default(),
1✔
1260
                    )],
1✔
1261
                    result_descriptor: RasterResultDescriptor {
1✔
1262
                        data_type: RasterDataType::U8,
1✔
1263
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1264
                        time: None,
1✔
1265
                        bbox: None,
1✔
1266
                        resolution: None,
1✔
1267
                        bands: RasterBandDescriptors::new_single_band(),
1✔
1268
                    },
1✔
1269
                },
1✔
1270
            }
1✔
1271
            .boxed()
1✔
1272
            .into(),
1✔
1273
        };
1✔
1274

1275
        let query_processor = histogram
1✔
1276
            .boxed()
1✔
1277
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1278
            .await
1✔
1279
            .unwrap()
1✔
1280
            .query_processor()
1✔
1281
            .unwrap()
1✔
1282
            .json_vega()
1✔
1283
            .unwrap();
1✔
1284

1285
        let result = query_processor
1✔
1286
            .plot_query(
1✔
1287
                PlotQueryRectangle {
1✔
1288
                    spatial_bounds: BoundingBox2D::new((0., -4.).into(), (2., 0.).into()).unwrap(),
1✔
1289
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1290
                        2013, 12, 1, 12, 0, 0,
1✔
1291
                    ))
1✔
1292
                    .unwrap(),
1✔
1293
                    spatial_resolution: SpatialResolution::one(),
1✔
1294
                    attributes: PlotSeriesSelection::all(),
1✔
1295
                },
1✔
1296
                &MockQueryContext::test_default(),
1✔
1297
            )
1✔
1298
            .await
1✔
1299
            .unwrap();
1✔
1300

1301
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1302
        expected.add_attribute(
1✔
1303
            BoxPlotAttribute::new("Raster-1".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1304
        );
1305

1306
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1307
    }
1✔
1308

1309
    #[tokio::test]
1310
    async fn raster_with_no_data_include_no_data() {
1✔
1311
        let tile_size_in_pixels = [4, 2].into();
1✔
1312
        let tiling_specification = TilingSpecification {
1✔
1313
            origin_coordinate: [0.0, 0.0].into(),
1✔
1314
            tile_size_in_pixels,
1✔
1315
        };
1✔
1316
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1317

1318
        let histogram = BoxPlot {
1✔
1319
            params: BoxPlotParams {
1✔
1320
                column_names: vec![],
1✔
1321
            },
1✔
1322
            sources: MockRasterSource {
1✔
1323
                params: MockRasterSourceParams {
1✔
1324
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1325
                        TimeInterval::default(),
1✔
1326
                        TileInformation {
1✔
1327
                            global_geo_transform: TestDefault::test_default(),
1✔
1328
                            global_tile_position: [0, 0].into(),
1✔
1329
                            tile_size_in_pixels,
1✔
1330
                        },
1✔
1331
                        0,
1✔
1332
                        Grid2D::new(tile_size_in_pixels, vec![1, 2, 0, 4, 0, 6, 7, 0])
1✔
1333
                            .unwrap()
1✔
1334
                            .into(),
1✔
1335
                        CacheHint::default(),
1✔
1336
                    )],
1✔
1337
                    result_descriptor: RasterResultDescriptor {
1✔
1338
                        data_type: RasterDataType::U8,
1✔
1339
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1340
                        time: None,
1✔
1341
                        bbox: None,
1✔
1342
                        resolution: None,
1✔
1343
                        bands: RasterBandDescriptors::new_single_band(),
1✔
1344
                    },
1✔
1345
                },
1✔
1346
            }
1✔
1347
            .boxed()
1✔
1348
            .into(),
1✔
1349
        };
1✔
1350

1351
        let query_processor = histogram
1✔
1352
            .boxed()
1✔
1353
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1354
            .await
1✔
1355
            .unwrap()
1✔
1356
            .query_processor()
1✔
1357
            .unwrap()
1✔
1358
            .json_vega()
1✔
1359
            .unwrap();
1✔
1360

1361
        let result = query_processor
1✔
1362
            .plot_query(
1✔
1363
                PlotQueryRectangle {
1✔
1364
                    spatial_bounds: BoundingBox2D::new((0., -4.).into(), (2., 0.).into()).unwrap(),
1✔
1365
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1366
                        2013, 12, 1, 12, 0, 0,
1✔
1367
                    ))
1✔
1368
                    .unwrap(),
1✔
1369
                    spatial_resolution: SpatialResolution::one(),
1✔
1370
                    attributes: PlotSeriesSelection::all(),
1✔
1371
                },
1✔
1372
                &MockQueryContext::test_default(),
1✔
1373
            )
1✔
1374
            .await
1✔
1375
            .unwrap();
1✔
1376

1377
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1378
        expected.add_attribute(
1✔
1379
            BoxPlotAttribute::new("Raster-1".to_string(), 0.0, 7.0, 1.5, 0.0, 5.0, true).unwrap(),
1✔
1380
        );
1381

1382
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1383
    }
1✔
1384

1385
    #[tokio::test]
1386
    async fn multiple_rasters_with_no_data_exclude_no_data() {
1✔
1387
        let tile_size_in_pixels = [4, 2].into();
1✔
1388
        let tiling_specification = TilingSpecification {
1✔
1389
            origin_coordinate: [0.0, 0.0].into(),
1✔
1390
            tile_size_in_pixels,
1✔
1391
        };
1✔
1392
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1393

1394
        let src = MockRasterSource {
1✔
1395
            params: MockRasterSourceParams {
1✔
1396
                data: vec![RasterTile2D::new_with_tile_info(
1✔
1397
                    TimeInterval::default(),
1✔
1398
                    TileInformation {
1✔
1399
                        global_geo_transform: TestDefault::test_default(),
1✔
1400
                        global_tile_position: [0, 0].into(),
1✔
1401
                        tile_size_in_pixels,
1✔
1402
                    },
1✔
1403
                    0,
1✔
1404
                    MaskedGrid2D::new(
1✔
1405
                        Grid2D::new(tile_size_in_pixels, vec![1, 2, 0, 4, 0, 6, 7, 0]).unwrap(),
1✔
1406
                        Grid2D::new(
1✔
1407
                            tile_size_in_pixels,
1✔
1408
                            vec![true, true, false, true, false, true, true, false],
1✔
1409
                        )
1✔
1410
                        .unwrap(),
1✔
1411
                    )
1✔
1412
                    .unwrap()
1✔
1413
                    .into(),
1✔
1414
                    CacheHint::default(),
1✔
1415
                )],
1✔
1416
                result_descriptor: RasterResultDescriptor {
1✔
1417
                    data_type: RasterDataType::U8,
1✔
1418
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1419
                    time: None,
1✔
1420
                    bbox: None,
1✔
1421
                    resolution: None,
1✔
1422
                    bands: RasterBandDescriptors::new_single_band(),
1✔
1423
                },
1✔
1424
            },
1✔
1425
        };
1✔
1426

1427
        let histogram = BoxPlot {
1✔
1428
            params: BoxPlotParams {
1✔
1429
                column_names: vec![],
1✔
1430
            },
1✔
1431
            sources: vec![
1✔
1432
                src.clone().boxed(),
1✔
1433
                src.clone().boxed(),
1✔
1434
                src.clone().boxed(),
1✔
1435
            ]
1✔
1436
            .into(),
1✔
1437
        };
1✔
1438

1439
        let query_processor = histogram
1✔
1440
            .boxed()
1✔
1441
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1442
            .await
1✔
1443
            .unwrap()
1✔
1444
            .query_processor()
1✔
1445
            .unwrap()
1✔
1446
            .json_vega()
1✔
1447
            .unwrap();
1✔
1448

1449
        let result = query_processor
1✔
1450
            .plot_query(
1✔
1451
                PlotQueryRectangle {
1✔
1452
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1453
                        .unwrap(),
1✔
1454
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1455
                        2013, 12, 1, 12, 0, 0,
1✔
1456
                    ))
1✔
1457
                    .unwrap(),
1✔
1458
                    spatial_resolution: SpatialResolution::one(),
1✔
1459
                    attributes: PlotSeriesSelection::all(),
1✔
1460
                },
1✔
1461
                &MockQueryContext::test_default(),
1✔
1462
            )
1✔
1463
            .await
1✔
1464
            .unwrap();
1✔
1465

1466
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1467
        expected.add_attribute(
1✔
1468
            BoxPlotAttribute::new("Raster-1".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1469
        );
1470
        expected.add_attribute(
1✔
1471
            BoxPlotAttribute::new("Raster-2".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1472
        );
1473
        expected.add_attribute(
1✔
1474
            BoxPlotAttribute::new("Raster-3".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1475
        );
1476

1477
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1478
    }
1✔
1479
}
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