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

geo-engine / geoengine / 3929938005

pending completion
3929938005

push

github

GitHub
Merge #713

84930 of 96741 relevant lines covered (87.79%)

79640.1 hits per line

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

95.38
/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
    CreateSpan, ExecutionContext, InitializedPlotOperator, InitializedRasterOperator,
16
    InitializedVectorOperator, MultipleRasterOrSingleVectorSource, Operator, OperatorName,
17
    PlotOperator, PlotQueryProcessor, PlotResultDescriptor, QueryContext, QueryProcessor,
18
    TypedPlotQueryProcessor, TypedRasterQueryProcessor, TypedVectorQueryProcessor,
19
};
20
use crate::error::{self, Error};
21
use crate::util::input::MultiRasterOrVectorOperator;
22
use crate::util::statistics::PSquareQuantileEstimator;
23
use crate::util::Result;
24
use snafu::ensure;
25
use tracing::{span, Level};
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)]
4✔
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
        context: &dyn ExecutionContext,
14✔
54
    ) -> Result<Box<dyn InitializedPlotOperator>> {
14✔
55
        match self.sources.source {
14✔
56
            MultiRasterOrVectorOperator::Raster(raster_sources) => {
7✔
57
                ensure!(
7✔
58
                    (1..=MAX_NUMBER_OF_RASTER_INPUTS).contains(&raster_sources.len()),
7✔
59
                    error::InvalidNumberOfRasterInputs {
×
60
                        expected: 1..MAX_NUMBER_OF_RASTER_INPUTS,
×
61
                        found: raster_sources.len()
×
62
                    }
×
63
                );
64
                ensure!( self.params.column_names.is_empty() || self.params.column_names.len() == raster_sources.len(),
7✔
65
                    error::InvalidOperatorSpec {
×
66
                        reason: "BoxPlot on raster data must either contain a name/alias for every input ('column_names' parameter) or no names at all."
×
67
                            .to_string(),
×
68
                });
×
69

70
                let output_names = if self.params.column_names.is_empty() {
7✔
71
                    (1..=raster_sources.len())
7✔
72
                        .map(|i| format!("Raster-{i}"))
9✔
73
                        .collect::<Vec<_>>()
7✔
74
                } else {
75
                    self.params.column_names.clone()
×
76
                };
77

78
                let initialized = futures::future::join_all(
7✔
79
                    raster_sources.into_iter().map(|op| op.initialize(context)),
9✔
80
                )
7✔
81
                .await
×
82
                .into_iter()
7✔
83
                .collect::<Result<Vec<_>>>()?;
7✔
84

85
                if initialized.len() > 1 {
7✔
86
                    let srs = initialized[0].result_descriptor().spatial_reference;
1✔
87
                    ensure!(
1✔
88
                        initialized
1✔
89
                            .iter()
1✔
90
                            .all(|op| op.result_descriptor().spatial_reference == srs),
3✔
91
                        error::AllSourcesMustHaveSameSpatialReference
×
92
                    );
93
                }
6✔
94

95
                let in_descriptors = initialized
7✔
96
                    .iter()
7✔
97
                    .map(InitializedRasterOperator::result_descriptor)
7✔
98
                    .collect::<Vec<_>>();
7✔
99

7✔
100
                let time = time_interval_extent(in_descriptors.iter().map(|d| d.time));
7✔
101
                let bbox = partitions_extent(in_descriptors.iter().map(|d| d.bbox));
7✔
102

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

124
                let source = vector_source.initialize(context).await?;
6✔
125
                for cn in &self.params.column_names {
12✔
126
                    match source.result_descriptor().column_data_type(cn.as_str()) {
7✔
127
                        Some(column) if !column.is_numeric() => {
7✔
128
                            return Err(Error::InvalidOperatorSpec {
1✔
129
                                reason: format!("Column '{cn}' is not numeric."),
1✔
130
                            });
1✔
131
                        }
132
                        Some(_) => {
6✔
133
                            // OK
6✔
134
                        }
6✔
135
                        None => {
136
                            return Err(Error::ColumnDoesNotExist {
×
137
                                column: cn.to_string(),
×
138
                            });
×
139
                        }
140
                    }
141
                }
142

143
                let in_desc = source.result_descriptor();
5✔
144

5✔
145
                Ok(InitializedBoxPlot::new(
5✔
146
                    PlotResultDescriptor {
5✔
147
                        spatial_reference: in_desc.spatial_reference,
5✔
148
                        time: in_desc.time,
5✔
149
                        bbox: in_desc.bbox,
5✔
150
                    },
5✔
151
                    self.params.column_names.clone(),
5✔
152
                    source,
5✔
153
                )
5✔
154
                .boxed())
5✔
155
            }
156
        }
157
    }
28✔
158

159
    span_fn!(BoxPlot);
×
160
}
161

162
/// The initialization of `BoxPlot`
163
pub struct InitializedBoxPlot<Op> {
164
    result_descriptor: PlotResultDescriptor,
165
    names: Vec<String>,
166

167
    source: Op,
168
}
169

170
impl<Op> InitializedBoxPlot<Op> {
171
    pub fn new(result_descriptor: PlotResultDescriptor, names: Vec<String>, source: Op) -> Self {
12✔
172
        Self {
12✔
173
            result_descriptor,
12✔
174
            names,
12✔
175
            source,
12✔
176
        }
12✔
177
    }
12✔
178
}
179
impl InitializedPlotOperator for InitializedBoxPlot<Box<dyn InitializedVectorOperator>> {
180
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
181
        &self.result_descriptor
×
182
    }
×
183

184
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
5✔
185
        let processor = BoxPlotVectorQueryProcessor {
5✔
186
            input: self.source.query_processor()?,
5✔
187
            column_names: self.names.clone(),
5✔
188
        };
5✔
189

5✔
190
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
5✔
191
    }
5✔
192
}
193

194
impl InitializedPlotOperator for InitializedBoxPlot<Vec<Box<dyn InitializedRasterOperator>>> {
195
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
196
        &self.result_descriptor
×
197
    }
×
198

199
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
7✔
200
        let input = self
7✔
201
            .source
7✔
202
            .iter()
7✔
203
            .map(InitializedRasterOperator::query_processor)
7✔
204
            .collect::<Result<Vec<_>>>()?;
7✔
205

206
        let processor = BoxPlotRasterQueryProcessor {
7✔
207
            input,
7✔
208
            names: self.names.clone(),
7✔
209
        };
7✔
210
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
7✔
211
    }
7✔
212
}
213

214
/// A query processor that calculates the boxplots about its vector input.
215
pub struct BoxPlotVectorQueryProcessor {
216
    input: TypedVectorQueryProcessor,
217
    column_names: Vec<String>,
218
}
219

220
#[async_trait]
221
impl PlotQueryProcessor for BoxPlotVectorQueryProcessor {
222
    type OutputFormat = PlotData;
223

224
    fn plot_type(&self) -> &'static str {
×
225
        BOXPLOT_OPERATOR_NAME
×
226
    }
×
227

228
    async fn plot_query<'p>(
5✔
229
        &'p self,
5✔
230
        query: VectorQueryRectangle,
5✔
231
        ctx: &'p dyn QueryContext,
5✔
232
    ) -> Result<Self::OutputFormat> {
5✔
233
        let mut accums: Vec<BoxPlotAccum> = self
5✔
234
            .column_names
5✔
235
            .iter()
5✔
236
            .map(|name| BoxPlotAccum::new(name.clone()))
6✔
237
            .collect();
5✔
238

239
        call_on_generic_vector_processor!(&self.input, processor => {
5✔
240
            let mut query = processor.query(query, ctx).await?;
5✔
241
            while let Some(collection) = query.next().await {
11✔
242
                let collection = collection?;
6✔
243

244
                for accum in &mut accums {
14✔
245
                    let feature_data = collection.data(&accum.name).expect("checked in param");
8✔
246
                    let iter = feature_data.float_options_iter().map(|o| match o {
20,032✔
247
                        Some(v) => v,
20,030✔
248
                        None => f64::NAN,
2✔
249
                    });
20,032✔
250
                    accum.update(iter)?;
8✔
251
                }
252
            }
253
        });
254

255
        let mut chart = geoengine_datatypes::plots::BoxPlot::new();
5✔
256
        for accum in &mut accums {
11✔
257
            if let Some(attrib) = accum.finish()? {
6✔
258
                chart.add_attribute(attrib);
5✔
259
            }
5✔
260
        }
261
        Ok(chart.to_vega_embeddable(false)?)
5✔
262
    }
10✔
263
}
264

265
/// A query processor that calculates the boxplots about its raster input.
266
pub struct BoxPlotRasterQueryProcessor {
267
    input: Vec<TypedRasterQueryProcessor>,
268
    names: Vec<String>,
269
}
270

271
impl BoxPlotRasterQueryProcessor {
272
    async fn process_raster(
9✔
273
        name: String,
9✔
274
        input: &TypedRasterQueryProcessor,
9✔
275
        query: PlotQueryRectangle,
9✔
276
        ctx: &dyn QueryContext,
9✔
277
    ) -> Result<Option<BoxPlotAttribute>> {
9✔
278
        call_on_generic_raster_processor!(input, processor => {
9✔
279

280

281
            let mut stream = processor.query(query.into(), ctx).await?;
9✔
282
            let mut accum = BoxPlotAccum::new(name);
9✔
283

284
            while let Some(tile) = stream.next().await {
57,252✔
285
                let tile = tile?;
57,243✔
286

287
                match tile.grid_array {
57,243✔
288
                    // Ignore empty grids if no_data should not be included
289
                    GridOrEmpty::Empty(_) => {},
57,236✔
290
                    GridOrEmpty::Grid(grid) => {
7✔
291
                        accum.update(grid.masked_element_deref_iterator().filter_map(|pixel_option| pixel_option.map(|p| { let v: f64 = p.as_(); v})))?;
52✔
292
                    }
293
                }
294
            }
295
            accum.finish()
9✔
296
        })
297
    }
9✔
298
}
299

300
#[async_trait]
301
impl PlotQueryProcessor for BoxPlotRasterQueryProcessor {
302
    type OutputFormat = PlotData;
303

304
    fn plot_type(&self) -> &'static str {
×
305
        BOXPLOT_OPERATOR_NAME
×
306
    }
×
307

308
    async fn plot_query<'p>(
7✔
309
        &'p self,
7✔
310
        query: PlotQueryRectangle,
7✔
311
        ctx: &'p dyn QueryContext,
7✔
312
    ) -> Result<Self::OutputFormat> {
7✔
313
        let results: Vec<_> = self
7✔
314
            .input
7✔
315
            .iter()
7✔
316
            .zip(self.names.iter())
7✔
317
            .map(|(proc, name)| Self::process_raster(name.clone(), proc, query, ctx))
9✔
318
            .collect();
7✔
319

320
        let results = futures::future::join_all(results)
7✔
321
            .await
×
322
            .into_iter()
7✔
323
            .collect::<Result<Vec<_>>>();
7✔
324

7✔
325
        let mut chart = geoengine_datatypes::plots::BoxPlot::new();
7✔
326
        results?
7✔
327
            .into_iter()
7✔
328
            .flatten()
7✔
329
            .for_each(|a| chart.add_attribute(a));
7✔
330
        Ok(chart.to_vega_embeddable(false)?)
7✔
331
    }
14✔
332
}
333

334
//
335
// AUX Structures
336
//
337

338
enum BoxPlotAccumKind {
339
    Exact(Vec<f64>),
340
    Estimated(PSquareQuantileEstimator<f64>),
341
}
342

343
impl BoxPlotAccumKind {
344
    fn update(&mut self, values: impl Iterator<Item = f64>) -> crate::util::Result<()> {
33✔
345
        match self {
33✔
346
            Self::Exact(ref mut x) => {
24✔
347
                x.extend(values.filter(|x| x.is_finite()));
11,072✔
348

24✔
349
                if x.len() > EXACT_CALC_BOUND {
24✔
350
                    let est = PSquareQuantileEstimator::new(0.5, x.as_slice())?;
1✔
351
                    *self = Self::Estimated(est);
1✔
352
                }
23✔
353
                Ok(())
24✔
354
            }
355
            Self::Estimated(ref mut est) => {
9✔
356
                for v in values {
9,009✔
357
                    est.update(v);
9,000✔
358
                }
9,000✔
359
                Ok(())
9✔
360
            }
361
        }
362
    }
33✔
363

364
    fn median(values: &[f64]) -> f64 {
30✔
365
        if values.len() % 2 == 0 {
30✔
366
            let i = values.len() / 2;
21✔
367
            (values[i] + values[i - 1]) / 2.0
21✔
368
        } else {
369
            values[values.len() / 2]
9✔
370
        }
371
    }
30✔
372

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

10✔
376
        let s = values.split_at(idx);
10✔
377

10✔
378
        if values.len() % 2 == 0 {
10✔
379
            s
5✔
380
        } else {
381
            (s.0, &s.1[1..])
5✔
382
        }
383
    }
10✔
384

385
    fn create_plot(
15✔
386
        &mut self,
15✔
387
        name: String,
15✔
388
    ) -> Result<Option<geoengine_datatypes::plots::BoxPlotAttribute>> {
15✔
389
        match self {
15✔
390
            Self::Estimated(est) => Ok(Some(BoxPlotAttribute::new(
1✔
391
                name,
1✔
392
                est.min(),
1✔
393
                est.max(),
1✔
394
                est.quantile_estimate(),
1✔
395
                est.marker2(),
1✔
396
                est.marker4(),
1✔
397
                false,
1✔
398
            )?)),
1✔
399
            Self::Exact(v) => {
14✔
400
                match v.len() {
14✔
401
                    0 => Ok(None),
3✔
402
                    1 => {
403
                        let x = v[0];
1✔
404
                        Ok(Some(BoxPlotAttribute::new(name, x, x, x, x, x, true)?))
1✔
405
                    }
406
                    l => {
10✔
407
                        v.sort_unstable_by(|a, b| {
68✔
408
                            a.partial_cmp(b).expect("Infinite values were filtered")
68✔
409
                        });
68✔
410
                        let min = v[0];
10✔
411
                        let max = v[l - 1];
10✔
412
                        // We compute the quartiles accodring to https://en.wikipedia.org/wiki/Quartile#Method_1
10✔
413
                        let median = Self::median(v);
10✔
414
                        let (low, high) = Self::split(v);
10✔
415
                        let q1 = Self::median(low);
10✔
416
                        let q3 = Self::median(high);
10✔
417
                        Ok(Some(BoxPlotAttribute::new(
10✔
418
                            name, min, max, median, q1, q3, true,
10✔
419
                        )?))
10✔
420
                    }
421
                }
422
            }
423
        }
424
    }
15✔
425
}
426

427
struct BoxPlotAccum {
428
    name: String,
429
    accum: BoxPlotAccumKind,
430
}
431

432
impl BoxPlotAccum {
433
    fn new(name: String) -> BoxPlotAccum {
15✔
434
        BoxPlotAccum {
15✔
435
            name,
15✔
436
            accum: BoxPlotAccumKind::Exact(Vec::new()),
15✔
437
        }
15✔
438
    }
15✔
439

440
    fn update(&mut self, values: impl Iterator<Item = f64>) -> crate::util::Result<()> {
15✔
441
        for chunk in &itertools::Itertools::chunks(values, BATCH_SIZE) {
33✔
442
            self.accum.update(chunk)?;
33✔
443
        }
444
        Ok(())
15✔
445
    }
15✔
446

447
    fn finish(&mut self) -> Result<Option<geoengine_datatypes::plots::BoxPlotAttribute>> {
15✔
448
        self.accum.create_plot(self.name.clone())
15✔
449
    }
15✔
450
}
451

452
#[cfg(test)]
453
mod tests {
454
    use serde_json::json;
455

456
    use geoengine_datatypes::primitives::{
457
        BoundingBox2D, DateTime, FeatureData, Measurement, NoGeometry, SpatialResolution,
458
        TimeInterval,
459
    };
460
    use geoengine_datatypes::raster::{
461
        EmptyGrid2D, Grid2D, MaskedGrid2D, RasterDataType, RasterTile2D, TileInformation,
462
        TilingSpecification,
463
    };
464
    use geoengine_datatypes::spatial_reference::SpatialReference;
465
    use geoengine_datatypes::util::test::TestDefault;
466
    use geoengine_datatypes::{collections::DataCollection, primitives::MultiPoint};
467

468
    use crate::engine::{
469
        ChunkByteSize, MockExecutionContext, MockQueryContext, RasterOperator,
470
        RasterResultDescriptor, VectorOperator,
471
    };
472
    use crate::mock::{MockFeatureCollectionSource, MockRasterSource, MockRasterSourceParams};
473

474
    use super::*;
475

476
    #[test]
1✔
477
    fn serialization() {
1✔
478
        let histogram = BoxPlot {
1✔
479
            params: BoxPlotParams {
1✔
480
                column_names: vec!["foobar".to_string()],
1✔
481
            },
1✔
482
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
483
                .boxed()
1✔
484
                .into(),
1✔
485
        };
1✔
486

1✔
487
        let serialized = json!({
1✔
488
            "type": "BoxPlot",
1✔
489
            "params": {
1✔
490
                "columnNames": ["foobar"],
1✔
491
            },
1✔
492
            "sources": {
1✔
493
                "source": {
1✔
494
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
495
                    "params": {
1✔
496
                        "collections": [],
1✔
497
                        "spatialReference": "EPSG:4326",
1✔
498
                        "measurements": {},
1✔
499
                    }
1✔
500
                }
1✔
501
            }
1✔
502
        })
1✔
503
        .to_string();
1✔
504

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

1✔
507
        assert_eq!(deserialized.params, histogram.params);
1✔
508
    }
1✔
509

510
    #[test]
1✔
511
    fn serialization_alt() {
1✔
512
        let histogram = BoxPlot {
1✔
513
            params: BoxPlotParams {
1✔
514
                column_names: vec![],
1✔
515
            },
1✔
516
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
517
                .boxed()
1✔
518
                .into(),
1✔
519
        };
1✔
520

1✔
521
        let serialized = json!({
1✔
522
            "type": "BoxPlot",
1✔
523
            "params": {
1✔
524
            },
1✔
525
            "sources": {
1✔
526
                "source": {
1✔
527
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
528
                    "params": {
1✔
529
                        "collections": [],
1✔
530
                        "spatialReference": "EPSG:4326",
1✔
531
                        "measurements": {},
1✔
532
                    }
1✔
533
                }
1✔
534
            }
1✔
535
        })
1✔
536
        .to_string();
1✔
537

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

1✔
540
        assert_eq!(deserialized.params, histogram.params);
1✔
541
    }
1✔
542

543
    #[tokio::test]
1✔
544
    async fn vector_data() {
1✔
545
        let vector_source = MockFeatureCollectionSource::multiple(vec![
1✔
546
            DataCollection::from_slices(
1✔
547
                &[] as &[NoGeometry],
1✔
548
                &[TimeInterval::default(); 8],
1✔
549
                &[
1✔
550
                    ("foo", FeatureData::Int(vec![1, 1, 2, 2, 3, 3, 4, 4])),
1✔
551
                    ("bar", FeatureData::Int(vec![1, 1, 2, 2, 3, 3, 4, 4])),
1✔
552
                ],
1✔
553
            )
1✔
554
            .unwrap(),
1✔
555
            DataCollection::from_slices(
1✔
556
                &[] as &[NoGeometry],
1✔
557
                &[TimeInterval::default(); 4],
1✔
558
                &[
1✔
559
                    ("foo", FeatureData::Int(vec![5, 6, 7, 8])),
1✔
560
                    ("bar", FeatureData::Int(vec![5, 6, 7, 8])),
1✔
561
                ],
1✔
562
            )
1✔
563
            .unwrap(),
1✔
564
        ])
1✔
565
        .boxed();
1✔
566

1✔
567
        let box_plot = BoxPlot {
1✔
568
            params: BoxPlotParams {
1✔
569
                column_names: vec!["foo".to_string(), "bar".to_string()],
1✔
570
            },
1✔
571
            sources: vector_source.into(),
1✔
572
        };
1✔
573

1✔
574
        let execution_context = MockExecutionContext::test_default();
1✔
575

576
        let query_processor = box_plot
1✔
577
            .boxed()
1✔
578
            .initialize(&execution_context)
1✔
579
            .await
×
580
            .unwrap()
1✔
581
            .query_processor()
1✔
582
            .unwrap()
1✔
583
            .json_vega()
1✔
584
            .unwrap();
1✔
585

586
        let result = query_processor
1✔
587
            .plot_query(
1✔
588
                VectorQueryRectangle {
1✔
589
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
590
                        .unwrap(),
1✔
591
                    time_interval: TimeInterval::default(),
1✔
592
                    spatial_resolution: SpatialResolution::one(),
1✔
593
                },
1✔
594
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
595
            )
1✔
596
            .await
×
597
            .unwrap();
1✔
598

1✔
599
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
600
        expected.add_attribute(
1✔
601
            BoxPlotAttribute::new("foo".to_string(), 1.0, 8.0, 3.5, 2.0, 5.5, true).unwrap(),
1✔
602
        );
1✔
603
        expected.add_attribute(
1✔
604
            BoxPlotAttribute::new("bar".to_string(), 1.0, 8.0, 3.5, 2.0, 5.5, true).unwrap(),
1✔
605
        );
1✔
606

1✔
607
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
608
    }
609

610
    #[tokio::test]
1✔
611
    async fn vector_data_with_nulls() {
1✔
612
        let vector_source = MockFeatureCollectionSource::single(
1✔
613
            DataCollection::from_slices(
1✔
614
                &[] as &[NoGeometry],
1✔
615
                &[TimeInterval::default(); 7],
1✔
616
                &[(
1✔
617
                    "foo",
1✔
618
                    FeatureData::NullableFloat(vec![
1✔
619
                        Some(1.),
1✔
620
                        Some(2.),
1✔
621
                        None,
1✔
622
                        Some(4.),
1✔
623
                        None,
1✔
624
                        Some(6.),
1✔
625
                        Some(7.),
1✔
626
                    ]),
1✔
627
                )],
1✔
628
            )
1✔
629
            .unwrap(),
1✔
630
        )
1✔
631
        .boxed();
1✔
632

1✔
633
        let box_plot = BoxPlot {
1✔
634
            params: BoxPlotParams {
1✔
635
                column_names: vec!["foo".to_string()],
1✔
636
            },
1✔
637
            sources: vector_source.into(),
1✔
638
        };
1✔
639

1✔
640
        let execution_context = MockExecutionContext::test_default();
1✔
641

642
        let query_processor = box_plot
1✔
643
            .boxed()
1✔
644
            .initialize(&execution_context)
1✔
645
            .await
×
646
            .unwrap()
1✔
647
            .query_processor()
1✔
648
            .unwrap()
1✔
649
            .json_vega()
1✔
650
            .unwrap();
1✔
651

652
        let result = query_processor
1✔
653
            .plot_query(
1✔
654
                VectorQueryRectangle {
1✔
655
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
656
                        .unwrap(),
1✔
657
                    time_interval: TimeInterval::default(),
1✔
658
                    spatial_resolution: SpatialResolution::one(),
1✔
659
                },
1✔
660
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
661
            )
1✔
662
            .await
×
663
            .unwrap();
1✔
664

1✔
665
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
666
        expected.add_attribute(
1✔
667
            BoxPlotAttribute::new("foo".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
668
        );
1✔
669

1✔
670
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
671
    }
672

673
    #[tokio::test]
1✔
674
    async fn vector_data_text_column() {
1✔
675
        let vector_source = MockFeatureCollectionSource::single(
1✔
676
            DataCollection::from_slices(
1✔
677
                &[] as &[NoGeometry],
1✔
678
                &[TimeInterval::default(); 1],
1✔
679
                &[("foo", FeatureData::Text(vec!["test".to_string()]))],
1✔
680
            )
1✔
681
            .unwrap(),
1✔
682
        )
1✔
683
        .boxed();
1✔
684

1✔
685
        let box_plot = BoxPlot {
1✔
686
            params: BoxPlotParams {
1✔
687
                column_names: vec!["foo".to_string()],
1✔
688
            },
1✔
689
            sources: vector_source.into(),
1✔
690
        };
1✔
691

1✔
692
        let execution_context = MockExecutionContext::test_default();
1✔
693

694
        let init = box_plot.boxed().initialize(&execution_context).await;
1✔
695

696
        assert!(init.is_err());
1✔
697
    }
698

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

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

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

720
        let init = box_plot.boxed().initialize(&execution_context).await;
1✔
721

722
        assert!(init.is_err());
1✔
723
    }
724

725
    #[tokio::test]
1✔
726
    async fn vector_data_single_feature() {
1✔
727
        let vector_source =
1✔
728
            MockFeatureCollectionSource::multiple(vec![DataCollection::from_slices(
1✔
729
                &[] as &[NoGeometry],
1✔
730
                &[TimeInterval::default(); 1],
1✔
731
                &[("foo", FeatureData::Int(vec![1]))],
1✔
732
            )
1✔
733
            .unwrap()])
1✔
734
            .boxed();
1✔
735

1✔
736
        let box_plot = BoxPlot {
1✔
737
            params: BoxPlotParams {
1✔
738
                column_names: vec!["foo".to_string()],
1✔
739
            },
1✔
740
            sources: vector_source.into(),
1✔
741
        };
1✔
742

1✔
743
        let execution_context = MockExecutionContext::test_default();
1✔
744

745
        let query_processor = box_plot
1✔
746
            .boxed()
1✔
747
            .initialize(&execution_context)
1✔
748
            .await
×
749
            .unwrap()
1✔
750
            .query_processor()
1✔
751
            .unwrap()
1✔
752
            .json_vega()
1✔
753
            .unwrap();
1✔
754

755
        let result = query_processor
1✔
756
            .plot_query(
1✔
757
                VectorQueryRectangle {
1✔
758
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
759
                        .unwrap(),
1✔
760
                    time_interval: TimeInterval::default(),
1✔
761
                    spatial_resolution: SpatialResolution::one(),
1✔
762
                },
1✔
763
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
764
            )
1✔
765
            .await
×
766
            .unwrap();
1✔
767

1✔
768
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
769
        expected.add_attribute(
1✔
770
            BoxPlotAttribute::new("foo".to_string(), 1.0, 1.0, 1.0, 1.0, 1.0, true).unwrap(),
1✔
771
        );
1✔
772

1✔
773
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
774
    }
775

776
    #[tokio::test]
1✔
777
    async fn vector_data_empty() {
1✔
778
        let vector_source =
1✔
779
            MockFeatureCollectionSource::multiple(vec![DataCollection::from_slices(
1✔
780
                &[] as &[NoGeometry],
1✔
781
                &[] as &[TimeInterval],
1✔
782
                &[("foo", FeatureData::Int(vec![]))],
1✔
783
            )
1✔
784
            .unwrap()])
1✔
785
            .boxed();
1✔
786

1✔
787
        let box_plot = BoxPlot {
1✔
788
            params: BoxPlotParams {
1✔
789
                column_names: vec!["foo".to_string()],
1✔
790
            },
1✔
791
            sources: vector_source.into(),
1✔
792
        };
1✔
793

1✔
794
        let execution_context = MockExecutionContext::test_default();
1✔
795

796
        let query_processor = box_plot
1✔
797
            .boxed()
1✔
798
            .initialize(&execution_context)
1✔
799
            .await
×
800
            .unwrap()
1✔
801
            .query_processor()
1✔
802
            .unwrap()
1✔
803
            .json_vega()
1✔
804
            .unwrap();
1✔
805

806
        let result = query_processor
1✔
807
            .plot_query(
1✔
808
                VectorQueryRectangle {
1✔
809
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
810
                        .unwrap(),
1✔
811
                    time_interval: TimeInterval::default(),
1✔
812
                    spatial_resolution: SpatialResolution::one(),
1✔
813
                },
1✔
814
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
815
            )
1✔
816
            .await
×
817
            .unwrap();
1✔
818

1✔
819
        let expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
820
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
821
    }
822

823
    #[tokio::test]
1✔
824
    async fn vector_data_estimator_switch() {
1✔
825
        let mut data = Vec::<i64>::with_capacity(2 * super::EXACT_CALC_BOUND);
1✔
826

827
        for i in 1..=data.capacity() as i64 {
20,000✔
828
            data.push(i);
20,000✔
829
        }
20,000✔
830

831
        let vector_source =
1✔
832
            MockFeatureCollectionSource::multiple(vec![DataCollection::from_slices(
1✔
833
                &[] as &[NoGeometry],
1✔
834
                &[TimeInterval::default(); 2 * super::EXACT_CALC_BOUND],
1✔
835
                &[("foo", FeatureData::Int(data))],
1✔
836
            )
1✔
837
            .unwrap()])
1✔
838
            .boxed();
1✔
839

1✔
840
        let box_plot = BoxPlot {
1✔
841
            params: BoxPlotParams {
1✔
842
                column_names: vec!["foo".to_string()],
1✔
843
            },
1✔
844
            sources: vector_source.into(),
1✔
845
        };
1✔
846

1✔
847
        let execution_context = MockExecutionContext::test_default();
1✔
848

849
        let query_processor = box_plot
1✔
850
            .boxed()
1✔
851
            .initialize(&execution_context)
1✔
852
            .await
×
853
            .unwrap()
1✔
854
            .query_processor()
1✔
855
            .unwrap()
1✔
856
            .json_vega()
1✔
857
            .unwrap();
1✔
858

859
        let result = query_processor
1✔
860
            .plot_query(
1✔
861
                VectorQueryRectangle {
1✔
862
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
863
                        .unwrap(),
1✔
864
                    time_interval: TimeInterval::default(),
1✔
865
                    spatial_resolution: SpatialResolution::one(),
1✔
866
                },
1✔
867
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
868
            )
1✔
869
            .await
×
870
            .unwrap();
1✔
871

1✔
872
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
873
        expected.add_attribute(
1✔
874
            BoxPlotAttribute::new(
1✔
875
                "foo".to_string(),
1✔
876
                1.0,
1✔
877
                2.0 * super::EXACT_CALC_BOUND as f64,
1✔
878
                super::EXACT_CALC_BOUND as f64,
1✔
879
                0.5 * super::EXACT_CALC_BOUND as f64,
1✔
880
                1.5 * super::EXACT_CALC_BOUND as f64,
1✔
881
                false,
1✔
882
            )
1✔
883
            .unwrap(),
1✔
884
        );
1✔
885

1✔
886
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
887
    }
888

889
    #[tokio::test]
1✔
890
    async fn no_data_raster_exclude_no_data() {
1✔
891
        let tile_size_in_pixels = [3, 2].into();
1✔
892
        let tiling_specification = TilingSpecification {
1✔
893
            origin_coordinate: [0.0, 0.0].into(),
1✔
894
            tile_size_in_pixels,
1✔
895
        };
1✔
896
        let box_plot = BoxPlot {
1✔
897
            params: BoxPlotParams {
1✔
898
                column_names: vec![],
1✔
899
            },
1✔
900
            sources: MockRasterSource {
1✔
901
                params: MockRasterSourceParams {
1✔
902
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
903
                        TimeInterval::default(),
1✔
904
                        TileInformation {
1✔
905
                            global_geo_transform: TestDefault::test_default(),
1✔
906
                            global_tile_position: [0, 0].into(),
1✔
907
                            tile_size_in_pixels,
1✔
908
                        },
1✔
909
                        EmptyGrid2D::<u8>::new(tile_size_in_pixels).into(),
1✔
910
                    )],
1✔
911
                    result_descriptor: RasterResultDescriptor {
1✔
912
                        data_type: RasterDataType::U8,
1✔
913
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
914
                        measurement: Measurement::Unitless,
1✔
915
                        time: None,
1✔
916
                        bbox: None,
1✔
917
                        resolution: None,
1✔
918
                    },
1✔
919
                },
1✔
920
            }
1✔
921
            .boxed()
1✔
922
            .into(),
1✔
923
        };
1✔
924

1✔
925
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
926

927
        let query_processor = box_plot
1✔
928
            .boxed()
1✔
929
            .initialize(&execution_context)
1✔
930
            .await
×
931
            .unwrap()
1✔
932
            .query_processor()
1✔
933
            .unwrap()
1✔
934
            .json_vega()
1✔
935
            .unwrap();
1✔
936

937
        let result = query_processor
1✔
938
            .plot_query(
1✔
939
                VectorQueryRectangle {
1✔
940
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
941
                        .unwrap(),
1✔
942
                    time_interval: TimeInterval::default(),
1✔
943
                    spatial_resolution: SpatialResolution::one(),
1✔
944
                },
1✔
945
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
946
            )
1✔
947
            .await
×
948
            .unwrap();
1✔
949

1✔
950
        let expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
951

1✔
952
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
953
    }
954

955
    #[tokio::test]
1✔
956
    async fn no_data_raster_include_no_data() {
1✔
957
        let tile_size_in_pixels = [3, 2].into();
1✔
958
        let tiling_specification = TilingSpecification {
1✔
959
            origin_coordinate: [0.0, 0.0].into(),
1✔
960
            tile_size_in_pixels,
1✔
961
        };
1✔
962
        let box_plot = BoxPlot {
1✔
963
            params: BoxPlotParams {
1✔
964
                column_names: vec![],
1✔
965
            },
1✔
966
            sources: MockRasterSource {
1✔
967
                params: MockRasterSourceParams {
1✔
968
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
969
                        TimeInterval::default(),
1✔
970
                        TileInformation {
1✔
971
                            global_geo_transform: TestDefault::test_default(),
1✔
972
                            global_tile_position: [0, 0].into(),
1✔
973
                            tile_size_in_pixels,
1✔
974
                        },
1✔
975
                        Grid2D::new(tile_size_in_pixels, vec![0, 0, 0, 0, 0, 0])
1✔
976
                            .unwrap()
1✔
977
                            .into(),
1✔
978
                    )],
1✔
979
                    result_descriptor: RasterResultDescriptor {
1✔
980
                        data_type: RasterDataType::U8,
1✔
981
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
982
                        measurement: Measurement::Unitless,
1✔
983
                        time: None,
1✔
984
                        bbox: None,
1✔
985
                        resolution: None,
1✔
986
                    },
1✔
987
                },
1✔
988
            }
1✔
989
            .boxed()
1✔
990
            .into(),
1✔
991
        };
1✔
992

1✔
993
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
994

995
        let query_processor = box_plot
1✔
996
            .boxed()
1✔
997
            .initialize(&execution_context)
1✔
998
            .await
×
999
            .unwrap()
1✔
1000
            .query_processor()
1✔
1001
            .unwrap()
1✔
1002
            .json_vega()
1✔
1003
            .unwrap();
1✔
1004

1005
        let result = query_processor
1✔
1006
            .plot_query(
1✔
1007
                VectorQueryRectangle {
1✔
1008
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
1009
                    time_interval: TimeInterval::default(),
1✔
1010
                    spatial_resolution: SpatialResolution::one(),
1✔
1011
                },
1✔
1012
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1013
            )
1✔
1014
            .await
×
1015
            .unwrap();
1✔
1016

1✔
1017
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1018
        expected.add_attribute(
1✔
1019
            BoxPlotAttribute::new("Raster-1".to_owned(), 0.0, 0.0, 0.0, 0.0, 0.0, true).unwrap(),
1✔
1020
        );
1✔
1021

1✔
1022
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1023
    }
1024

1025
    #[tokio::test]
1✔
1026
    async fn empty_tile_raster_exclude_no_data() {
1✔
1027
        let tile_size_in_pixels = [3, 2].into();
1✔
1028
        let tiling_specification = TilingSpecification {
1✔
1029
            origin_coordinate: [0.0, 0.0].into(),
1✔
1030
            tile_size_in_pixels,
1✔
1031
        };
1✔
1032
        let box_plot = BoxPlot {
1✔
1033
            params: BoxPlotParams {
1✔
1034
                column_names: vec![],
1✔
1035
            },
1✔
1036
            sources: MockRasterSource {
1✔
1037
                params: MockRasterSourceParams {
1✔
1038
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1039
                        TimeInterval::default(),
1✔
1040
                        TileInformation {
1✔
1041
                            global_geo_transform: TestDefault::test_default(),
1✔
1042
                            global_tile_position: [0, 0].into(),
1✔
1043
                            tile_size_in_pixels,
1✔
1044
                        },
1✔
1045
                        EmptyGrid2D::<u8>::new(tile_size_in_pixels).into(),
1✔
1046
                    )],
1✔
1047
                    result_descriptor: RasterResultDescriptor {
1✔
1048
                        data_type: RasterDataType::U8,
1✔
1049
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1050
                        measurement: Measurement::Unitless,
1✔
1051
                        time: None,
1✔
1052
                        bbox: None,
1✔
1053
                        resolution: None,
1✔
1054
                    },
1✔
1055
                },
1✔
1056
            }
1✔
1057
            .boxed()
1✔
1058
            .into(),
1✔
1059
        };
1✔
1060

1✔
1061
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1062

1063
        let query_processor = box_plot
1✔
1064
            .boxed()
1✔
1065
            .initialize(&execution_context)
1✔
1066
            .await
×
1067
            .unwrap()
1✔
1068
            .query_processor()
1✔
1069
            .unwrap()
1✔
1070
            .json_vega()
1✔
1071
            .unwrap();
1✔
1072

1073
        let result = query_processor
1✔
1074
            .plot_query(
1✔
1075
                VectorQueryRectangle {
1✔
1076
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1077
                        .unwrap(),
1✔
1078
                    time_interval: TimeInterval::default(),
1✔
1079
                    spatial_resolution: SpatialResolution::one(),
1✔
1080
                },
1✔
1081
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1082
            )
1✔
1083
            .await
×
1084
            .unwrap();
1✔
1085

1✔
1086
        let expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1087

1✔
1088
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1089
    }
1090

1091
    #[tokio::test]
1✔
1092
    async fn single_value_raster_stream() {
1✔
1093
        let tile_size_in_pixels = [3, 2].into();
1✔
1094
        let tiling_specification = TilingSpecification {
1✔
1095
            origin_coordinate: [0.0, 0.0].into(),
1✔
1096
            tile_size_in_pixels,
1✔
1097
        };
1✔
1098
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1099
        let histogram = BoxPlot {
1✔
1100
            params: BoxPlotParams {
1✔
1101
                column_names: vec![],
1✔
1102
            },
1✔
1103
            sources: MockRasterSource {
1✔
1104
                params: MockRasterSourceParams {
1✔
1105
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1106
                        TimeInterval::default(),
1✔
1107
                        TileInformation {
1✔
1108
                            global_geo_transform: TestDefault::test_default(),
1✔
1109
                            global_tile_position: [0, 0].into(),
1✔
1110
                            tile_size_in_pixels,
1✔
1111
                        },
1✔
1112
                        Grid2D::new(tile_size_in_pixels, vec![4; 6]).unwrap().into(),
1✔
1113
                    )],
1✔
1114
                    result_descriptor: RasterResultDescriptor {
1✔
1115
                        data_type: RasterDataType::U8,
1✔
1116
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1117
                        measurement: Measurement::Unitless,
1✔
1118
                        time: None,
1✔
1119
                        bbox: None,
1✔
1120
                        resolution: None,
1✔
1121
                    },
1✔
1122
                },
1✔
1123
            }
1✔
1124
            .boxed()
1✔
1125
            .into(),
1✔
1126
        };
1✔
1127

1128
        let query_processor = histogram
1✔
1129
            .boxed()
1✔
1130
            .initialize(&execution_context)
1✔
1131
            .await
×
1132
            .unwrap()
1✔
1133
            .query_processor()
1✔
1134
            .unwrap()
1✔
1135
            .json_vega()
1✔
1136
            .unwrap();
1✔
1137

1138
        let result = query_processor
1✔
1139
            .plot_query(
1✔
1140
                VectorQueryRectangle {
1✔
1141
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1142
                        .unwrap(),
1✔
1143
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1144
                        2013, 12, 1, 12, 0, 0,
1✔
1145
                    ))
1✔
1146
                    .unwrap(),
1✔
1147
                    spatial_resolution: SpatialResolution::one(),
1✔
1148
                },
1✔
1149
                &MockQueryContext::test_default(),
1✔
1150
            )
1✔
1151
            .await
×
1152
            .unwrap();
1✔
1153

1✔
1154
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1155
        expected.add_attribute(
1✔
1156
            BoxPlotAttribute::new("Raster-1".to_owned(), 4.0, 4.0, 4.0, 4.0, 4.0, true).unwrap(),
1✔
1157
        );
1✔
1158

1✔
1159
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1160
    }
1161

1162
    #[tokio::test]
1✔
1163
    async fn raster_with_no_data_exclude_no_data() {
1✔
1164
        let tile_size_in_pixels = [4, 2].into();
1✔
1165
        let tiling_specification = TilingSpecification {
1✔
1166
            origin_coordinate: [0.0, 0.0].into(),
1✔
1167
            tile_size_in_pixels,
1✔
1168
        };
1✔
1169
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1170

1✔
1171
        let histogram = BoxPlot {
1✔
1172
            params: BoxPlotParams {
1✔
1173
                column_names: vec![],
1✔
1174
            },
1✔
1175
            sources: MockRasterSource {
1✔
1176
                params: MockRasterSourceParams {
1✔
1177
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1178
                        TimeInterval::default(),
1✔
1179
                        TileInformation {
1✔
1180
                            global_geo_transform: TestDefault::test_default(),
1✔
1181
                            global_tile_position: [0, 0].into(),
1✔
1182
                            tile_size_in_pixels,
1✔
1183
                        },
1✔
1184
                        MaskedGrid2D::new(
1✔
1185
                            Grid2D::new(tile_size_in_pixels, vec![1, 2, 0, 4, 0, 6, 7, 0]).unwrap(),
1✔
1186
                            Grid2D::new(
1✔
1187
                                tile_size_in_pixels,
1✔
1188
                                vec![true, true, false, true, false, true, true, false],
1✔
1189
                            )
1✔
1190
                            .unwrap(),
1✔
1191
                        )
1✔
1192
                        .unwrap()
1✔
1193
                        .into(),
1✔
1194
                    )],
1✔
1195
                    result_descriptor: RasterResultDescriptor {
1✔
1196
                        data_type: RasterDataType::U8,
1✔
1197
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1198
                        measurement: Measurement::Unitless,
1✔
1199
                        time: None,
1✔
1200
                        bbox: None,
1✔
1201
                        resolution: None,
1✔
1202
                    },
1✔
1203
                },
1✔
1204
            }
1✔
1205
            .boxed()
1✔
1206
            .into(),
1✔
1207
        };
1✔
1208

1209
        let query_processor = histogram
1✔
1210
            .boxed()
1✔
1211
            .initialize(&execution_context)
1✔
1212
            .await
×
1213
            .unwrap()
1✔
1214
            .query_processor()
1✔
1215
            .unwrap()
1✔
1216
            .json_vega()
1✔
1217
            .unwrap();
1✔
1218

1219
        let result = query_processor
1✔
1220
            .plot_query(
1✔
1221
                VectorQueryRectangle {
1✔
1222
                    spatial_bounds: BoundingBox2D::new((0., -4.).into(), (2., 0.).into()).unwrap(),
1✔
1223
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1224
                        2013, 12, 1, 12, 0, 0,
1✔
1225
                    ))
1✔
1226
                    .unwrap(),
1✔
1227
                    spatial_resolution: SpatialResolution::one(),
1✔
1228
                },
1✔
1229
                &MockQueryContext::test_default(),
1✔
1230
            )
1✔
1231
            .await
×
1232
            .unwrap();
1✔
1233

1✔
1234
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1235
        expected.add_attribute(
1✔
1236
            BoxPlotAttribute::new("Raster-1".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1237
        );
1✔
1238

1✔
1239
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1240
    }
1241

1242
    #[tokio::test]
1✔
1243
    async fn raster_with_no_data_include_no_data() {
1✔
1244
        let tile_size_in_pixels = [4, 2].into();
1✔
1245
        let tiling_specification = TilingSpecification {
1✔
1246
            origin_coordinate: [0.0, 0.0].into(),
1✔
1247
            tile_size_in_pixels,
1✔
1248
        };
1✔
1249
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1250

1✔
1251
        let histogram = BoxPlot {
1✔
1252
            params: BoxPlotParams {
1✔
1253
                column_names: vec![],
1✔
1254
            },
1✔
1255
            sources: MockRasterSource {
1✔
1256
                params: MockRasterSourceParams {
1✔
1257
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1258
                        TimeInterval::default(),
1✔
1259
                        TileInformation {
1✔
1260
                            global_geo_transform: TestDefault::test_default(),
1✔
1261
                            global_tile_position: [0, 0].into(),
1✔
1262
                            tile_size_in_pixels,
1✔
1263
                        },
1✔
1264
                        Grid2D::new(tile_size_in_pixels, vec![1, 2, 0, 4, 0, 6, 7, 0])
1✔
1265
                            .unwrap()
1✔
1266
                            .into(),
1✔
1267
                    )],
1✔
1268
                    result_descriptor: RasterResultDescriptor {
1✔
1269
                        data_type: RasterDataType::U8,
1✔
1270
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1271
                        measurement: Measurement::Unitless,
1✔
1272
                        time: None,
1✔
1273
                        bbox: None,
1✔
1274
                        resolution: None,
1✔
1275
                    },
1✔
1276
                },
1✔
1277
            }
1✔
1278
            .boxed()
1✔
1279
            .into(),
1✔
1280
        };
1✔
1281

1282
        let query_processor = histogram
1✔
1283
            .boxed()
1✔
1284
            .initialize(&execution_context)
1✔
1285
            .await
×
1286
            .unwrap()
1✔
1287
            .query_processor()
1✔
1288
            .unwrap()
1✔
1289
            .json_vega()
1✔
1290
            .unwrap();
1✔
1291

1292
        let result = query_processor
1✔
1293
            .plot_query(
1✔
1294
                VectorQueryRectangle {
1✔
1295
                    spatial_bounds: BoundingBox2D::new((0., -4.).into(), (2., 0.).into()).unwrap(),
1✔
1296
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1297
                        2013, 12, 1, 12, 0, 0,
1✔
1298
                    ))
1✔
1299
                    .unwrap(),
1✔
1300
                    spatial_resolution: SpatialResolution::one(),
1✔
1301
                },
1✔
1302
                &MockQueryContext::test_default(),
1✔
1303
            )
1✔
1304
            .await
×
1305
            .unwrap();
1✔
1306

1✔
1307
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1308
        expected.add_attribute(
1✔
1309
            BoxPlotAttribute::new("Raster-1".to_string(), 0.0, 7.0, 1.5, 0.0, 5.0, true).unwrap(),
1✔
1310
        );
1✔
1311

1✔
1312
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1313
    }
1314

1315
    #[tokio::test]
1✔
1316
    async fn multiple_rasters_with_no_data_exclude_no_data() {
1✔
1317
        let tile_size_in_pixels = [4, 2].into();
1✔
1318
        let tiling_specification = TilingSpecification {
1✔
1319
            origin_coordinate: [0.0, 0.0].into(),
1✔
1320
            tile_size_in_pixels,
1✔
1321
        };
1✔
1322
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1323

1✔
1324
        let src = MockRasterSource {
1✔
1325
            params: MockRasterSourceParams {
1✔
1326
                data: vec![RasterTile2D::new_with_tile_info(
1✔
1327
                    TimeInterval::default(),
1✔
1328
                    TileInformation {
1✔
1329
                        global_geo_transform: TestDefault::test_default(),
1✔
1330
                        global_tile_position: [0, 0].into(),
1✔
1331
                        tile_size_in_pixels,
1✔
1332
                    },
1✔
1333
                    MaskedGrid2D::new(
1✔
1334
                        Grid2D::new(tile_size_in_pixels, vec![1, 2, 0, 4, 0, 6, 7, 0]).unwrap(),
1✔
1335
                        Grid2D::new(
1✔
1336
                            tile_size_in_pixels,
1✔
1337
                            vec![true, true, false, true, false, true, true, false],
1✔
1338
                        )
1✔
1339
                        .unwrap(),
1✔
1340
                    )
1✔
1341
                    .unwrap()
1✔
1342
                    .into(),
1✔
1343
                )],
1✔
1344
                result_descriptor: RasterResultDescriptor {
1✔
1345
                    data_type: RasterDataType::U8,
1✔
1346
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1347
                    measurement: Measurement::Unitless,
1✔
1348
                    time: None,
1✔
1349
                    bbox: None,
1✔
1350
                    resolution: None,
1✔
1351
                },
1✔
1352
            },
1✔
1353
        };
1✔
1354

1✔
1355
        let histogram = BoxPlot {
1✔
1356
            params: BoxPlotParams {
1✔
1357
                column_names: vec![],
1✔
1358
            },
1✔
1359
            sources: vec![
1✔
1360
                src.clone().boxed(),
1✔
1361
                src.clone().boxed(),
1✔
1362
                src.clone().boxed(),
1✔
1363
            ]
1✔
1364
            .into(),
1✔
1365
        };
1✔
1366

1367
        let query_processor = histogram
1✔
1368
            .boxed()
1✔
1369
            .initialize(&execution_context)
1✔
1370
            .await
×
1371
            .unwrap()
1✔
1372
            .query_processor()
1✔
1373
            .unwrap()
1✔
1374
            .json_vega()
1✔
1375
            .unwrap();
1✔
1376

1377
        let result = query_processor
1✔
1378
            .plot_query(
1✔
1379
                VectorQueryRectangle {
1✔
1380
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1381
                        .unwrap(),
1✔
1382
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1383
                        2013, 12, 1, 12, 0, 0,
1✔
1384
                    ))
1✔
1385
                    .unwrap(),
1✔
1386
                    spatial_resolution: SpatialResolution::one(),
1✔
1387
                },
1✔
1388
                &MockQueryContext::test_default(),
1✔
1389
            )
1✔
1390
            .await
×
1391
            .unwrap();
1✔
1392

1✔
1393
        let mut expected = geoengine_datatypes::plots::BoxPlot::new();
1✔
1394
        expected.add_attribute(
1✔
1395
            BoxPlotAttribute::new("Raster-1".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1396
        );
1✔
1397
        expected.add_attribute(
1✔
1398
            BoxPlotAttribute::new("Raster-2".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1399
        );
1✔
1400
        expected.add_attribute(
1✔
1401
            BoxPlotAttribute::new("Raster-3".to_string(), 1.0, 7.0, 4.0, 1.5, 6.5, true).unwrap(),
1✔
1402
        );
1✔
1403

1✔
1404
        assert_eq!(expected.to_vega_embeddable(false).unwrap(), result);
1✔
1405
    }
1406
}
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