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

geo-engine / geoengine / 5006008836

pending completion
5006008836

push

github

GitHub
Merge #785 #787

936 of 936 new or added lines in 50 files covered. (100.0%)

96010 of 107707 relevant lines covered (89.14%)

72676.46 hits per line

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

94.73
/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, BoundingBox2D,
5
    PlotQueryRectangle, VectorQueryRectangle,
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)]
14✔
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
impl PlotOperator for BoxPlot {
51
    async fn _initialize(
14✔
52
        self: Box<Self>,
14✔
53
        path: WorkflowOperatorPath,
14✔
54
        context: &dyn ExecutionContext,
14✔
55
    ) -> Result<Box<dyn InitializedPlotOperator>> {
14✔
56
        let name = CanonicOperatorName::from(&self);
14✔
57

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

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

81
                let output_names = if self.params.column_names.is_empty() {
7✔
82
                    (1..=raster_sources.len())
7✔
83
                        .map(|i| format!("Raster-{i}"))
9✔
84
                        .collect::<Vec<_>>()
7✔
85
                } else {
86
                    self.params.column_names.clone()
×
87
                };
88

89
                if raster_sources.len() > 1 {
7✔
90
                    let srs = raster_sources[0].result_descriptor().spatial_reference;
1✔
91
                    ensure!(
1✔
92
                        raster_sources
1✔
93
                            .iter()
1✔
94
                            .all(|op| op.result_descriptor().spatial_reference == srs),
3✔
95
                        error::AllSourcesMustHaveSameSpatialReference
×
96
                    );
97
                }
6✔
98

99
                let in_descriptors = raster_sources
7✔
100
                    .iter()
7✔
101
                    .map(InitializedRasterOperator::result_descriptor)
7✔
102
                    .collect::<Vec<_>>();
7✔
103

7✔
104
                let time = time_interval_extent(in_descriptors.iter().map(|d| d.time));
7✔
105
                let bbox = partitions_extent(in_descriptors.iter().map(|d| d.bbox));
7✔
106

7✔
107
                Ok(InitializedBoxPlot::new(
7✔
108
                    name,
7✔
109
                    PlotResultDescriptor {
7✔
110
                        spatial_reference: in_descriptors[0].spatial_reference,
7✔
111
                        time,
7✔
112
                        // converting `SpatialPartition2D` to `BoundingBox2D` is ok here, because is makes the covered area only larger
7✔
113
                        bbox: bbox
7✔
114
                            .and_then(|p| BoundingBox2D::new(p.lower_left(), p.upper_right()).ok()),
7✔
115
                    },
7✔
116
                    output_names,
7✔
117
                    raster_sources,
7✔
118
                )
7✔
119
                .boxed())
7✔
120
            }
121
            MultiRasterOrVectorOperator::Vector(vector_source) => {
7✔
122
                ensure!( !self.params.column_names.is_empty(),
7✔
123
                    error::InvalidOperatorSpec {
1✔
124
                        reason: "BoxPlot on vector data requires the selection of at least one numeric column ('column_names' parameter)."
1✔
125
                            .to_string(),
1✔
126
                    }
1✔
127
                );
128

129
                let vector_source = vector_source
6✔
130
                    .initialize(path.clone_and_append(0), context)
6✔
131
                    .await?;
×
132

133
                for cn in &self.params.column_names {
12✔
134
                    match vector_source
7✔
135
                        .result_descriptor()
7✔
136
                        .column_data_type(cn.as_str())
7✔
137
                    {
138
                        Some(column) if !column.is_numeric() => {
7✔
139
                            return Err(Error::InvalidOperatorSpec {
1✔
140
                                reason: format!("Column '{cn}' is not numeric."),
1✔
141
                            });
1✔
142
                        }
143
                        Some(_) => {
6✔
144
                            // OK
6✔
145
                        }
6✔
146
                        None => {
147
                            return Err(Error::ColumnDoesNotExist {
×
148
                                column: cn.to_string(),
×
149
                            });
×
150
                        }
151
                    }
152
                }
153

154
                let in_desc = vector_source.result_descriptor();
5✔
155

5✔
156
                Ok(InitializedBoxPlot::new(
5✔
157
                    name,
5✔
158
                    PlotResultDescriptor {
5✔
159
                        spatial_reference: in_desc.spatial_reference,
5✔
160
                        time: in_desc.time,
5✔
161
                        bbox: in_desc.bbox,
5✔
162
                    },
5✔
163
                    self.params.column_names.clone(),
5✔
164
                    vector_source,
5✔
165
                )
5✔
166
                .boxed())
5✔
167
            }
168
        }
169
    }
28✔
170

171
    span_fn!(BoxPlot);
×
172
}
173

174
/// The initialization of `BoxPlot`
175
pub struct InitializedBoxPlot<Op> {
176
    name: CanonicOperatorName,
177
    result_descriptor: PlotResultDescriptor,
178
    names: Vec<String>,
179

180
    source: Op,
181
}
182

183
impl<Op> InitializedBoxPlot<Op> {
184
    pub fn new(
12✔
185
        name: CanonicOperatorName,
12✔
186
        result_descriptor: PlotResultDescriptor,
12✔
187
        names: Vec<String>,
12✔
188
        source: Op,
12✔
189
    ) -> Self {
12✔
190
        Self {
12✔
191
            name,
12✔
192
            result_descriptor,
12✔
193
            names,
12✔
194
            source,
12✔
195
        }
12✔
196
    }
12✔
197
}
198
impl InitializedPlotOperator for InitializedBoxPlot<Box<dyn InitializedVectorOperator>> {
199
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
200
        &self.result_descriptor
×
201
    }
×
202

203
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
5✔
204
        let processor = BoxPlotVectorQueryProcessor {
5✔
205
            input: self.source.query_processor()?,
5✔
206
            column_names: self.names.clone(),
5✔
207
        };
5✔
208

5✔
209
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
5✔
210
    }
5✔
211

212
    fn canonic_name(&self) -> CanonicOperatorName {
×
213
        self.name.clone()
×
214
    }
×
215
}
216

217
impl InitializedPlotOperator for InitializedBoxPlot<Vec<Box<dyn InitializedRasterOperator>>> {
218
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
219
        &self.result_descriptor
×
220
    }
×
221

222
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
7✔
223
        let input = self
7✔
224
            .source
7✔
225
            .iter()
7✔
226
            .map(InitializedRasterOperator::query_processor)
7✔
227
            .collect::<Result<Vec<_>>>()?;
7✔
228

229
        let processor = BoxPlotRasterQueryProcessor {
7✔
230
            input,
7✔
231
            names: self.names.clone(),
7✔
232
        };
7✔
233
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
7✔
234
    }
7✔
235

236
    fn canonic_name(&self) -> CanonicOperatorName {
×
237
        self.name.clone()
×
238
    }
×
239
}
240

241
/// A query processor that calculates the boxplots about its vector input.
242
pub struct BoxPlotVectorQueryProcessor {
243
    input: TypedVectorQueryProcessor,
244
    column_names: Vec<String>,
245
}
246

247
#[async_trait]
248
impl PlotQueryProcessor for BoxPlotVectorQueryProcessor {
249
    type OutputFormat = PlotData;
250

251
    fn plot_type(&self) -> &'static str {
×
252
        BOXPLOT_OPERATOR_NAME
×
253
    }
×
254

255
    async fn plot_query<'p>(
5✔
256
        &'p self,
5✔
257
        query: VectorQueryRectangle,
5✔
258
        ctx: &'p dyn QueryContext,
5✔
259
    ) -> Result<Self::OutputFormat> {
5✔
260
        let mut accums: Vec<BoxPlotAccum> = self
5✔
261
            .column_names
5✔
262
            .iter()
5✔
263
            .map(|name| BoxPlotAccum::new(name.clone()))
6✔
264
            .collect();
5✔
265

266
        call_on_generic_vector_processor!(&self.input, processor => {
5✔
267
            let mut query = processor.query(query, ctx).await?;
5✔
268
            while let Some(collection) = query.next().await {
11✔
269
                let collection = collection?;
6✔
270

271
                for accum in &mut accums {
14✔
272
                    let feature_data = collection.data(&accum.name).expect("checked in param");
8✔
273
                    let iter = feature_data.float_options_iter().map(|o| match o {
20,032✔
274
                        Some(v) => v,
20,030✔
275
                        None => f64::NAN,
2✔
276
                    });
20,032✔
277
                    accum.update(iter)?;
8✔
278
                }
279
            }
280
        });
281

282
        let mut chart = geoengine_datatypes::plots::BoxPlot::new();
5✔
283
        for accum in &mut accums {
11✔
284
            if let Some(attrib) = accum.finish()? {
6✔
285
                chart.add_attribute(attrib);
5✔
286
            }
5✔
287
        }
288
        Ok(chart.to_vega_embeddable(false)?)
5✔
289
    }
10✔
290
}
291

292
/// A query processor that calculates the boxplots about its raster input.
293
pub struct BoxPlotRasterQueryProcessor {
294
    input: Vec<TypedRasterQueryProcessor>,
295
    names: Vec<String>,
296
}
297

298
impl BoxPlotRasterQueryProcessor {
299
    async fn process_raster(
9✔
300
        name: String,
9✔
301
        input: &TypedRasterQueryProcessor,
9✔
302
        query: PlotQueryRectangle,
9✔
303
        ctx: &dyn QueryContext,
9✔
304
    ) -> Result<Option<BoxPlotAttribute>> {
9✔
305
        call_on_generic_raster_processor!(input, processor => {
9✔
306

307

308
            let mut stream = processor.query(query.into(), ctx).await?;
9✔
309
            let mut accum = BoxPlotAccum::new(name);
9✔
310

311
            while let Some(tile) = stream.next().await {
58,122✔
312
                let tile = tile?;
58,113✔
313

314
                match tile.grid_array {
58,113✔
315
                    // Ignore empty grids if no_data should not be included
316
                    GridOrEmpty::Empty(_) => {},
58,106✔
317
                    GridOrEmpty::Grid(grid) => {
7✔
318
                        accum.update(grid.masked_element_deref_iterator().filter_map(|pixel_option| pixel_option.map(|p| { let v: f64 = p.as_(); v})))?;
52✔
319
                    }
320
                }
321
            }
322
            accum.finish()
9✔
323
        })
324
    }
9✔
325
}
326

327
#[async_trait]
328
impl PlotQueryProcessor for BoxPlotRasterQueryProcessor {
329
    type OutputFormat = PlotData;
330

331
    fn plot_type(&self) -> &'static str {
×
332
        BOXPLOT_OPERATOR_NAME
×
333
    }
×
334

335
    async fn plot_query<'p>(
7✔
336
        &'p self,
7✔
337
        query: PlotQueryRectangle,
7✔
338
        ctx: &'p dyn QueryContext,
7✔
339
    ) -> Result<Self::OutputFormat> {
7✔
340
        let results: Vec<_> = self
7✔
341
            .input
7✔
342
            .iter()
7✔
343
            .zip(self.names.iter())
7✔
344
            .map(|(proc, name)| Self::process_raster(name.clone(), proc, query, ctx))
9✔
345
            .collect();
7✔
346

347
        let results = futures::future::join_all(results)
7✔
348
            .await
×
349
            .into_iter()
7✔
350
            .collect::<Result<Vec<_>>>();
7✔
351

7✔
352
        let mut chart = geoengine_datatypes::plots::BoxPlot::new();
7✔
353
        results?
7✔
354
            .into_iter()
7✔
355
            .flatten()
7✔
356
            .for_each(|a| chart.add_attribute(a));
7✔
357
        Ok(chart.to_vega_embeddable(false)?)
7✔
358
    }
14✔
359
}
360

361
//
362
// AUX Structures
363
//
364

365
enum BoxPlotAccumKind {
366
    Exact(Vec<f64>),
367
    Estimated(PSquareQuantileEstimator<f64>),
368
}
369

370
impl BoxPlotAccumKind {
371
    fn update(&mut self, values: impl Iterator<Item = f64>) -> crate::util::Result<()> {
33✔
372
        match self {
33✔
373
            Self::Exact(ref mut x) => {
24✔
374
                x.extend(values.filter(|x| x.is_finite()));
11,072✔
375

24✔
376
                if x.len() > EXACT_CALC_BOUND {
24✔
377
                    let est = PSquareQuantileEstimator::new(0.5, x.as_slice())?;
1✔
378
                    *self = Self::Estimated(est);
1✔
379
                }
23✔
380
                Ok(())
24✔
381
            }
382
            Self::Estimated(ref mut est) => {
9✔
383
                for v in values {
9,009✔
384
                    est.update(v);
9,000✔
385
                }
9,000✔
386
                Ok(())
9✔
387
            }
388
        }
389
    }
33✔
390

391
    fn median(values: &[f64]) -> f64 {
30✔
392
        if values.len() % 2 == 0 {
30✔
393
            let i = values.len() / 2;
21✔
394
            (values[i] + values[i - 1]) / 2.0
21✔
395
        } else {
396
            values[values.len() / 2]
9✔
397
        }
398
    }
30✔
399

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

10✔
403
        let s = values.split_at(idx);
10✔
404

10✔
405
        if values.len() % 2 == 0 {
10✔
406
            s
5✔
407
        } else {
408
            (s.0, &s.1[1..])
5✔
409
        }
410
    }
10✔
411

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

454
struct BoxPlotAccum {
455
    name: String,
456
    accum: BoxPlotAccumKind,
457
}
458

459
impl BoxPlotAccum {
460
    fn new(name: String) -> BoxPlotAccum {
15✔
461
        BoxPlotAccum {
15✔
462
            name,
15✔
463
            accum: BoxPlotAccumKind::Exact(Vec::new()),
15✔
464
        }
15✔
465
    }
15✔
466

467
    fn update(&mut self, values: impl Iterator<Item = f64>) -> crate::util::Result<()> {
15✔
468
        for chunk in &itertools::Itertools::chunks(values, BATCH_SIZE) {
33✔
469
            self.accum.update(chunk)?;
33✔
470
        }
471
        Ok(())
15✔
472
    }
15✔
473

474
    fn finish(&mut self) -> Result<Option<geoengine_datatypes::plots::BoxPlotAttribute>> {
15✔
475
        self.accum.create_plot(self.name.clone())
15✔
476
    }
15✔
477
}
478

479
#[cfg(test)]
480
mod tests {
481
    use serde_json::json;
482

483
    use geoengine_datatypes::primitives::{
484
        BoundingBox2D, DateTime, FeatureData, Measurement, NoGeometry, SpatialResolution,
485
        TimeInterval,
486
    };
487
    use geoengine_datatypes::raster::{
488
        EmptyGrid2D, Grid2D, MaskedGrid2D, RasterDataType, RasterTile2D, TileInformation,
489
        TilingSpecification,
490
    };
491
    use geoengine_datatypes::spatial_reference::SpatialReference;
492
    use geoengine_datatypes::util::test::TestDefault;
493
    use geoengine_datatypes::{collections::DataCollection, primitives::MultiPoint};
494

495
    use crate::engine::{
496
        ChunkByteSize, MockExecutionContext, MockQueryContext, RasterOperator,
497
        RasterResultDescriptor, VectorOperator,
498
    };
499
    use crate::mock::{MockFeatureCollectionSource, MockRasterSource, MockRasterSourceParams};
500

501
    use super::*;
502

503
    #[test]
1✔
504
    fn serialization() {
1✔
505
        let histogram = BoxPlot {
1✔
506
            params: BoxPlotParams {
1✔
507
                column_names: vec!["foobar".to_string()],
1✔
508
            },
1✔
509
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
510
                .boxed()
1✔
511
                .into(),
1✔
512
        };
1✔
513

1✔
514
        let serialized = json!({
1✔
515
            "type": "BoxPlot",
1✔
516
            "params": {
1✔
517
                "columnNames": ["foobar"],
1✔
518
            },
1✔
519
            "sources": {
1✔
520
                "source": {
1✔
521
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
522
                    "params": {
1✔
523
                        "collections": [],
1✔
524
                        "spatialReference": "EPSG:4326",
1✔
525
                        "measurements": {},
1✔
526
                    }
1✔
527
                }
1✔
528
            }
1✔
529
        })
1✔
530
        .to_string();
1✔
531

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

1✔
534
        assert_eq!(deserialized.params, histogram.params);
1✔
535
    }
1✔
536

537
    #[test]
1✔
538
    fn serialization_alt() {
1✔
539
        let histogram = BoxPlot {
1✔
540
            params: BoxPlotParams {
1✔
541
                column_names: vec![],
1✔
542
            },
1✔
543
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
544
                .boxed()
1✔
545
                .into(),
1✔
546
        };
1✔
547

1✔
548
        let serialized = json!({
1✔
549
            "type": "BoxPlot",
1✔
550
            "params": {
1✔
551
            },
1✔
552
            "sources": {
1✔
553
                "source": {
1✔
554
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
555
                    "params": {
1✔
556
                        "collections": [],
1✔
557
                        "spatialReference": "EPSG:4326",
1✔
558
                        "measurements": {},
1✔
559
                    }
1✔
560
                }
1✔
561
            }
1✔
562
        })
1✔
563
        .to_string();
1✔
564

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

1✔
567
        assert_eq!(deserialized.params, histogram.params);
1✔
568
    }
1✔
569

570
    #[tokio::test]
1✔
571
    async fn vector_data() {
1✔
572
        let vector_source = MockFeatureCollectionSource::multiple(vec![
1✔
573
            DataCollection::from_slices(
1✔
574
                &[] as &[NoGeometry],
1✔
575
                &[TimeInterval::default(); 8],
1✔
576
                &[
1✔
577
                    ("foo", FeatureData::Int(vec![1, 1, 2, 2, 3, 3, 4, 4])),
1✔
578
                    ("bar", FeatureData::Int(vec![1, 1, 2, 2, 3, 3, 4, 4])),
1✔
579
                ],
1✔
580
            )
1✔
581
            .unwrap(),
1✔
582
            DataCollection::from_slices(
1✔
583
                &[] as &[NoGeometry],
1✔
584
                &[TimeInterval::default(); 4],
1✔
585
                &[
1✔
586
                    ("foo", FeatureData::Int(vec![5, 6, 7, 8])),
1✔
587
                    ("bar", FeatureData::Int(vec![5, 6, 7, 8])),
1✔
588
                ],
1✔
589
            )
1✔
590
            .unwrap(),
1✔
591
        ])
1✔
592
        .boxed();
1✔
593

1✔
594
        let box_plot = BoxPlot {
1✔
595
            params: BoxPlotParams {
1✔
596
                column_names: vec!["foo".to_string(), "bar".to_string()],
1✔
597
            },
1✔
598
            sources: vector_source.into(),
1✔
599
        };
1✔
600

1✔
601
        let execution_context = MockExecutionContext::test_default();
1✔
602

603
        let query_processor = box_plot
1✔
604
            .boxed()
1✔
605
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
606
            .await
×
607
            .unwrap()
1✔
608
            .query_processor()
1✔
609
            .unwrap()
1✔
610
            .json_vega()
1✔
611
            .unwrap();
1✔
612

613
        let result = query_processor
1✔
614
            .plot_query(
1✔
615
                VectorQueryRectangle {
1✔
616
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
617
                        .unwrap(),
1✔
618
                    time_interval: TimeInterval::default(),
1✔
619
                    spatial_resolution: SpatialResolution::one(),
1✔
620
                },
1✔
621
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
622
            )
1✔
623
            .await
×
624
            .unwrap();
1✔
625

1✔
626
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
627
        expected.add_attribute(
1✔
628
            BoxPlotAttribute::new("foo".to_string(), 1.0, 8.0, 3.5, 2.0, 5.5, true).unwrap(),
1✔
629
        );
1✔
630
        expected.add_attribute(
1✔
631
            BoxPlotAttribute::new("bar".to_string(), 1.0, 8.0, 3.5, 2.0, 5.5, true).unwrap(),
1✔
632
        );
1✔
633

1✔
634
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
635
    }
636

637
    #[tokio::test]
1✔
638
    async fn vector_data_with_nulls() {
1✔
639
        let vector_source = MockFeatureCollectionSource::single(
1✔
640
            DataCollection::from_slices(
1✔
641
                &[] as &[NoGeometry],
1✔
642
                &[TimeInterval::default(); 7],
1✔
643
                &[(
1✔
644
                    "foo",
1✔
645
                    FeatureData::NullableFloat(vec![
1✔
646
                        Some(1.),
1✔
647
                        Some(2.),
1✔
648
                        None,
1✔
649
                        Some(4.),
1✔
650
                        None,
1✔
651
                        Some(6.),
1✔
652
                        Some(7.),
1✔
653
                    ]),
1✔
654
                )],
1✔
655
            )
1✔
656
            .unwrap(),
1✔
657
        )
1✔
658
        .boxed();
1✔
659

1✔
660
        let box_plot = BoxPlot {
1✔
661
            params: BoxPlotParams {
1✔
662
                column_names: vec!["foo".to_string()],
1✔
663
            },
1✔
664
            sources: vector_source.into(),
1✔
665
        };
1✔
666

1✔
667
        let execution_context = MockExecutionContext::test_default();
1✔
668

669
        let query_processor = box_plot
1✔
670
            .boxed()
1✔
671
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
672
            .await
×
673
            .unwrap()
1✔
674
            .query_processor()
1✔
675
            .unwrap()
1✔
676
            .json_vega()
1✔
677
            .unwrap();
1✔
678

679
        let result = query_processor
1✔
680
            .plot_query(
1✔
681
                VectorQueryRectangle {
1✔
682
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
683
                        .unwrap(),
1✔
684
                    time_interval: TimeInterval::default(),
1✔
685
                    spatial_resolution: SpatialResolution::one(),
1✔
686
                },
1✔
687
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
688
            )
1✔
689
            .await
×
690
            .unwrap();
1✔
691

1✔
692
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
693
        expected.add_attribute(
1✔
694
            BoxPlotAttribute::new("foo".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
695
        );
1✔
696

1✔
697
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
698
    }
699

700
    #[tokio::test]
1✔
701
    async fn vector_data_text_column() {
1✔
702
        let vector_source = MockFeatureCollectionSource::single(
1✔
703
            DataCollection::from_slices(
1✔
704
                &[] as &[NoGeometry],
1✔
705
                &[TimeInterval::default(); 1],
1✔
706
                &[("foo", FeatureData::Text(vec!["test".to_string()]))],
1✔
707
            )
1✔
708
            .unwrap(),
1✔
709
        )
1✔
710
        .boxed();
1✔
711

1✔
712
        let box_plot = BoxPlot {
1✔
713
            params: BoxPlotParams {
1✔
714
                column_names: vec!["foo".to_string()],
1✔
715
            },
1✔
716
            sources: vector_source.into(),
1✔
717
        };
1✔
718

1✔
719
        let execution_context = MockExecutionContext::test_default();
1✔
720

721
        let init = box_plot
1✔
722
            .boxed()
1✔
723
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
724
            .await;
×
725

726
        assert!(init.is_err());
1✔
727
    }
728

729
    #[tokio::test]
1✔
730
    async fn vector_data_missing_column() {
1✔
731
        let vector_source = MockFeatureCollectionSource::single(
1✔
732
            DataCollection::from_slices(
1✔
733
                &[] as &[NoGeometry],
1✔
734
                &[TimeInterval::default(); 1],
1✔
735
                &[("foo", FeatureData::Text(vec!["test".to_string()]))],
1✔
736
            )
1✔
737
            .unwrap(),
1✔
738
        )
1✔
739
        .boxed();
1✔
740

1✔
741
        let box_plot = BoxPlot {
1✔
742
            params: BoxPlotParams {
1✔
743
                column_names: vec![],
1✔
744
            },
1✔
745
            sources: vector_source.into(),
1✔
746
        };
1✔
747

1✔
748
        let execution_context = MockExecutionContext::test_default();
1✔
749

750
        let init = box_plot
1✔
751
            .boxed()
1✔
752
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
753
            .await;
×
754

755
        assert!(init.is_err());
1✔
756
    }
757

758
    #[tokio::test]
1✔
759
    async fn vector_data_single_feature() {
1✔
760
        let vector_source =
1✔
761
            MockFeatureCollectionSource::multiple(vec![DataCollection::from_slices(
1✔
762
                &[] as &[NoGeometry],
1✔
763
                &[TimeInterval::default(); 1],
1✔
764
                &[("foo", FeatureData::Int(vec![1]))],
1✔
765
            )
1✔
766
            .unwrap()])
1✔
767
            .boxed();
1✔
768

1✔
769
        let box_plot = BoxPlot {
1✔
770
            params: BoxPlotParams {
1✔
771
                column_names: vec!["foo".to_string()],
1✔
772
            },
1✔
773
            sources: vector_source.into(),
1✔
774
        };
1✔
775

1✔
776
        let execution_context = MockExecutionContext::test_default();
1✔
777

778
        let query_processor = box_plot
1✔
779
            .boxed()
1✔
780
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
781
            .await
×
782
            .unwrap()
1✔
783
            .query_processor()
1✔
784
            .unwrap()
1✔
785
            .json_vega()
1✔
786
            .unwrap();
1✔
787

788
        let result = query_processor
1✔
789
            .plot_query(
1✔
790
                VectorQueryRectangle {
1✔
791
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
792
                        .unwrap(),
1✔
793
                    time_interval: TimeInterval::default(),
1✔
794
                    spatial_resolution: SpatialResolution::one(),
1✔
795
                },
1✔
796
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
797
            )
1✔
798
            .await
×
799
            .unwrap();
1✔
800

1✔
801
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
802
        expected.add_attribute(
1✔
803
            BoxPlotAttribute::new("foo".to_string(), 1.0, 1.0, 1.0, 1.0, 1.0, true).unwrap(),
1✔
804
        );
1✔
805

1✔
806
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
807
    }
808

809
    #[tokio::test]
1✔
810
    async fn vector_data_empty() {
1✔
811
        let vector_source =
1✔
812
            MockFeatureCollectionSource::multiple(vec![DataCollection::from_slices(
1✔
813
                &[] as &[NoGeometry],
1✔
814
                &[] as &[TimeInterval],
1✔
815
                &[("foo", FeatureData::Int(vec![]))],
1✔
816
            )
1✔
817
            .unwrap()])
1✔
818
            .boxed();
1✔
819

1✔
820
        let box_plot = BoxPlot {
1✔
821
            params: BoxPlotParams {
1✔
822
                column_names: vec!["foo".to_string()],
1✔
823
            },
1✔
824
            sources: vector_source.into(),
1✔
825
        };
1✔
826

1✔
827
        let execution_context = MockExecutionContext::test_default();
1✔
828

829
        let query_processor = box_plot
1✔
830
            .boxed()
1✔
831
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
832
            .await
×
833
            .unwrap()
1✔
834
            .query_processor()
1✔
835
            .unwrap()
1✔
836
            .json_vega()
1✔
837
            .unwrap();
1✔
838

839
        let result = query_processor
1✔
840
            .plot_query(
1✔
841
                VectorQueryRectangle {
1✔
842
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
843
                        .unwrap(),
1✔
844
                    time_interval: TimeInterval::default(),
1✔
845
                    spatial_resolution: SpatialResolution::one(),
1✔
846
                },
1✔
847
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
848
            )
1✔
849
            .await
×
850
            .unwrap();
1✔
851

1✔
852
        let expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
853
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
854
    }
855

856
    #[tokio::test]
1✔
857
    async fn vector_data_estimator_switch() {
1✔
858
        let mut data = Vec::<i64>::with_capacity(2 * super::EXACT_CALC_BOUND);
1✔
859

860
        for i in 1..=data.capacity() as i64 {
20,000✔
861
            data.push(i);
20,000✔
862
        }
20,000✔
863

864
        let vector_source =
1✔
865
            MockFeatureCollectionSource::multiple(vec![DataCollection::from_slices(
1✔
866
                &[] as &[NoGeometry],
1✔
867
                &[TimeInterval::default(); 2 * super::EXACT_CALC_BOUND],
1✔
868
                &[("foo", FeatureData::Int(data))],
1✔
869
            )
1✔
870
            .unwrap()])
1✔
871
            .boxed();
1✔
872

1✔
873
        let box_plot = BoxPlot {
1✔
874
            params: BoxPlotParams {
1✔
875
                column_names: vec!["foo".to_string()],
1✔
876
            },
1✔
877
            sources: vector_source.into(),
1✔
878
        };
1✔
879

1✔
880
        let execution_context = MockExecutionContext::test_default();
1✔
881

882
        let query_processor = box_plot
1✔
883
            .boxed()
1✔
884
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
885
            .await
×
886
            .unwrap()
1✔
887
            .query_processor()
1✔
888
            .unwrap()
1✔
889
            .json_vega()
1✔
890
            .unwrap();
1✔
891

892
        let result = query_processor
1✔
893
            .plot_query(
1✔
894
                VectorQueryRectangle {
1✔
895
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
896
                        .unwrap(),
1✔
897
                    time_interval: TimeInterval::default(),
1✔
898
                    spatial_resolution: SpatialResolution::one(),
1✔
899
                },
1✔
900
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
901
            )
1✔
902
            .await
×
903
            .unwrap();
1✔
904

1✔
905
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
906
        expected.add_attribute(
1✔
907
            BoxPlotAttribute::new(
1✔
908
                "foo".to_string(),
1✔
909
                1.0,
1✔
910
                2.0 * super::EXACT_CALC_BOUND as f64,
1✔
911
                super::EXACT_CALC_BOUND as f64,
1✔
912
                0.5 * super::EXACT_CALC_BOUND as f64,
1✔
913
                1.5 * super::EXACT_CALC_BOUND as f64,
1✔
914
                false,
1✔
915
            )
1✔
916
            .unwrap(),
1✔
917
        );
1✔
918

1✔
919
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
920
    }
921

922
    #[tokio::test]
1✔
923
    async fn no_data_raster_exclude_no_data() {
1✔
924
        let tile_size_in_pixels = [3, 2].into();
1✔
925
        let tiling_specification = TilingSpecification {
1✔
926
            origin_coordinate: [0.0, 0.0].into(),
1✔
927
            tile_size_in_pixels,
1✔
928
        };
1✔
929
        let box_plot = BoxPlot {
1✔
930
            params: BoxPlotParams {
1✔
931
                column_names: vec![],
1✔
932
            },
1✔
933
            sources: MockRasterSource {
1✔
934
                params: MockRasterSourceParams {
1✔
935
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
936
                        TimeInterval::default(),
1✔
937
                        TileInformation {
1✔
938
                            global_geo_transform: TestDefault::test_default(),
1✔
939
                            global_tile_position: [0, 0].into(),
1✔
940
                            tile_size_in_pixels,
1✔
941
                        },
1✔
942
                        EmptyGrid2D::<u8>::new(tile_size_in_pixels).into(),
1✔
943
                    )],
1✔
944
                    result_descriptor: RasterResultDescriptor {
1✔
945
                        data_type: RasterDataType::U8,
1✔
946
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
947
                        measurement: Measurement::Unitless,
1✔
948
                        time: None,
1✔
949
                        bbox: None,
1✔
950
                        resolution: None,
1✔
951
                    },
1✔
952
                },
1✔
953
            }
1✔
954
            .boxed()
1✔
955
            .into(),
1✔
956
        };
1✔
957

1✔
958
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
959

960
        let query_processor = box_plot
1✔
961
            .boxed()
1✔
962
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
963
            .await
×
964
            .unwrap()
1✔
965
            .query_processor()
1✔
966
            .unwrap()
1✔
967
            .json_vega()
1✔
968
            .unwrap();
1✔
969

970
        let result = query_processor
1✔
971
            .plot_query(
1✔
972
                VectorQueryRectangle {
1✔
973
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
974
                        .unwrap(),
1✔
975
                    time_interval: TimeInterval::default(),
1✔
976
                    spatial_resolution: SpatialResolution::one(),
1✔
977
                },
1✔
978
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
979
            )
1✔
980
            .await
×
981
            .unwrap();
1✔
982

1✔
983
        let expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
984

1✔
985
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
986
    }
987

988
    #[tokio::test]
1✔
989
    async fn no_data_raster_include_no_data() {
1✔
990
        let tile_size_in_pixels = [3, 2].into();
1✔
991
        let tiling_specification = TilingSpecification {
1✔
992
            origin_coordinate: [0.0, 0.0].into(),
1✔
993
            tile_size_in_pixels,
1✔
994
        };
1✔
995
        let box_plot = BoxPlot {
1✔
996
            params: BoxPlotParams {
1✔
997
                column_names: vec![],
1✔
998
            },
1✔
999
            sources: MockRasterSource {
1✔
1000
                params: MockRasterSourceParams {
1✔
1001
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1002
                        TimeInterval::default(),
1✔
1003
                        TileInformation {
1✔
1004
                            global_geo_transform: TestDefault::test_default(),
1✔
1005
                            global_tile_position: [0, 0].into(),
1✔
1006
                            tile_size_in_pixels,
1✔
1007
                        },
1✔
1008
                        Grid2D::new(tile_size_in_pixels, vec![0, 0, 0, 0, 0, 0])
1✔
1009
                            .unwrap()
1✔
1010
                            .into(),
1✔
1011
                    )],
1✔
1012
                    result_descriptor: RasterResultDescriptor {
1✔
1013
                        data_type: RasterDataType::U8,
1✔
1014
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1015
                        measurement: Measurement::Unitless,
1✔
1016
                        time: None,
1✔
1017
                        bbox: None,
1✔
1018
                        resolution: None,
1✔
1019
                    },
1✔
1020
                },
1✔
1021
            }
1✔
1022
            .boxed()
1✔
1023
            .into(),
1✔
1024
        };
1✔
1025

1✔
1026
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1027

1028
        let query_processor = box_plot
1✔
1029
            .boxed()
1✔
1030
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1031
            .await
×
1032
            .unwrap()
1✔
1033
            .query_processor()
1✔
1034
            .unwrap()
1✔
1035
            .json_vega()
1✔
1036
            .unwrap();
1✔
1037

1038
        let result = query_processor
1✔
1039
            .plot_query(
1✔
1040
                VectorQueryRectangle {
1✔
1041
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
1042
                    time_interval: TimeInterval::default(),
1✔
1043
                    spatial_resolution: SpatialResolution::one(),
1✔
1044
                },
1✔
1045
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1046
            )
1✔
1047
            .await
×
1048
            .unwrap();
1✔
1049

1✔
1050
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1051
        expected.add_attribute(
1✔
1052
            BoxPlotAttribute::new("Raster-1".to_owned(), 0.0, 0.0, 0.0, 0.0, 0.0, true).unwrap(),
1✔
1053
        );
1✔
1054

1✔
1055
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1056
    }
1057

1058
    #[tokio::test]
1✔
1059
    async fn empty_tile_raster_exclude_no_data() {
1✔
1060
        let tile_size_in_pixels = [3, 2].into();
1✔
1061
        let tiling_specification = TilingSpecification {
1✔
1062
            origin_coordinate: [0.0, 0.0].into(),
1✔
1063
            tile_size_in_pixels,
1✔
1064
        };
1✔
1065
        let box_plot = BoxPlot {
1✔
1066
            params: BoxPlotParams {
1✔
1067
                column_names: vec![],
1✔
1068
            },
1✔
1069
            sources: MockRasterSource {
1✔
1070
                params: MockRasterSourceParams {
1✔
1071
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1072
                        TimeInterval::default(),
1✔
1073
                        TileInformation {
1✔
1074
                            global_geo_transform: TestDefault::test_default(),
1✔
1075
                            global_tile_position: [0, 0].into(),
1✔
1076
                            tile_size_in_pixels,
1✔
1077
                        },
1✔
1078
                        EmptyGrid2D::<u8>::new(tile_size_in_pixels).into(),
1✔
1079
                    )],
1✔
1080
                    result_descriptor: RasterResultDescriptor {
1✔
1081
                        data_type: RasterDataType::U8,
1✔
1082
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1083
                        measurement: Measurement::Unitless,
1✔
1084
                        time: None,
1✔
1085
                        bbox: None,
1✔
1086
                        resolution: None,
1✔
1087
                    },
1✔
1088
                },
1✔
1089
            }
1✔
1090
            .boxed()
1✔
1091
            .into(),
1✔
1092
        };
1✔
1093

1✔
1094
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1095

1096
        let query_processor = box_plot
1✔
1097
            .boxed()
1✔
1098
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1099
            .await
×
1100
            .unwrap()
1✔
1101
            .query_processor()
1✔
1102
            .unwrap()
1✔
1103
            .json_vega()
1✔
1104
            .unwrap();
1✔
1105

1106
        let result = query_processor
1✔
1107
            .plot_query(
1✔
1108
                VectorQueryRectangle {
1✔
1109
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1110
                        .unwrap(),
1✔
1111
                    time_interval: TimeInterval::default(),
1✔
1112
                    spatial_resolution: SpatialResolution::one(),
1✔
1113
                },
1✔
1114
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1115
            )
1✔
1116
            .await
×
1117
            .unwrap();
1✔
1118

1✔
1119
        let expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1120

1✔
1121
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1122
    }
1123

1124
    #[tokio::test]
1✔
1125
    async fn single_value_raster_stream() {
1✔
1126
        let tile_size_in_pixels = [3, 2].into();
1✔
1127
        let tiling_specification = TilingSpecification {
1✔
1128
            origin_coordinate: [0.0, 0.0].into(),
1✔
1129
            tile_size_in_pixels,
1✔
1130
        };
1✔
1131
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1132
        let histogram = BoxPlot {
1✔
1133
            params: BoxPlotParams {
1✔
1134
                column_names: vec![],
1✔
1135
            },
1✔
1136
            sources: MockRasterSource {
1✔
1137
                params: MockRasterSourceParams {
1✔
1138
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1139
                        TimeInterval::default(),
1✔
1140
                        TileInformation {
1✔
1141
                            global_geo_transform: TestDefault::test_default(),
1✔
1142
                            global_tile_position: [0, 0].into(),
1✔
1143
                            tile_size_in_pixels,
1✔
1144
                        },
1✔
1145
                        Grid2D::new(tile_size_in_pixels, vec![4; 6]).unwrap().into(),
1✔
1146
                    )],
1✔
1147
                    result_descriptor: RasterResultDescriptor {
1✔
1148
                        data_type: RasterDataType::U8,
1✔
1149
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1150
                        measurement: Measurement::Unitless,
1✔
1151
                        time: None,
1✔
1152
                        bbox: None,
1✔
1153
                        resolution: None,
1✔
1154
                    },
1✔
1155
                },
1✔
1156
            }
1✔
1157
            .boxed()
1✔
1158
            .into(),
1✔
1159
        };
1✔
1160

1161
        let query_processor = histogram
1✔
1162
            .boxed()
1✔
1163
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1164
            .await
×
1165
            .unwrap()
1✔
1166
            .query_processor()
1✔
1167
            .unwrap()
1✔
1168
            .json_vega()
1✔
1169
            .unwrap();
1✔
1170

1171
        let result = query_processor
1✔
1172
            .plot_query(
1✔
1173
                VectorQueryRectangle {
1✔
1174
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1175
                        .unwrap(),
1✔
1176
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1177
                        2013, 12, 1, 12, 0, 0,
1✔
1178
                    ))
1✔
1179
                    .unwrap(),
1✔
1180
                    spatial_resolution: SpatialResolution::one(),
1✔
1181
                },
1✔
1182
                &MockQueryContext::test_default(),
1✔
1183
            )
1✔
1184
            .await
×
1185
            .unwrap();
1✔
1186

1✔
1187
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1188
        expected.add_attribute(
1✔
1189
            BoxPlotAttribute::new("Raster-1".to_owned(), 4.0, 4.0, 4.0, 4.0, 4.0, true).unwrap(),
1✔
1190
        );
1✔
1191

1✔
1192
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1193
    }
1194

1195
    #[tokio::test]
1✔
1196
    async fn raster_with_no_data_exclude_no_data() {
1✔
1197
        let tile_size_in_pixels = [4, 2].into();
1✔
1198
        let tiling_specification = TilingSpecification {
1✔
1199
            origin_coordinate: [0.0, 0.0].into(),
1✔
1200
            tile_size_in_pixels,
1✔
1201
        };
1✔
1202
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1203

1✔
1204
        let histogram = BoxPlot {
1✔
1205
            params: BoxPlotParams {
1✔
1206
                column_names: vec![],
1✔
1207
            },
1✔
1208
            sources: MockRasterSource {
1✔
1209
                params: MockRasterSourceParams {
1✔
1210
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1211
                        TimeInterval::default(),
1✔
1212
                        TileInformation {
1✔
1213
                            global_geo_transform: TestDefault::test_default(),
1✔
1214
                            global_tile_position: [0, 0].into(),
1✔
1215
                            tile_size_in_pixels,
1✔
1216
                        },
1✔
1217
                        MaskedGrid2D::new(
1✔
1218
                            Grid2D::new(tile_size_in_pixels, vec![1, 2, 0, 4, 0, 6, 7, 0]).unwrap(),
1✔
1219
                            Grid2D::new(
1✔
1220
                                tile_size_in_pixels,
1✔
1221
                                vec![true, true, false, true, false, true, true, false],
1✔
1222
                            )
1✔
1223
                            .unwrap(),
1✔
1224
                        )
1✔
1225
                        .unwrap()
1✔
1226
                        .into(),
1✔
1227
                    )],
1✔
1228
                    result_descriptor: RasterResultDescriptor {
1✔
1229
                        data_type: RasterDataType::U8,
1✔
1230
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1231
                        measurement: Measurement::Unitless,
1✔
1232
                        time: None,
1✔
1233
                        bbox: None,
1✔
1234
                        resolution: None,
1✔
1235
                    },
1✔
1236
                },
1✔
1237
            }
1✔
1238
            .boxed()
1✔
1239
            .into(),
1✔
1240
        };
1✔
1241

1242
        let query_processor = histogram
1✔
1243
            .boxed()
1✔
1244
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1245
            .await
×
1246
            .unwrap()
1✔
1247
            .query_processor()
1✔
1248
            .unwrap()
1✔
1249
            .json_vega()
1✔
1250
            .unwrap();
1✔
1251

1252
        let result = query_processor
1✔
1253
            .plot_query(
1✔
1254
                VectorQueryRectangle {
1✔
1255
                    spatial_bounds: BoundingBox2D::new((0., -4.).into(), (2., 0.).into()).unwrap(),
1✔
1256
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1257
                        2013, 12, 1, 12, 0, 0,
1✔
1258
                    ))
1✔
1259
                    .unwrap(),
1✔
1260
                    spatial_resolution: SpatialResolution::one(),
1✔
1261
                },
1✔
1262
                &MockQueryContext::test_default(),
1✔
1263
            )
1✔
1264
            .await
×
1265
            .unwrap();
1✔
1266

1✔
1267
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1268
        expected.add_attribute(
1✔
1269
            BoxPlotAttribute::new("Raster-1".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1270
        );
1✔
1271

1✔
1272
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1273
    }
1274

1275
    #[tokio::test]
1✔
1276
    async fn raster_with_no_data_include_no_data() {
1✔
1277
        let tile_size_in_pixels = [4, 2].into();
1✔
1278
        let tiling_specification = TilingSpecification {
1✔
1279
            origin_coordinate: [0.0, 0.0].into(),
1✔
1280
            tile_size_in_pixels,
1✔
1281
        };
1✔
1282
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1283

1✔
1284
        let histogram = BoxPlot {
1✔
1285
            params: BoxPlotParams {
1✔
1286
                column_names: vec![],
1✔
1287
            },
1✔
1288
            sources: MockRasterSource {
1✔
1289
                params: MockRasterSourceParams {
1✔
1290
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1291
                        TimeInterval::default(),
1✔
1292
                        TileInformation {
1✔
1293
                            global_geo_transform: TestDefault::test_default(),
1✔
1294
                            global_tile_position: [0, 0].into(),
1✔
1295
                            tile_size_in_pixels,
1✔
1296
                        },
1✔
1297
                        Grid2D::new(tile_size_in_pixels, vec![1, 2, 0, 4, 0, 6, 7, 0])
1✔
1298
                            .unwrap()
1✔
1299
                            .into(),
1✔
1300
                    )],
1✔
1301
                    result_descriptor: RasterResultDescriptor {
1✔
1302
                        data_type: RasterDataType::U8,
1✔
1303
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1304
                        measurement: Measurement::Unitless,
1✔
1305
                        time: None,
1✔
1306
                        bbox: None,
1✔
1307
                        resolution: None,
1✔
1308
                    },
1✔
1309
                },
1✔
1310
            }
1✔
1311
            .boxed()
1✔
1312
            .into(),
1✔
1313
        };
1✔
1314

1315
        let query_processor = histogram
1✔
1316
            .boxed()
1✔
1317
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1318
            .await
×
1319
            .unwrap()
1✔
1320
            .query_processor()
1✔
1321
            .unwrap()
1✔
1322
            .json_vega()
1✔
1323
            .unwrap();
1✔
1324

1325
        let result = query_processor
1✔
1326
            .plot_query(
1✔
1327
                VectorQueryRectangle {
1✔
1328
                    spatial_bounds: BoundingBox2D::new((0., -4.).into(), (2., 0.).into()).unwrap(),
1✔
1329
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1330
                        2013, 12, 1, 12, 0, 0,
1✔
1331
                    ))
1✔
1332
                    .unwrap(),
1✔
1333
                    spatial_resolution: SpatialResolution::one(),
1✔
1334
                },
1✔
1335
                &MockQueryContext::test_default(),
1✔
1336
            )
1✔
1337
            .await
×
1338
            .unwrap();
1✔
1339

1✔
1340
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1341
        expected.add_attribute(
1✔
1342
            BoxPlotAttribute::new("Raster-1".to_string(), 0.0, 7.0, 1.5, 0.0, 5.0, true).unwrap(),
1✔
1343
        );
1✔
1344

1✔
1345
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1346
    }
1347

1348
    #[tokio::test]
1✔
1349
    async fn multiple_rasters_with_no_data_exclude_no_data() {
1✔
1350
        let tile_size_in_pixels = [4, 2].into();
1✔
1351
        let tiling_specification = TilingSpecification {
1✔
1352
            origin_coordinate: [0.0, 0.0].into(),
1✔
1353
            tile_size_in_pixels,
1✔
1354
        };
1✔
1355
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1356

1✔
1357
        let src = MockRasterSource {
1✔
1358
            params: MockRasterSourceParams {
1✔
1359
                data: vec![RasterTile2D::new_with_tile_info(
1✔
1360
                    TimeInterval::default(),
1✔
1361
                    TileInformation {
1✔
1362
                        global_geo_transform: TestDefault::test_default(),
1✔
1363
                        global_tile_position: [0, 0].into(),
1✔
1364
                        tile_size_in_pixels,
1✔
1365
                    },
1✔
1366
                    MaskedGrid2D::new(
1✔
1367
                        Grid2D::new(tile_size_in_pixels, vec![1, 2, 0, 4, 0, 6, 7, 0]).unwrap(),
1✔
1368
                        Grid2D::new(
1✔
1369
                            tile_size_in_pixels,
1✔
1370
                            vec![true, true, false, true, false, true, true, false],
1✔
1371
                        )
1✔
1372
                        .unwrap(),
1✔
1373
                    )
1✔
1374
                    .unwrap()
1✔
1375
                    .into(),
1✔
1376
                )],
1✔
1377
                result_descriptor: RasterResultDescriptor {
1✔
1378
                    data_type: RasterDataType::U8,
1✔
1379
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1380
                    measurement: Measurement::Unitless,
1✔
1381
                    time: None,
1✔
1382
                    bbox: None,
1✔
1383
                    resolution: None,
1✔
1384
                },
1✔
1385
            },
1✔
1386
        };
1✔
1387

1✔
1388
        let histogram = BoxPlot {
1✔
1389
            params: BoxPlotParams {
1✔
1390
                column_names: vec![],
1✔
1391
            },
1✔
1392
            sources: vec![
1✔
1393
                src.clone().boxed(),
1✔
1394
                src.clone().boxed(),
1✔
1395
                src.clone().boxed(),
1✔
1396
            ]
1✔
1397
            .into(),
1✔
1398
        };
1✔
1399

1400
        let query_processor = histogram
1✔
1401
            .boxed()
1✔
1402
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1403
            .await
×
1404
            .unwrap()
1✔
1405
            .query_processor()
1✔
1406
            .unwrap()
1✔
1407
            .json_vega()
1✔
1408
            .unwrap();
1✔
1409

1410
        let result = query_processor
1✔
1411
            .plot_query(
1✔
1412
                VectorQueryRectangle {
1✔
1413
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1414
                        .unwrap(),
1✔
1415
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1416
                        2013, 12, 1, 12, 0, 0,
1✔
1417
                    ))
1✔
1418
                    .unwrap(),
1✔
1419
                    spatial_resolution: SpatialResolution::one(),
1✔
1420
                },
1✔
1421
                &MockQueryContext::test_default(),
1✔
1422
            )
1✔
1423
            .await
×
1424
            .unwrap();
1✔
1425

1✔
1426
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1427
        expected.add_attribute(
1✔
1428
            BoxPlotAttribute::new("Raster-1".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1429
        );
1✔
1430
        expected.add_attribute(
1✔
1431
            BoxPlotAttribute::new("Raster-2".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1432
        );
1✔
1433
        expected.add_attribute(
1✔
1434
            BoxPlotAttribute::new("Raster-3".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1435
        );
1✔
1436

1✔
1437
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1438
    }
1439
}
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