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

geo-engine / geoengine / 10178074589

31 Jul 2024 09:34AM UTC coverage: 91.068% (+0.4%) from 90.682%
10178074589

push

github

web-flow
Merge pull request #973 from geo-engine/remove-XGB-update-toolchain

Remove-XGB-update-toolchain

81 of 88 new or added lines in 29 files covered. (92.05%)

456 existing lines in 119 files now uncovered.

131088 of 143945 relevant lines covered (91.07%)

53581.03 hits per line

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

97.61
/operators/src/plot/box_plot.rs
1
use async_trait::async_trait;
2
use futures::StreamExt;
3
use geoengine_datatypes::primitives::{
4
    partitions_extent, time_interval_extent, AxisAlignedRectangle, BandSelection, BoundingBox2D,
5
    PlotQueryRectangle, RasterQueryRectangle,
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::input::MultiRasterOrVectorOperator;
23
use crate::util::statistics::PSquareQuantileEstimator;
24
use crate::util::Result;
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)]
3✔
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);
14✔
58

14✔
59
        match self.sources.source {
14✔
60
            MultiRasterOrVectorOperator::Raster(raster_sources) => {
14✔
61
                ensure!(
7✔
62
                    (1..=MAX_NUMBER_OF_RASTER_INPUTS).contains(&raster_sources.len()),
7✔
63
                    error::InvalidNumberOfRasterInputs {
14✔
64
                        expected: 1..MAX_NUMBER_OF_RASTER_INPUTS,
×
65
                        found: raster_sources.len()
×
66
                    }
×
67
                );
14✔
68
                ensure!( self.params.column_names.is_empty() || self.params.column_names.len() == raster_sources.len(),
14✔
69
                    error::InvalidOperatorSpec {
14✔
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

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

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

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

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

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

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

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

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

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

14✔
165
                let in_desc = vector_source.result_descriptor();
14✔
166

5✔
167
                Ok(InitializedBoxPlot::new(
5✔
168
                    name,
5✔
169
                    PlotResultDescriptor {
5✔
170
                        spatial_reference: in_desc.spatial_reference,
5✔
171
                        time: in_desc.time,
5✔
172
                        bbox: in_desc.bbox,
5✔
173
                    },
5✔
174
                    self.params.column_names.clone(),
5✔
175
                    vector_source,
5✔
176
                )
5✔
177
                .boxed())
5✔
178
            }
14✔
179
        }
14✔
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
        };
5✔
219

5✔
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
5✔
272
            .column_names
5✔
273
            .iter()
5✔
274
            .map(|name| BoxPlotAccum::new(name.clone()))
6✔
275
            .collect();
5✔
276

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

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

5✔
293
        let mut chart = geoengine_datatypes::plots::BoxPlot::new();
5✔
294
        for accum in &mut accums {
11✔
295
            if let Some(attrib) = accum.finish()? {
6✔
296
                chart.add_attribute(attrib);
5✔
297
            }
5✔
298
        }
5✔
299
        Ok(chart.to_vega_embeddable(false)?)
5✔
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✔
UNCOV
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
7✔
352
            .input
7✔
353
            .iter()
7✔
354
            .zip(self.names.iter())
7✔
355
            .map(|(proc, name)| Self::process_raster(name.clone(), proc, query.clone(), ctx))
9✔
356
            .collect();
7✔
357

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

7✔
363
        let mut chart = geoengine_datatypes::plots::BoxPlot::new();
7✔
364
        results?
7✔
365
            .into_iter()
7✔
366
            .flatten()
7✔
367
            .for_each(|a| chart.add_attribute(a));
7✔
368
        Ok(chart.to_vega_embeddable(false)?)
7✔
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(ref mut x) => {
24✔
385
                x.extend(values.filter(|x| x.is_finite()));
11,072✔
386

24✔
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(ref mut 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() % 2 == 0 {
30✔
404
            let i = values.len() / 2;
21✔
405
            (values[i] + values[i - 1]) / 2.0
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

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

10✔
416
        if values.len() % 2 == 0 {
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,
1✔
436
            )?)),
1✔
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
10✔
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
                        )?))
10✔
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

1✔
525
        let serialized = json!({
1✔
526
            "type": "BoxPlot",
1✔
527
            "params": {
1✔
528
                "columnNames": ["foobar"],
1✔
529
            },
1✔
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
                    }
1✔
538
                }
1✔
539
            }
1✔
540
        })
1✔
541
        .to_string();
1✔
542

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

1✔
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

1✔
559
        let serialized = json!({
1✔
560
            "type": "BoxPlot",
1✔
561
            "params": {
1✔
562
            },
1✔
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
                    }
1✔
571
                }
1✔
572
            }
1✔
573
        })
1✔
574
        .to_string();
1✔
575

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

1✔
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
            )
1✔
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
            )
1✔
601
            .unwrap(),
1✔
602
        ])
1✔
603
        .boxed();
1✔
604

1✔
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

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

1✔
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

1✔
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

1✔
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
        );
1✔
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
        );
1✔
645

1✔
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
            )
1✔
668
            .unwrap(),
1✔
669
        )
1✔
670
        .boxed();
1✔
671

1✔
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

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

1✔
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

1✔
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

1✔
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
        );
1✔
709

1✔
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
            )
1✔
721
            .unwrap(),
1✔
722
        )
1✔
723
        .boxed();
1✔
724

1✔
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

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

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

1✔
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
            )
1✔
750
            .unwrap(),
1✔
751
        )
1✔
752
        .boxed();
1✔
753

1✔
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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
841
        let execution_context = MockExecutionContext::test_default();
1✔
842

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

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

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

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

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

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

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

1✔
895
        let execution_context = MockExecutionContext::test_default();
1✔
896

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

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

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

1✔
935
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
936
    }
1✔
937

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

1✔
976
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
977

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

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

1✔
1002
        let expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1003

1✔
1004
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1005
    }
1✔
1006

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

1✔
1047
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1048

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

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

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

1✔
1077
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1078
    }
1✔
1079

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

1✔
1118
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1119

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

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

1✔
1144
        let expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1145

1✔
1146
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1147
    }
1✔
1148

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

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

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

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

1✔
1220
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1221
    }
1✔
1222

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

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

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

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

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

1✔
1303
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1304
    }
1✔
1305

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

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

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

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

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

1✔
1379
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1380
    }
1✔
1381

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

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

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

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

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

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

1✔
1474
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1475
    }
1✔
1476
}
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