• 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.5
/operators/src/plot/histogram.rs
1
use crate::engine::{CreateSpan, QueryProcessor};
2
use crate::error;
3
use crate::error::Error;
4
use crate::string_token;
5
use crate::util::Result;
6
use crate::{
7
    engine::{
8
        ExecutionContext, InitializedPlotOperator, InitializedRasterOperator,
9
        InitializedVectorOperator, Operator, OperatorName, PlotOperator, PlotQueryProcessor,
10
        PlotResultDescriptor, QueryContext, SingleRasterOrVectorSource, TypedPlotQueryProcessor,
11
        TypedRasterQueryProcessor, TypedVectorQueryProcessor,
12
    },
13
    util::input::RasterOrVectorOperator,
14
};
15
use async_trait::async_trait;
16
use float_cmp::approx_eq;
17
use futures::stream::BoxStream;
18
use futures::{StreamExt, TryFutureExt};
19
use geoengine_datatypes::plots::{Plot, PlotData};
20
use geoengine_datatypes::primitives::{
21
    AxisAlignedRectangle, BoundingBox2D, DataRef, FeatureDataRef, FeatureDataType, Geometry,
22
    Measurement, VectorQueryRectangle,
23
};
24
use geoengine_datatypes::raster::{Pixel, RasterTile2D};
25
use geoengine_datatypes::{
26
    collections::{FeatureCollection, FeatureCollectionInfos},
27
    raster::GridSize,
28
};
29
use serde::{Deserialize, Serialize};
30
use snafu::{ensure, OptionExt};
31
use std::convert::TryFrom;
32
use tracing::{span, Level};
33

34
pub const HISTOGRAM_OPERATOR_NAME: &str = "Histogram";
35

36
/// A histogram plot about either a raster or a vector input.
37
///
38
/// For vector inputs, it calculates the histogram on one of its attributes.
39
///
40
pub type Histogram = Operator<HistogramParams, SingleRasterOrVectorSource>;
41

42
impl OperatorName for Histogram {
43
    const TYPE_NAME: &'static str = "Histogram";
44
}
45

46
/// The parameter spec for `Histogram`
47
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16✔
48
#[serde(rename_all = "camelCase")]
49
pub struct HistogramParams {
50
    /// Name of the (numeric) attribute to compute the histogram on. Ignored for operation on rasters.
51
    pub column_name: Option<String>,
52
    /// The bounds (min/max) of the histogram.
53
    pub bounds: HistogramBounds,
54
    /// If the number of buckets is undefined, it is derived from the square-root choice rule.
55
    pub buckets: Option<usize>,
56
    /// Whether to create an interactive output (`false` by default)
57
    #[serde(default)]
58
    pub interactive: bool,
59
}
60

61
string_token!(Data, "data");
62

63
/// Let the bounds either be computed or given.
64
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9✔
65
#[serde(untagged)]
66
pub enum HistogramBounds {
67
    Data(Data),
68
    Values { min: f64, max: f64 },
69
    // TODO: use bounds in measurement if they are available
70
}
71

72
#[typetag::serde]
×
73
#[async_trait]
74
impl PlotOperator for Histogram {
75
    async fn _initialize(
11✔
76
        self: Box<Self>,
11✔
77
        context: &dyn ExecutionContext,
11✔
78
    ) -> Result<Box<dyn InitializedPlotOperator>> {
11✔
79
        Ok(match self.sources.source {
11✔
80
            RasterOrVectorOperator::Raster(raster_source) => {
6✔
81
                ensure!(
6✔
82
                    self.params.column_name.is_none(),
6✔
83
                    error::InvalidOperatorSpec {
1✔
84
                        reason: "Histogram on raster input must not have `columnName` field set"
1✔
85
                            .to_string(),
1✔
86
                    }
1✔
87
                );
88

89
                let initialized = raster_source.initialize(context).await?;
5✔
90

91
                let in_desc = initialized.result_descriptor();
5✔
92
                InitializedHistogram::new(
5✔
93
                    PlotResultDescriptor {
5✔
94
                        spatial_reference: in_desc.spatial_reference,
5✔
95
                        time: in_desc.time,
5✔
96
                        // converting `SpatialPartition2D` to `BoundingBox2D` is ok here, because is makes the covered area only larger
5✔
97
                        bbox: in_desc
5✔
98
                            .bbox
5✔
99
                            .and_then(|p| BoundingBox2D::new(p.lower_left(), p.upper_right()).ok()),
5✔
100
                    },
5✔
101
                    self.params,
5✔
102
                    initialized,
5✔
103
                )
5✔
104
                .boxed()
5✔
105
            }
106
            RasterOrVectorOperator::Vector(vector_source) => {
5✔
107
                let column_name =
5✔
108
                    self.params
5✔
109
                        .column_name
5✔
110
                        .as_ref()
5✔
111
                        .context(error::InvalidOperatorSpec {
5✔
112
                            reason: "Histogram on vector input is missing `columnName` field"
5✔
113
                                .to_string(),
5✔
114
                        })?;
5✔
115

116
                let vector_source = vector_source.initialize(context).await?;
5✔
117

118
                match vector_source
5✔
119
                    .result_descriptor()
5✔
120
                    .column_data_type(column_name)
5✔
121
                {
122
                    None => {
123
                        return Err(Error::ColumnDoesNotExist {
×
124
                            column: column_name.to_string(),
×
125
                        });
×
126
                    }
127
                    Some(FeatureDataType::Category | FeatureDataType::Text) => {
128
                        // TODO: incorporate category data
129
                        return Err(Error::InvalidOperatorSpec {
1✔
130
                            reason: format!("column `{column_name}` must be numerical"),
1✔
131
                        });
1✔
132
                    }
133
                    Some(
134
                        FeatureDataType::Int
135
                        | FeatureDataType::Float
136
                        | FeatureDataType::Bool
137
                        | FeatureDataType::DateTime,
138
                    ) => {
4✔
139
                        // okay
4✔
140
                    }
4✔
141
                }
4✔
142

4✔
143
                let in_desc = vector_source.result_descriptor().clone();
4✔
144

4✔
145
                InitializedHistogram::new(in_desc.into(), self.params, vector_source).boxed()
4✔
146
            }
147
        })
148
    }
22✔
149

150
    span_fn!(Histogram);
×
151
}
152

153
/// The initialization of `Histogram`
154
pub struct InitializedHistogram<Op> {
155
    result_descriptor: PlotResultDescriptor,
156
    metadata: HistogramMetadataOptions,
157
    source: Op,
158
    interactive: bool,
159
    column_name: Option<String>,
160
}
161

162
impl<Op> InitializedHistogram<Op> {
163
    pub fn new(
9✔
164
        result_descriptor: PlotResultDescriptor,
9✔
165
        params: HistogramParams,
9✔
166
        source: Op,
9✔
167
    ) -> Self {
9✔
168
        let (min, max) = if let HistogramBounds::Values { min, max } = params.bounds {
9✔
169
            (Some(min), Some(max))
3✔
170
        } else {
171
            (None, None)
6✔
172
        };
173

174
        Self {
9✔
175
            result_descriptor,
9✔
176
            metadata: HistogramMetadataOptions {
9✔
177
                number_of_buckets: params.buckets,
9✔
178
                min,
9✔
179
                max,
9✔
180
            },
9✔
181
            source,
9✔
182
            interactive: params.interactive,
9✔
183
            column_name: params.column_name,
9✔
184
        }
9✔
185
    }
9✔
186
}
187

188
impl InitializedPlotOperator for InitializedHistogram<Box<dyn InitializedRasterOperator>> {
189
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
5✔
190
        let processor = HistogramRasterQueryProcessor {
5✔
191
            input: self.source.query_processor()?,
5✔
192
            measurement: self.source.result_descriptor().measurement.clone(),
5✔
193
            metadata: self.metadata,
5✔
194
            interactive: self.interactive,
5✔
195
        };
5✔
196

5✔
197
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
5✔
198
    }
5✔
199

200
    fn result_descriptor(&self) -> &PlotResultDescriptor {
1✔
201
        &self.result_descriptor
1✔
202
    }
1✔
203
}
204

205
impl InitializedPlotOperator for InitializedHistogram<Box<dyn InitializedVectorOperator>> {
206
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
4✔
207
        let processor = HistogramVectorQueryProcessor {
4✔
208
            input: self.source.query_processor()?,
4✔
209
            column_name: self.column_name.clone().unwrap_or_default(),
4✔
210
            measurement: self
4✔
211
                .source
4✔
212
                .result_descriptor()
4✔
213
                .column_measurement(self.column_name.as_deref().unwrap_or_default())
4✔
214
                .cloned()
4✔
215
                .into(),
4✔
216
            metadata: self.metadata,
4✔
217
            interactive: self.interactive,
4✔
218
        };
4✔
219

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

223
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
224
        &self.result_descriptor
×
225
    }
×
226
}
227

228
/// A query processor that calculates the Histogram about its raster inputs.
229
pub struct HistogramRasterQueryProcessor {
230
    input: TypedRasterQueryProcessor,
231
    measurement: Measurement,
232
    metadata: HistogramMetadataOptions,
233
    interactive: bool,
234
}
235

236
/// A query processor that calculates the Histogram about its vector inputs.
237
pub struct HistogramVectorQueryProcessor {
238
    input: TypedVectorQueryProcessor,
239
    column_name: String,
240
    measurement: Measurement,
241
    metadata: HistogramMetadataOptions,
242
    interactive: bool,
243
}
244

245
#[async_trait]
246
impl PlotQueryProcessor for HistogramRasterQueryProcessor {
247
    type OutputFormat = PlotData;
248

249
    fn plot_type(&self) -> &'static str {
1✔
250
        HISTOGRAM_OPERATOR_NAME
1✔
251
    }
1✔
252

253
    async fn plot_query<'p>(
5✔
254
        &'p self,
5✔
255
        query: VectorQueryRectangle,
5✔
256
        ctx: &'p dyn QueryContext,
5✔
257
    ) -> Result<Self::OutputFormat> {
5✔
258
        self.preprocess(query, ctx)
5✔
259
            .and_then(move |mut histogram_metadata| async move {
5✔
260
                histogram_metadata.sanitize();
5✔
261
                if histogram_metadata.has_invalid_parameters() {
5✔
262
                    // early return of empty histogram
263
                    return self.empty_histogram();
1✔
264
                }
4✔
265

4✔
266
                self.process(histogram_metadata, query, ctx).await
4✔
267
            })
5✔
268
            .await
×
269
    }
10✔
270
}
271

272
#[async_trait]
273
impl PlotQueryProcessor for HistogramVectorQueryProcessor {
274
    type OutputFormat = PlotData;
275

276
    fn plot_type(&self) -> &'static str {
×
277
        HISTOGRAM_OPERATOR_NAME
×
278
    }
×
279

280
    async fn plot_query<'p>(
4✔
281
        &'p self,
4✔
282
        query: VectorQueryRectangle,
4✔
283
        ctx: &'p dyn QueryContext,
4✔
284
    ) -> Result<Self::OutputFormat> {
4✔
285
        self.preprocess(query, ctx)
4✔
286
            .and_then(move |mut histogram_metadata| async move {
4✔
287
                histogram_metadata.sanitize();
4✔
288
                if histogram_metadata.has_invalid_parameters() {
4✔
289
                    // early return of empty histogram
290
                    return self.empty_histogram();
1✔
291
                }
3✔
292

3✔
293
                self.process(histogram_metadata, query, ctx).await
3✔
294
            })
4✔
295
            .await
×
296
    }
8✔
297
}
298

299
impl HistogramRasterQueryProcessor {
300
    async fn preprocess<'p>(
5✔
301
        &'p self,
5✔
302
        query: VectorQueryRectangle,
5✔
303
        ctx: &'p dyn QueryContext,
5✔
304
    ) -> Result<HistogramMetadata> {
5✔
305
        async fn process_metadata<T: Pixel>(
3✔
306
            mut input: BoxStream<'_, Result<RasterTile2D<T>>>,
3✔
307
            metadata: HistogramMetadataOptions,
3✔
308
        ) -> Result<HistogramMetadata> {
3✔
309
            let mut computed_metadata = HistogramMetadataInProgress::default();
3✔
310

311
            while let Some(tile) = input.next().await {
6✔
312
                match tile?.grid_array {
3✔
313
                    geoengine_datatypes::raster::GridOrEmpty::Grid(g) => {
2✔
314
                        computed_metadata.add_raster_batch(g.masked_element_deref_iterator());
2✔
315
                    }
2✔
316
                    geoengine_datatypes::raster::GridOrEmpty::Empty(_) => {} // TODO: find out if we really do nothing for empty tiles?
1✔
317
                }
318
            }
319

320
            Ok(metadata.merge_with(computed_metadata.into()))
3✔
321
        }
3✔
322

323
        if let Ok(metadata) = HistogramMetadata::try_from(self.metadata) {
5✔
324
            return Ok(metadata);
2✔
325
        }
3✔
326

3✔
327
        // TODO: compute only number of buckets if possible
3✔
328

3✔
329
        call_on_generic_raster_processor!(&self.input, processor => {
3✔
330
            process_metadata(processor.query(query.into(), ctx).await?, self.metadata).await
3✔
331
        })
332
    }
5✔
333

334
    async fn process<'p>(
4✔
335
        &'p self,
4✔
336
        metadata: HistogramMetadata,
4✔
337
        query: VectorQueryRectangle,
4✔
338
        ctx: &'p dyn QueryContext,
4✔
339
    ) -> Result<<HistogramRasterQueryProcessor as PlotQueryProcessor>::OutputFormat> {
4✔
340
        let mut histogram = geoengine_datatypes::plots::Histogram::builder(
4✔
341
            metadata.number_of_buckets,
4✔
342
            metadata.min,
4✔
343
            metadata.max,
4✔
344
            self.measurement.clone(),
4✔
345
        )
4✔
346
        .build()
4✔
347
        .map_err(Error::from)?;
4✔
348

349
        call_on_generic_raster_processor!(&self.input, processor => {
4✔
350
            let mut query = processor.query(query.into(), ctx).await?;
4✔
351

352
            while let Some(tile) = query.next().await {
8✔
353

354

355
                match tile?.grid_array {
4✔
356
                    geoengine_datatypes::raster::GridOrEmpty::Grid(g) => histogram.add_raster_data(g.masked_element_deref_iterator()),
4✔
357
                    geoengine_datatypes::raster::GridOrEmpty::Empty(n) => histogram.add_nodata_batch(n.number_of_elements() as u64) // TODO: why u64?
×
358
                }
359
            }
360
        });
361

362
        let chart = histogram.to_vega_embeddable(self.interactive)?;
4✔
363

364
        Ok(chart)
4✔
365
    }
4✔
366

367
    fn empty_histogram(
1✔
368
        &self,
1✔
369
    ) -> Result<<HistogramRasterQueryProcessor as PlotQueryProcessor>::OutputFormat> {
1✔
370
        let histogram =
1✔
371
            geoengine_datatypes::plots::Histogram::builder(1, 0., 0., self.measurement.clone())
1✔
372
                .build()
1✔
373
                .map_err(Error::from)?;
1✔
374

375
        let chart = histogram.to_vega_embeddable(self.interactive)?;
1✔
376

377
        Ok(chart)
1✔
378
    }
1✔
379
}
380

381
impl HistogramVectorQueryProcessor {
382
    async fn preprocess<'p>(
4✔
383
        &'p self,
4✔
384
        query: VectorQueryRectangle,
4✔
385
        ctx: &'p dyn QueryContext,
4✔
386
    ) -> Result<HistogramMetadata> {
4✔
387
        async fn process_metadata<'m, G>(
3✔
388
            mut input: BoxStream<'m, Result<FeatureCollection<G>>>,
3✔
389
            column_name: &'m str,
3✔
390
            metadata: HistogramMetadataOptions,
3✔
391
        ) -> Result<HistogramMetadata>
3✔
392
        where
3✔
393
            G: Geometry + 'static,
3✔
394
            FeatureCollection<G>: FeatureCollectionInfos,
3✔
395
        {
3✔
396
            let mut computed_metadata = HistogramMetadataInProgress::default();
3✔
397

398
            while let Some(collection) = input.next().await {
6✔
399
                let collection = collection?;
3✔
400

401
                let feature_data = collection.data(column_name).expect("check in param");
3✔
402
                computed_metadata.add_vector_batch(feature_data);
3✔
403
            }
404

405
            Ok(metadata.merge_with(computed_metadata.into()))
3✔
406
        }
3✔
407

408
        if let Ok(metadata) = HistogramMetadata::try_from(self.metadata) {
4✔
409
            return Ok(metadata);
1✔
410
        }
3✔
411

3✔
412
        // TODO: compute only number of buckets if possible
3✔
413

3✔
414
        call_on_generic_vector_processor!(&self.input, processor => {
3✔
415
            process_metadata(processor.query(query, ctx).await?, &self.column_name, self.metadata).await
3✔
416
        })
417
    }
4✔
418

419
    async fn process<'p>(
3✔
420
        &'p self,
3✔
421
        metadata: HistogramMetadata,
3✔
422
        query: VectorQueryRectangle,
3✔
423
        ctx: &'p dyn QueryContext,
3✔
424
    ) -> Result<<HistogramRasterQueryProcessor as PlotQueryProcessor>::OutputFormat> {
3✔
425
        let mut histogram = geoengine_datatypes::plots::Histogram::builder(
3✔
426
            metadata.number_of_buckets,
3✔
427
            metadata.min,
3✔
428
            metadata.max,
3✔
429
            self.measurement.clone(),
3✔
430
        )
3✔
431
        .build()
3✔
432
        .map_err(Error::from)?;
3✔
433

434
        call_on_generic_vector_processor!(&self.input, processor => {
3✔
435
            let mut query = processor.query(query, ctx).await?;
3✔
436

437
            while let Some(collection) = query.next().await {
7✔
438
                let collection = collection?;
4✔
439

440
                let feature_data = collection.data(&self.column_name).expect("checked in param");
4✔
441

442
                histogram.add_feature_data(feature_data)?;
4✔
443
            }
444
        });
445

446
        let chart = histogram.to_vega_embeddable(self.interactive)?;
3✔
447

448
        Ok(chart)
3✔
449
    }
3✔
450

451
    fn empty_histogram(
1✔
452
        &self,
1✔
453
    ) -> Result<<HistogramRasterQueryProcessor as PlotQueryProcessor>::OutputFormat> {
1✔
454
        let histogram =
1✔
455
            geoengine_datatypes::plots::Histogram::builder(1, 0., 0., self.measurement.clone())
1✔
456
                .build()
1✔
457
                .map_err(Error::from)?;
1✔
458

459
        let chart = histogram.to_vega_embeddable(self.interactive)?;
1✔
460

461
        Ok(chart)
1✔
462
    }
1✔
463
}
464

465
#[derive(Debug, Copy, Clone, PartialEq)]
×
466
struct HistogramMetadata {
467
    pub number_of_buckets: usize,
468
    pub min: f64,
469
    pub max: f64,
470
}
471

472
impl HistogramMetadata {
473
    /// Fix invalid configurations if they are fixeable
474
    fn sanitize(&mut self) {
9✔
475
        // prevent the rare case that min=max and you have more than one bucket
9✔
476
        if approx_eq!(f64, self.min, self.max) && self.number_of_buckets > 1 {
9✔
477
            self.number_of_buckets = 1;
1✔
478
        }
8✔
479
    }
9✔
480

481
    fn has_invalid_parameters(&self) -> bool {
9✔
482
        self.number_of_buckets == 0 || self.min > self.max
9✔
483
    }
9✔
484
}
485

486
#[derive(Debug, Copy, Clone, PartialEq)]
×
487
struct HistogramMetadataOptions {
488
    pub number_of_buckets: Option<usize>,
489
    pub min: Option<f64>,
490
    pub max: Option<f64>,
491
}
492

493
impl TryFrom<HistogramMetadataOptions> for HistogramMetadata {
494
    type Error = ();
495

496
    fn try_from(options: HistogramMetadataOptions) -> Result<Self, Self::Error> {
497
        match (options.number_of_buckets, options.min, options.max) {
9✔
498
            (Some(number_of_buckets), Some(min), Some(max)) => Ok(Self {
3✔
499
                number_of_buckets,
3✔
500
                min,
3✔
501
                max,
3✔
502
            }),
3✔
503
            _ => Err(()),
6✔
504
        }
505
    }
9✔
506
}
507

508
impl HistogramMetadataOptions {
509
    fn merge_with(self, metadata: HistogramMetadata) -> HistogramMetadata {
6✔
510
        HistogramMetadata {
6✔
511
            number_of_buckets: self.number_of_buckets.unwrap_or(metadata.number_of_buckets),
6✔
512
            min: self.min.unwrap_or(metadata.min),
6✔
513
            max: self.max.unwrap_or(metadata.max),
6✔
514
        }
6✔
515
    }
6✔
516
}
517

518
#[derive(Debug, Copy, Clone, PartialEq)]
×
519
struct HistogramMetadataInProgress {
520
    pub n: usize,
521
    pub min: f64,
522
    pub max: f64,
523
}
524

525
impl Default for HistogramMetadataInProgress {
526
    fn default() -> Self {
6✔
527
        Self {
6✔
528
            n: 0,
6✔
529
            min: f64::MAX,
6✔
530
            max: f64::MIN,
6✔
531
        }
6✔
532
    }
6✔
533
}
534

535
impl HistogramMetadataInProgress {
536
    #[inline]
537
    fn add_raster_batch<T: Pixel, I: Iterator<Item = Option<T>>>(&mut self, values: I) {
2✔
538
        values.for_each(|pixel_option| {
2✔
539
            if let Some(p) = pixel_option {
12✔
540
                self.n += 1;
12✔
541
                self.update_minmax(p.as_());
12✔
542
            }
12✔
543
        });
12✔
544
    }
2✔
545

546
    #[inline]
547
    fn add_vector_batch(&mut self, values: FeatureDataRef) {
3✔
548
        fn add_data_ref<'d, D, T>(metadata: &mut HistogramMetadataInProgress, data_ref: &'d D)
3✔
549
        where
3✔
550
            D: DataRef<'d, T>,
3✔
551
            T: 'static,
3✔
552
        {
3✔
553
            for v in data_ref.float_options_iter().flatten() {
5✔
554
                metadata.n += 1;
5✔
555
                metadata.update_minmax(v);
5✔
556
            }
5✔
557
        }
3✔
558

3✔
559
        match values {
3✔
560
            FeatureDataRef::Int(values) => {
×
561
                add_data_ref(self, &values);
×
562
            }
×
563
            FeatureDataRef::Float(values) => {
3✔
564
                add_data_ref(self, &values);
3✔
565
            }
3✔
566
            FeatureDataRef::Bool(values) => {
×
567
                add_data_ref(self, &values);
×
568
            }
×
569
            FeatureDataRef::DateTime(values) => {
×
570
                add_data_ref(self, &values);
×
571
            }
×
572
            FeatureDataRef::Category(_) | FeatureDataRef::Text(_) => {
×
573
                // do nothing since we don't support them
×
574
                // TODO: fill with live once we support category and text types
×
575
            }
×
576
        }
577
    }
3✔
578

579
    #[inline]
580
    fn update_minmax(&mut self, value: f64) {
17✔
581
        self.min = f64::min(self.min, value);
17✔
582
        self.max = f64::max(self.max, value);
17✔
583
    }
17✔
584
}
585

586
impl From<HistogramMetadataInProgress> for HistogramMetadata {
587
    fn from(metadata: HistogramMetadataInProgress) -> Self {
6✔
588
        Self {
6✔
589
            number_of_buckets: f64::sqrt(metadata.n as f64) as usize,
6✔
590
            min: metadata.min,
6✔
591
            max: metadata.max,
6✔
592
        }
6✔
593
    }
6✔
594
}
595

596
#[cfg(test)]
597
mod tests {
598
    use super::*;
599

600
    use crate::engine::{
601
        ChunkByteSize, MockExecutionContext, MockQueryContext, RasterOperator,
602
        RasterResultDescriptor, StaticMetaData, VectorColumnInfo, VectorOperator,
603
        VectorResultDescriptor,
604
    };
605
    use crate::mock::{MockFeatureCollectionSource, MockRasterSource, MockRasterSourceParams};
606
    use crate::source::{
607
        OgrSourceColumnSpec, OgrSourceDataset, OgrSourceDatasetTimeType, OgrSourceErrorSpec,
608
    };
609
    use crate::test_data;
610
    use geoengine_datatypes::dataset::{DataId, DatasetId};
611
    use geoengine_datatypes::primitives::{
612
        BoundingBox2D, DateTime, FeatureData, NoGeometry, SpatialResolution, TimeInterval,
613
    };
614
    use geoengine_datatypes::raster::{
615
        EmptyGrid2D, Grid2D, RasterDataType, RasterTile2D, TileInformation, TilingSpecification,
616
    };
617
    use geoengine_datatypes::spatial_reference::SpatialReference;
618
    use geoengine_datatypes::util::test::TestDefault;
619
    use geoengine_datatypes::util::Identifier;
620
    use geoengine_datatypes::{
621
        collections::{DataCollection, VectorDataType},
622
        primitives::MultiPoint,
623
    };
624
    use serde_json::json;
625

626
    #[test]
1✔
627
    fn serialization() {
1✔
628
        let histogram = Histogram {
1✔
629
            params: HistogramParams {
1✔
630
                column_name: Some("foobar".to_string()),
1✔
631
                bounds: HistogramBounds::Values {
1✔
632
                    min: 5.0,
1✔
633
                    max: 10.0,
1✔
634
                },
1✔
635
                buckets: Some(15),
1✔
636
                interactive: false,
1✔
637
            },
1✔
638
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
639
                .boxed()
1✔
640
                .into(),
1✔
641
        };
1✔
642

1✔
643
        let serialized = json!({
1✔
644
            "type": "Histogram",
1✔
645
            "params": {
1✔
646
                "columnName": "foobar",
1✔
647
                "bounds": {
1✔
648
                    "min": 5.0,
1✔
649
                    "max": 10.0,
1✔
650
                },
1✔
651
                "buckets": 15,
1✔
652
                "interactivity": false,
1✔
653
            },
1✔
654
            "sources": {
1✔
655
                "source": {
1✔
656
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
657
                    "params": {
1✔
658
                        "collections": [],
1✔
659
                        "spatialReference": "EPSG:4326",
1✔
660
                        "measurements": {},
1✔
661
                    }
1✔
662
                }
1✔
663
            }
1✔
664
        })
1✔
665
        .to_string();
1✔
666

1✔
667
        let deserialized: Histogram = serde_json::from_str(&serialized).unwrap();
1✔
668

1✔
669
        assert_eq!(deserialized.params, histogram.params);
1✔
670
    }
1✔
671

672
    #[test]
1✔
673
    fn serialization_alt() {
1✔
674
        let histogram = Histogram {
1✔
675
            params: HistogramParams {
1✔
676
                column_name: None,
1✔
677
                bounds: HistogramBounds::Data(Default::default()),
1✔
678
                buckets: None,
1✔
679
                interactive: false,
1✔
680
            },
1✔
681
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
682
                .boxed()
1✔
683
                .into(),
1✔
684
        };
1✔
685

1✔
686
        let serialized = json!({
1✔
687
            "type": "Histogram",
1✔
688
            "params": {
1✔
689
                "bounds": "data",
1✔
690
            },
1✔
691
            "sources": {
1✔
692
                "source": {
1✔
693
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
694
                    "params": {
1✔
695
                        "collections": [],
1✔
696
                        "spatialReference": "EPSG:4326",
1✔
697
                        "measurements": {},
1✔
698
                    }
1✔
699
                }
1✔
700
            }
1✔
701
        })
1✔
702
        .to_string();
1✔
703

1✔
704
        let deserialized: Histogram = serde_json::from_str(&serialized).unwrap();
1✔
705

1✔
706
        assert_eq!(deserialized.params, histogram.params);
1✔
707
    }
1✔
708

709
    #[tokio::test]
1✔
710
    async fn column_name_for_raster_source() {
1✔
711
        let histogram = Histogram {
1✔
712
            params: HistogramParams {
1✔
713
                column_name: Some("foo".to_string()),
1✔
714
                bounds: HistogramBounds::Values { min: 0.0, max: 8.0 },
1✔
715
                buckets: Some(3),
1✔
716
                interactive: false,
1✔
717
            },
1✔
718
            sources: mock_raster_source().into(),
1✔
719
        };
1✔
720

1✔
721
        let execution_context = MockExecutionContext::test_default();
1✔
722

723
        assert!(histogram
1✔
724
            .boxed()
1✔
725
            .initialize(&execution_context)
1✔
726
            .await
×
727
            .is_err());
1✔
728
    }
729

730
    fn mock_raster_source() -> Box<dyn RasterOperator> {
3✔
731
        MockRasterSource {
3✔
732
            params: MockRasterSourceParams {
3✔
733
                data: vec![RasterTile2D::new_with_tile_info(
3✔
734
                    TimeInterval::default(),
3✔
735
                    TileInformation {
3✔
736
                        global_geo_transform: TestDefault::test_default(),
3✔
737
                        global_tile_position: [0, 0].into(),
3✔
738
                        tile_size_in_pixels: [3, 2].into(),
3✔
739
                    },
3✔
740
                    Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
3✔
741
                        .unwrap()
3✔
742
                        .into(),
3✔
743
                )],
3✔
744
                result_descriptor: RasterResultDescriptor {
3✔
745
                    data_type: RasterDataType::U8,
3✔
746
                    spatial_reference: SpatialReference::epsg_4326().into(),
3✔
747
                    measurement: Measurement::Unitless,
3✔
748
                    time: None,
3✔
749
                    bbox: None,
3✔
750
                    resolution: None,
3✔
751
                },
3✔
752
            },
3✔
753
        }
3✔
754
        .boxed()
3✔
755
    }
3✔
756

757
    #[tokio::test]
1✔
758
    async fn simple_raster() {
1✔
759
        let tile_size_in_pixels = [3, 2].into();
1✔
760
        let tiling_specification = TilingSpecification {
1✔
761
            origin_coordinate: [0.0, 0.0].into(),
1✔
762
            tile_size_in_pixels,
1✔
763
        };
1✔
764
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
765

1✔
766
        let histogram = Histogram {
1✔
767
            params: HistogramParams {
1✔
768
                column_name: None,
1✔
769
                bounds: HistogramBounds::Values { min: 0.0, max: 8.0 },
1✔
770
                buckets: Some(3),
1✔
771
                interactive: false,
1✔
772
            },
1✔
773
            sources: mock_raster_source().into(),
1✔
774
        };
1✔
775

776
        let query_processor = histogram
1✔
777
            .boxed()
1✔
778
            .initialize(&execution_context)
1✔
779
            .await
×
780
            .unwrap()
1✔
781
            .query_processor()
1✔
782
            .unwrap()
1✔
783
            .json_vega()
1✔
784
            .unwrap();
1✔
785

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

1✔
798
        assert_eq!(
1✔
799
            result,
1✔
800
            geoengine_datatypes::plots::Histogram::builder(3, 0., 8., Measurement::Unitless)
1✔
801
                .counts(vec![2, 3, 1])
1✔
802
                .build()
1✔
803
                .unwrap()
1✔
804
                .to_vega_embeddable(false)
1✔
805
                .unwrap()
1✔
806
        );
1✔
807
    }
808

809
    #[tokio::test]
1✔
810
    async fn simple_raster_without_spec() {
1✔
811
        let tile_size_in_pixels = [3, 2].into();
1✔
812
        let tiling_specification = TilingSpecification {
1✔
813
            origin_coordinate: [0.0, 0.0].into(),
1✔
814
            tile_size_in_pixels,
1✔
815
        };
1✔
816
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
817

1✔
818
        let histogram = Histogram {
1✔
819
            params: HistogramParams {
1✔
820
                column_name: None,
1✔
821
                bounds: HistogramBounds::Data(Default::default()),
1✔
822
                buckets: None,
1✔
823
                interactive: false,
1✔
824
            },
1✔
825
            sources: mock_raster_source().into(),
1✔
826
        };
1✔
827

828
        let query_processor = histogram
1✔
829
            .boxed()
1✔
830
            .initialize(&execution_context)
1✔
831
            .await
×
832
            .unwrap()
1✔
833
            .query_processor()
1✔
834
            .unwrap()
1✔
835
            .json_vega()
1✔
836
            .unwrap();
1✔
837

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

1✔
850
        assert_eq!(
1✔
851
            result,
1✔
852
            geoengine_datatypes::plots::Histogram::builder(2, 1., 6., Measurement::Unitless)
1✔
853
                .counts(vec![3, 3])
1✔
854
                .build()
1✔
855
                .unwrap()
1✔
856
                .to_vega_embeddable(false)
1✔
857
                .unwrap()
1✔
858
        );
1✔
859
    }
860

861
    #[tokio::test]
1✔
862
    async fn vector_data() {
1✔
863
        let vector_source = MockFeatureCollectionSource::multiple(vec![
1✔
864
            DataCollection::from_slices(
1✔
865
                &[] as &[NoGeometry],
1✔
866
                &[TimeInterval::default(); 8],
1✔
867
                &[("foo", FeatureData::Int(vec![1, 1, 2, 2, 3, 3, 4, 4]))],
1✔
868
            )
1✔
869
            .unwrap(),
1✔
870
            DataCollection::from_slices(
1✔
871
                &[] as &[NoGeometry],
1✔
872
                &[TimeInterval::default(); 4],
1✔
873
                &[("foo", FeatureData::Int(vec![5, 6, 7, 8]))],
1✔
874
            )
1✔
875
            .unwrap(),
1✔
876
        ])
1✔
877
        .boxed();
1✔
878

1✔
879
        let histogram = Histogram {
1✔
880
            params: HistogramParams {
1✔
881
                column_name: Some("foo".to_string()),
1✔
882
                bounds: HistogramBounds::Values { min: 0.0, max: 8.0 },
1✔
883
                buckets: Some(3),
1✔
884
                interactive: true,
1✔
885
            },
1✔
886
            sources: vector_source.into(),
1✔
887
        };
1✔
888

1✔
889
        let execution_context = MockExecutionContext::test_default();
1✔
890

891
        let query_processor = histogram
1✔
892
            .boxed()
1✔
893
            .initialize(&execution_context)
1✔
894
            .await
×
895
            .unwrap()
1✔
896
            .query_processor()
1✔
897
            .unwrap()
1✔
898
            .json_vega()
1✔
899
            .unwrap();
1✔
900

901
        let result = query_processor
1✔
902
            .plot_query(
1✔
903
                VectorQueryRectangle {
1✔
904
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
905
                        .unwrap(),
1✔
906
                    time_interval: TimeInterval::default(),
1✔
907
                    spatial_resolution: SpatialResolution::one(),
1✔
908
                },
1✔
909
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
910
            )
1✔
911
            .await
×
912
            .unwrap();
1✔
913

1✔
914
        assert_eq!(
1✔
915
            result,
1✔
916
            geoengine_datatypes::plots::Histogram::builder(3, 0., 8., Measurement::Unitless)
1✔
917
                .counts(vec![4, 5, 3])
1✔
918
                .build()
1✔
919
                .unwrap()
1✔
920
                .to_vega_embeddable(true)
1✔
921
                .unwrap()
1✔
922
        );
1✔
923
    }
924

925
    #[tokio::test]
1✔
926
    async fn vector_data_with_nulls() {
1✔
927
        let vector_source = MockFeatureCollectionSource::single(
1✔
928
            DataCollection::from_slices(
1✔
929
                &[] as &[NoGeometry],
1✔
930
                &[TimeInterval::default(); 6],
1✔
931
                &[(
1✔
932
                    "foo",
1✔
933
                    FeatureData::NullableFloat(vec![
1✔
934
                        Some(1.),
1✔
935
                        Some(2.),
1✔
936
                        None,
1✔
937
                        Some(4.),
1✔
938
                        None,
1✔
939
                        Some(5.),
1✔
940
                    ]),
1✔
941
                )],
1✔
942
            )
1✔
943
            .unwrap(),
1✔
944
        )
1✔
945
        .boxed();
1✔
946

1✔
947
        let histogram = Histogram {
1✔
948
            params: HistogramParams {
1✔
949
                column_name: Some("foo".to_string()),
1✔
950
                bounds: HistogramBounds::Data(Default::default()),
1✔
951
                buckets: None,
1✔
952
                interactive: false,
1✔
953
            },
1✔
954
            sources: vector_source.into(),
1✔
955
        };
1✔
956

1✔
957
        let execution_context = MockExecutionContext::test_default();
1✔
958

959
        let query_processor = histogram
1✔
960
            .boxed()
1✔
961
            .initialize(&execution_context)
1✔
962
            .await
×
963
            .unwrap()
1✔
964
            .query_processor()
1✔
965
            .unwrap()
1✔
966
            .json_vega()
1✔
967
            .unwrap();
1✔
968

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

1✔
982
        assert_eq!(
1✔
983
            result,
1✔
984
            geoengine_datatypes::plots::Histogram::builder(2, 1., 5., Measurement::Unitless)
1✔
985
                .counts(vec![2, 2])
1✔
986
                .build()
1✔
987
                .unwrap()
1✔
988
                .to_vega_embeddable(false)
1✔
989
                .unwrap()
1✔
990
        );
1✔
991
    }
992

993
    #[tokio::test]
1✔
994
    #[allow(clippy::too_many_lines)]
995
    async fn text_attribute() {
1✔
996
        let dataset_id = DatasetId::new();
1✔
997

1✔
998
        let workflow = serde_json::json!({
1✔
999
            "type": "Histogram",
1✔
1000
            "params": {
1✔
1001
                "columnName": "featurecla",
1✔
1002
                "bounds": "data"
1✔
1003
            },
1✔
1004
            "sources": {
1✔
1005
                "source": {
1✔
1006
                    "type": "OgrSource",
1✔
1007
                    "params": {
1✔
1008
                        "data": {
1✔
1009
                            "type": "internal",
1✔
1010
                            "datasetId": dataset_id
1✔
1011
                        },
1✔
1012
                        "attributeProjection": null
1✔
1013
                    },
1✔
1014
                }
1✔
1015
            }
1✔
1016
        });
1✔
1017
        let histogram: Histogram = serde_json::from_value(workflow).unwrap();
1✔
1018

1✔
1019
        let mut execution_context = MockExecutionContext::test_default();
1✔
1020
        execution_context.add_meta_data::<_, _, VectorQueryRectangle>(
1✔
1021
            DataId::Internal { dataset_id },
1✔
1022
            Box::new(StaticMetaData {
1✔
1023
                loading_info: OgrSourceDataset {
1✔
1024
                    file_name: test_data!("vector/data/ne_10m_ports/ne_10m_ports.shp").into(),
1✔
1025
                    layer_name: "ne_10m_ports".to_string(),
1✔
1026
                    data_type: Some(VectorDataType::MultiPoint),
1✔
1027
                    time: OgrSourceDatasetTimeType::None,
1✔
1028
                    default_geometry: None,
1✔
1029
                    columns: Some(OgrSourceColumnSpec {
1✔
1030
                        format_specifics: None,
1✔
1031
                        x: String::new(),
1✔
1032
                        y: None,
1✔
1033
                        int: vec!["natlscale".to_string()],
1✔
1034
                        float: vec!["scalerank".to_string()],
1✔
1035
                        text: vec![
1✔
1036
                            "featurecla".to_string(),
1✔
1037
                            "name".to_string(),
1✔
1038
                            "website".to_string(),
1✔
1039
                        ],
1✔
1040
                        bool: vec![],
1✔
1041
                        datetime: vec![],
1✔
1042
                        rename: None,
1✔
1043
                    }),
1✔
1044
                    force_ogr_time_filter: false,
1✔
1045
                    force_ogr_spatial_filter: false,
1✔
1046
                    on_error: OgrSourceErrorSpec::Ignore,
1✔
1047
                    sql_query: None,
1✔
1048
                    attribute_query: None,
1✔
1049
                },
1✔
1050
                result_descriptor: VectorResultDescriptor {
1✔
1051
                    data_type: VectorDataType::MultiPoint,
1✔
1052
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1053
                    columns: [
1✔
1054
                        (
1✔
1055
                            "natlscale".to_string(),
1✔
1056
                            VectorColumnInfo {
1✔
1057
                                data_type: FeatureDataType::Float,
1✔
1058
                                measurement: Measurement::Unitless,
1✔
1059
                            },
1✔
1060
                        ),
1✔
1061
                        (
1✔
1062
                            "scalerank".to_string(),
1✔
1063
                            VectorColumnInfo {
1✔
1064
                                data_type: FeatureDataType::Int,
1✔
1065
                                measurement: Measurement::Unitless,
1✔
1066
                            },
1✔
1067
                        ),
1✔
1068
                        (
1✔
1069
                            "featurecla".to_string(),
1✔
1070
                            VectorColumnInfo {
1✔
1071
                                data_type: FeatureDataType::Text,
1✔
1072
                                measurement: Measurement::Unitless,
1✔
1073
                            },
1✔
1074
                        ),
1✔
1075
                        (
1✔
1076
                            "name".to_string(),
1✔
1077
                            VectorColumnInfo {
1✔
1078
                                data_type: FeatureDataType::Text,
1✔
1079
                                measurement: Measurement::Unitless,
1✔
1080
                            },
1✔
1081
                        ),
1✔
1082
                        (
1✔
1083
                            "website".to_string(),
1✔
1084
                            VectorColumnInfo {
1✔
1085
                                data_type: FeatureDataType::Text,
1✔
1086
                                measurement: Measurement::Unitless,
1✔
1087
                            },
1✔
1088
                        ),
1✔
1089
                    ]
1✔
1090
                    .iter()
1✔
1091
                    .cloned()
1✔
1092
                    .collect(),
1✔
1093
                    time: None,
1✔
1094
                    bbox: None,
1✔
1095
                },
1✔
1096
                phantom: Default::default(),
1✔
1097
            }),
1✔
1098
        );
1✔
1099

1100
        if let Err(Error::InvalidOperatorSpec { reason }) =
1✔
1101
            histogram.boxed().initialize(&execution_context).await
1✔
1102
        {
1103
            assert_eq!(reason, "column `featurecla` must be numerical");
1✔
1104
        } else {
1105
            panic!("we currently don't support text features, but this went through");
×
1106
        }
1107
    }
1108

1109
    #[tokio::test]
1✔
1110
    async fn no_data_raster() {
1✔
1111
        let tile_size_in_pixels = [3, 2].into();
1✔
1112
        let tiling_specification = TilingSpecification {
1✔
1113
            origin_coordinate: [0.0, 0.0].into(),
1✔
1114
            tile_size_in_pixels,
1✔
1115
        };
1✔
1116
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1117
        let histogram = Histogram {
1✔
1118
            params: HistogramParams {
1✔
1119
                column_name: None,
1✔
1120
                bounds: HistogramBounds::Data(Data::default()),
1✔
1121
                buckets: None,
1✔
1122
                interactive: false,
1✔
1123
            },
1✔
1124
            sources: MockRasterSource {
1✔
1125
                params: MockRasterSourceParams {
1✔
1126
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1127
                        TimeInterval::default(),
1✔
1128
                        TileInformation {
1✔
1129
                            global_geo_transform: TestDefault::test_default(),
1✔
1130
                            global_tile_position: [0, 0].into(),
1✔
1131
                            tile_size_in_pixels,
1✔
1132
                        },
1✔
1133
                        EmptyGrid2D::<u8>::new(tile_size_in_pixels).into(),
1✔
1134
                    )],
1✔
1135
                    result_descriptor: RasterResultDescriptor {
1✔
1136
                        data_type: RasterDataType::U8,
1✔
1137
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1138
                        measurement: Measurement::Unitless,
1✔
1139
                        time: None,
1✔
1140
                        bbox: None,
1✔
1141
                        resolution: None,
1✔
1142
                    },
1✔
1143
                },
1✔
1144
            }
1✔
1145
            .boxed()
1✔
1146
            .into(),
1✔
1147
        };
1✔
1148

1149
        let query_processor = histogram
1✔
1150
            .boxed()
1✔
1151
            .initialize(&execution_context)
1✔
1152
            .await
×
1153
            .unwrap()
1✔
1154
            .query_processor()
1✔
1155
            .unwrap()
1✔
1156
            .json_vega()
1✔
1157
            .unwrap();
1✔
1158

1159
        let result = query_processor
1✔
1160
            .plot_query(
1✔
1161
                VectorQueryRectangle {
1✔
1162
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
1163
                    time_interval: TimeInterval::default(),
1✔
1164
                    spatial_resolution: SpatialResolution::one(),
1✔
1165
                },
1✔
1166
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1167
            )
1✔
1168
            .await
×
1169
            .unwrap();
1✔
1170

1✔
1171
        assert_eq!(
1✔
1172
            result,
1✔
1173
            geoengine_datatypes::plots::Histogram::builder(1, 0., 0., Measurement::Unitless)
1✔
1174
                .build()
1✔
1175
                .unwrap()
1✔
1176
                .to_vega_embeddable(false)
1✔
1177
                .unwrap()
1✔
1178
        );
1✔
1179
    }
1180

1181
    #[tokio::test]
1✔
1182
    async fn empty_feature_collection() {
1✔
1183
        let vector_source = MockFeatureCollectionSource::single(
1✔
1184
            DataCollection::from_slices(
1✔
1185
                &[] as &[NoGeometry],
1✔
1186
                &[] as &[TimeInterval],
1✔
1187
                &[("foo", FeatureData::Float(vec![]))],
1✔
1188
            )
1✔
1189
            .unwrap(),
1✔
1190
        )
1✔
1191
        .boxed();
1✔
1192

1✔
1193
        let histogram = Histogram {
1✔
1194
            params: HistogramParams {
1✔
1195
                column_name: Some("foo".to_string()),
1✔
1196
                bounds: HistogramBounds::Data(Default::default()),
1✔
1197
                buckets: None,
1✔
1198
                interactive: false,
1✔
1199
            },
1✔
1200
            sources: vector_source.into(),
1✔
1201
        };
1✔
1202

1✔
1203
        let execution_context = MockExecutionContext::test_default();
1✔
1204

1205
        let query_processor = histogram
1✔
1206
            .boxed()
1✔
1207
            .initialize(&execution_context)
1✔
1208
            .await
×
1209
            .unwrap()
1✔
1210
            .query_processor()
1✔
1211
            .unwrap()
1✔
1212
            .json_vega()
1✔
1213
            .unwrap();
1✔
1214

1215
        let result = query_processor
1✔
1216
            .plot_query(
1✔
1217
                VectorQueryRectangle {
1✔
1218
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1219
                        .unwrap(),
1✔
1220
                    time_interval: TimeInterval::default(),
1✔
1221
                    spatial_resolution: SpatialResolution::one(),
1✔
1222
                },
1✔
1223
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1224
            )
1✔
1225
            .await
×
1226
            .unwrap();
1✔
1227

1✔
1228
        assert_eq!(
1✔
1229
            result,
1✔
1230
            geoengine_datatypes::plots::Histogram::builder(1, 0., 0., Measurement::Unitless)
1✔
1231
                .build()
1✔
1232
                .unwrap()
1✔
1233
                .to_vega_embeddable(false)
1✔
1234
                .unwrap()
1✔
1235
        );
1✔
1236
    }
1237

1238
    #[tokio::test]
1✔
1239
    async fn feature_collection_with_one_feature() {
1✔
1240
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
1241
            vec![DataCollection::from_slices(
1✔
1242
                &[] as &[NoGeometry],
1✔
1243
                &[TimeInterval::default()],
1✔
1244
                &[("foo", FeatureData::Float(vec![5.0]))],
1✔
1245
            )
1✔
1246
            .unwrap()],
1✔
1247
            [(
1✔
1248
                "foo".to_string(),
1✔
1249
                Measurement::continuous("bar".to_string(), None),
1✔
1250
            )]
1✔
1251
            .into_iter()
1✔
1252
            .collect(),
1✔
1253
        )
1✔
1254
        .boxed();
1✔
1255

1✔
1256
        let histogram = Histogram {
1✔
1257
            params: HistogramParams {
1✔
1258
                column_name: Some("foo".to_string()),
1✔
1259
                bounds: HistogramBounds::Data(Default::default()),
1✔
1260
                buckets: None,
1✔
1261
                interactive: false,
1✔
1262
            },
1✔
1263
            sources: vector_source.into(),
1✔
1264
        };
1✔
1265

1✔
1266
        let execution_context = MockExecutionContext::test_default();
1✔
1267

1268
        let query_processor = histogram
1✔
1269
            .boxed()
1✔
1270
            .initialize(&execution_context)
1✔
1271
            .await
×
1272
            .unwrap()
1✔
1273
            .query_processor()
1✔
1274
            .unwrap()
1✔
1275
            .json_vega()
1✔
1276
            .unwrap();
1✔
1277

1278
        let result = query_processor
1✔
1279
            .plot_query(
1✔
1280
                VectorQueryRectangle {
1✔
1281
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1282
                        .unwrap(),
1✔
1283
                    time_interval: TimeInterval::default(),
1✔
1284
                    spatial_resolution: SpatialResolution::one(),
1✔
1285
                },
1✔
1286
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1287
            )
1✔
1288
            .await
×
1289
            .unwrap();
1✔
1290

1✔
1291
        assert_eq!(
1✔
1292
            result,
1✔
1293
            geoengine_datatypes::plots::Histogram::builder(
1✔
1294
                1,
1✔
1295
                5.,
1✔
1296
                5.,
1✔
1297
                Measurement::continuous("bar".to_string(), None)
1✔
1298
            )
1✔
1299
            .counts(vec![1])
1✔
1300
            .build()
1✔
1301
            .unwrap()
1✔
1302
            .to_vega_embeddable(false)
1✔
1303
            .unwrap()
1✔
1304
        );
1✔
1305
    }
1306

1307
    #[tokio::test]
1✔
1308
    async fn single_value_raster_stream() {
1✔
1309
        let tile_size_in_pixels = [3, 2].into();
1✔
1310
        let tiling_specification = TilingSpecification {
1✔
1311
            origin_coordinate: [0.0, 0.0].into(),
1✔
1312
            tile_size_in_pixels,
1✔
1313
        };
1✔
1314
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1315
        let histogram = Histogram {
1✔
1316
            params: HistogramParams {
1✔
1317
                column_name: None,
1✔
1318
                bounds: HistogramBounds::Data(Data::default()),
1✔
1319
                buckets: None,
1✔
1320
                interactive: false,
1✔
1321
            },
1✔
1322
            sources: MockRasterSource {
1✔
1323
                params: MockRasterSourceParams {
1✔
1324
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1325
                        TimeInterval::default(),
1✔
1326
                        TileInformation {
1✔
1327
                            global_geo_transform: TestDefault::test_default(),
1✔
1328
                            global_tile_position: [0, 0].into(),
1✔
1329
                            tile_size_in_pixels,
1✔
1330
                        },
1✔
1331
                        Grid2D::new(tile_size_in_pixels, vec![4; 6]).unwrap().into(),
1✔
1332
                    )],
1✔
1333
                    result_descriptor: RasterResultDescriptor {
1✔
1334
                        data_type: RasterDataType::U8,
1✔
1335
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1336
                        measurement: Measurement::Unitless,
1✔
1337
                        time: None,
1✔
1338
                        bbox: None,
1✔
1339
                        resolution: None,
1✔
1340
                    },
1✔
1341
                },
1✔
1342
            }
1✔
1343
            .boxed()
1✔
1344
            .into(),
1✔
1345
        };
1✔
1346

1347
        let query_processor = histogram
1✔
1348
            .boxed()
1✔
1349
            .initialize(&execution_context)
1✔
1350
            .await
×
1351
            .unwrap()
1✔
1352
            .query_processor()
1✔
1353
            .unwrap()
1✔
1354
            .json_vega()
1✔
1355
            .unwrap();
1✔
1356

1357
        let result = query_processor
1✔
1358
            .plot_query(
1✔
1359
                VectorQueryRectangle {
1✔
1360
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
1361
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1362
                        2013, 12, 1, 12, 0, 0,
1✔
1363
                    ))
1✔
1364
                    .unwrap(),
1✔
1365
                    spatial_resolution: SpatialResolution::one(),
1✔
1366
                },
1✔
1367
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1368
            )
1✔
1369
            .await
×
1370
            .unwrap();
1✔
1371

1✔
1372
        assert_eq!(
1✔
1373
            result,
1✔
1374
            geoengine_datatypes::plots::Histogram::builder(1, 4., 4., Measurement::Unitless)
1✔
1375
                .counts(vec![6])
1✔
1376
                .build()
1✔
1377
                .unwrap()
1✔
1378
                .to_vega_embeddable(false)
1✔
1379
                .unwrap()
1✔
1380
        );
1✔
1381
    }
1382
}
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