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

geo-engine / geoengine / 6034971426

31 Aug 2023 08:32AM UTC coverage: 90.041% (+0.1%) from 89.934%
6034971426

push

github

web-flow
Merge pull request #868 from geo-engine/update-2023-08-29

Update 2023 08 29

171 of 171 new or added lines in 36 files covered. (100.0%)

106394 of 118162 relevant lines covered (90.04%)

61276.98 hits per line

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

95.22
/operators/src/plot/pie_chart.rs
1
use crate::engine::{
2
    CanonicOperatorName, ExecutionContext, InitializedPlotOperator, InitializedSources,
3
    InitializedVectorOperator, Operator, OperatorName, PlotOperator, PlotQueryProcessor,
4
    PlotResultDescriptor, QueryContext, TypedPlotQueryProcessor, TypedVectorQueryProcessor,
5
    WorkflowOperatorPath,
6
};
7
use crate::engine::{QueryProcessor, SingleVectorSource};
8
use crate::error::Error;
9
use crate::util::Result;
10
use async_trait::async_trait;
11
use futures::StreamExt;
12
use geoengine_datatypes::collections::FeatureCollectionInfos;
13
use geoengine_datatypes::plots::{Plot, PlotData};
14
use geoengine_datatypes::primitives::{FeatureDataRef, Measurement, VectorQueryRectangle};
15
use serde::{Deserialize, Serialize};
16
use snafu::Snafu;
17
use std::collections::HashMap;
18

19
pub const PIE_CHART_OPERATOR_NAME: &str = "PieChart";
20

21
/// If the number of slices in the result is greater than this, the operator will fail.
22
pub const MAX_NUMBER_OF_SLICES: usize = 32;
23

24
/// A pie chart plot about a column of a vector input.
25
pub type PieChart = Operator<PieChartParams, SingleVectorSource>;
26

27
impl OperatorName for PieChart {
28
    const TYPE_NAME: &'static str = PIE_CHART_OPERATOR_NAME;
29
}
30

31
/// The parameter spec for `PieChart`
32
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6✔
33
#[serde(tag = "type", rename_all = "camelCase")]
34
pub enum PieChartParams {
35
    /// Count the distinct values of a column
36
    #[serde(rename_all = "camelCase")]
37
    Count {
38
        /// Name of the (numeric) attribute to compute the histogram on. Fails if set for rasters.
39
        column_name: String,
40
        /// Whether to display the pie chart as a normal pie chart or as a donut chart.
41
        /// Defaults to `false`.
42
        #[serde(default)]
43
        donut: bool,
44
    },
45
    // TODO: another useful method would be `Sum` which sums up all values of a column A for a group column B
46
}
47

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

58
        let initialized_sources = self.sources.initialize_sources(path, context).await?;
6✔
59
        let vector_source = initialized_sources.vector;
6✔
60

6✔
61
        let in_desc = vector_source.result_descriptor().clone();
6✔
62

6✔
63
        match self.params {
6✔
64
            PieChartParams::Count { column_name, donut } => {
6✔
65
                let Some(column_measurement) = in_desc.column_measurement(&column_name) else {
6✔
66
                    return Err(Error::ColumnDoesNotExist {
×
67
                        column: column_name,
×
68
                    });
×
69
                };
70

71
                let mut column_label = column_measurement.to_string();
6✔
72
                if column_label.is_empty() {
6✔
73
                    // in case of `Measurement::Unitless`
2✔
74
                    column_label = column_name.clone();
2✔
75
                }
4✔
76

77
                let class_mapping =
6✔
78
                    if let Measurement::Classification(measurement) = &column_measurement {
6✔
79
                        Some(measurement.classes.clone())
3✔
80
                    } else {
81
                        None
3✔
82
                    };
83

84
                Ok(InitializedCountPieChart::new(
6✔
85
                    name,
6✔
86
                    vector_source,
6✔
87
                    in_desc.into(),
6✔
88
                    column_name.clone(),
6✔
89
                    column_label,
6✔
90
                    class_mapping,
6✔
91
                    donut,
6✔
92
                )
6✔
93
                .boxed())
6✔
94
            }
95
        }
96
    }
12✔
97

98
    span_fn!(PieChart);
×
99
}
100

101
/// The initialization of `Histogram`
102
pub struct InitializedCountPieChart<Op> {
103
    name: CanonicOperatorName,
104
    source: Op,
105
    result_descriptor: PlotResultDescriptor,
106
    column_name: String,
107
    column_label: String,
108
    class_mapping: Option<HashMap<u8, String>>,
109
    donut: bool,
110
}
111

112
impl<Op> InitializedCountPieChart<Op> {
113
    pub fn new(
6✔
114
        name: CanonicOperatorName,
6✔
115
        source: Op,
6✔
116
        result_descriptor: PlotResultDescriptor,
6✔
117
        column_name: String,
6✔
118
        column_label: String,
6✔
119
        class_mapping: Option<HashMap<u8, String>>,
6✔
120
        donut: bool,
6✔
121
    ) -> Self {
6✔
122
        Self {
6✔
123
            name,
6✔
124
            source,
6✔
125
            result_descriptor,
6✔
126
            column_name,
6✔
127
            column_label,
6✔
128
            class_mapping,
6✔
129
            donut,
6✔
130
        }
6✔
131
    }
6✔
132
}
133

134
impl InitializedPlotOperator for InitializedCountPieChart<Box<dyn InitializedVectorOperator>> {
135
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
6✔
136
        let processor = CountPieChartVectorQueryProcessor {
6✔
137
            input: self.source.query_processor()?,
6✔
138
            column_label: self.column_label.clone(),
6✔
139
            column_name: self.column_name.clone(),
6✔
140
            class_mapping: self.class_mapping.clone(),
6✔
141
            donut: self.donut,
6✔
142
        };
6✔
143

6✔
144
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
6✔
145
    }
6✔
146

147
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
148
        &self.result_descriptor
×
149
    }
×
150

151
    fn canonic_name(&self) -> CanonicOperatorName {
×
152
        self.name.clone()
×
153
    }
×
154
}
155

156
/// A query processor that calculates the Histogram about its vector inputs.
157
pub struct CountPieChartVectorQueryProcessor {
158
    input: TypedVectorQueryProcessor,
159
    column_label: String,
160
    column_name: String,
161
    class_mapping: Option<HashMap<u8, String>>,
162
    donut: bool,
163
}
164

165
#[async_trait]
166
impl PlotQueryProcessor for CountPieChartVectorQueryProcessor {
167
    type OutputFormat = PlotData;
168

169
    fn plot_type(&self) -> &'static str {
×
170
        PIE_CHART_OPERATOR_NAME
×
171
    }
×
172

173
    async fn plot_query<'p>(
6✔
174
        &'p self,
6✔
175
        query: VectorQueryRectangle,
6✔
176
        ctx: &'p dyn QueryContext,
6✔
177
    ) -> Result<Self::OutputFormat> {
6✔
178
        self.process(query, ctx).await
50✔
179
    }
12✔
180
}
181

182
/// Creates an iterator over all values as string
183
/// Null-values are empty strings.
184
pub fn feature_data_strings_iter<'f>(
185
    feature_data: &'f FeatureDataRef,
186
    class_mapping: Option<&'f HashMap<u8, String>>,
187
) -> Box<dyn Iterator<Item = String> + 'f> {
188
    match (feature_data, class_mapping) {
52✔
189
        (FeatureDataRef::Category(feature_data_ref), Some(class_mapping)) => {
×
190
            return Box::new(feature_data_ref.as_ref().iter().map(|v| {
×
191
                class_mapping
×
192
                    .get(v)
×
193
                    .map_or_else(String::new, ToString::to_string)
×
194
            }));
×
195
        }
196
        (FeatureDataRef::Int(feature_data_ref), Some(class_mapping)) => {
2✔
197
            return Box::new(feature_data_ref.as_ref().iter().map(|v| {
12✔
198
                class_mapping
12✔
199
                    .get(&(*v as u8))
12✔
200
                    .map_or_else(String::new, ToString::to_string)
12✔
201
            }));
12✔
202
        }
203
        (FeatureDataRef::Float(feature_data_ref), Some(class_mapping)) => {
2✔
204
            return Box::new(feature_data_ref.as_ref().iter().map(|v| {
2✔
205
                class_mapping
1✔
206
                    .get(&(*v as u8))
1✔
207
                    .map_or_else(String::new, ToString::to_string)
1✔
208
            }));
2✔
209
        }
210
        _ => {
48✔
211
            // no special treatment for other types
48✔
212
        }
48✔
213
    }
48✔
214

48✔
215
    feature_data.strings_iter()
48✔
216
}
52✔
217

218
impl CountPieChartVectorQueryProcessor {
219
    async fn process<'p>(
6✔
220
        &'p self,
6✔
221
        query: VectorQueryRectangle,
6✔
222
        ctx: &'p dyn QueryContext,
6✔
223
    ) -> Result<<CountPieChartVectorQueryProcessor as PlotQueryProcessor>::OutputFormat> {
6✔
224
        let mut slices: HashMap<String, f64> = HashMap::new();
6✔
225

6✔
226
        // TODO: parallelize
6✔
227

6✔
228
        call_on_generic_vector_processor!(&self.input, processor => {
6✔
229
            let mut query = processor.query(query, ctx).await?;
6✔
230

231
            while let Some(collection) = query.next().await {
57✔
232
                let collection = collection?;
52✔
233

234
                let feature_data = collection.data(&self.column_name)?;
52✔
235

236
                let feature_data_strings = feature_data_strings_iter(&feature_data, self.class_mapping.as_ref());
52✔
237

238
                for v in feature_data_strings {
66✔
239
                    if v.is_empty() {
66✔
240
                        continue; // ignore no data
2✔
241
                    }
47✔
242

17✔
243
                    *slices.entry(v).or_insert(0.0) += 1.0;
64✔
244
                }
245

246
                if slices.len() > MAX_NUMBER_OF_SLICES {
52✔
247
                    return Err(PieChartError::TooManySlices.into());
1✔
248
                }
51✔
249
            }
250
        });
251

252
        // TODO: display NO-DATA count?
253

254
        let bar_chart = geoengine_datatypes::plots::PieChart::new(
5✔
255
            slices.into_iter().collect(),
5✔
256
            self.column_label.clone(),
5✔
257
            self.donut,
5✔
258
        )?;
5✔
259
        let chart = bar_chart.to_vega_embeddable(false)?;
5✔
260

261
        Ok(chart)
5✔
262
    }
6✔
263
}
264

265
#[derive(Debug, Snafu, Clone, PartialEq, Eq)]
×
266
#[snafu(
267
    visibility(pub(crate)),
268
    context(suffix(false)), // disables default `Snafu` suffix
269
    module(error),
270
)]
271
pub enum PieChartError {
272
    #[snafu(display(
273
        "The number of slices is too high. Maximum is {}.",
274
        MAX_NUMBER_OF_SLICES
275
    ))]
276
    TooManySlices,
277
}
278

279
#[cfg(test)]
280
mod tests {
281

282
    use super::*;
283

284
    use crate::engine::{
285
        ChunkByteSize, MockExecutionContext, MockQueryContext, StaticMetaData, VectorColumnInfo,
286
        VectorOperator, VectorResultDescriptor,
287
    };
288
    use crate::mock::MockFeatureCollectionSource;
289
    use crate::source::{
290
        AttributeFilter, OgrSource, OgrSourceColumnSpec, OgrSourceDataset,
291
        OgrSourceDatasetTimeType, OgrSourceErrorSpec, OgrSourceParameters,
292
    };
293
    use crate::test_data;
294
    use geoengine_datatypes::dataset::{DataId, DatasetId, NamedData};
295
    use geoengine_datatypes::primitives::CacheTtlSeconds;
296
    use geoengine_datatypes::primitives::{
297
        BoundingBox2D, FeatureData, FeatureDataType, NoGeometry, SpatialResolution, TimeInterval,
298
    };
299
    use geoengine_datatypes::spatial_reference::SpatialReference;
300
    use geoengine_datatypes::util::test::TestDefault;
301
    use geoengine_datatypes::util::Identifier;
302
    use geoengine_datatypes::{
303
        collections::{DataCollection, VectorDataType},
304
        primitives::MultiPoint,
305
    };
306
    use serde_json::json;
307

308
    #[test]
1✔
309
    fn serialization() {
1✔
310
        let pie_chart = PieChart {
1✔
311
            params: PieChartParams::Count {
1✔
312
                column_name: "my_column".to_string(),
1✔
313
                donut: false,
1✔
314
            },
1✔
315
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
316
                .boxed()
1✔
317
                .into(),
1✔
318
        };
1✔
319

1✔
320
        let serialized = json!({
1✔
321
            "type": "PieChart",
1✔
322
            "params": {
1✔
323
                "type": "count",
1✔
324
                "columnName": "my_column",
1✔
325
            },
1✔
326
            "sources": {
1✔
327
                "vector": {
1✔
328
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
329
                    "params": {
1✔
330
                        "collections": [],
1✔
331
                        "spatialReference": "EPSG:4326",
1✔
332
                        "measurements": {},
1✔
333
                    }
1✔
334
                }
1✔
335
            }
1✔
336
        })
1✔
337
        .to_string();
1✔
338

1✔
339
        let deserialized: PieChart = serde_json::from_str(&serialized).unwrap();
1✔
340

1✔
341
        assert_eq!(deserialized.params, pie_chart.params);
1✔
342
    }
1✔
343

344
    #[tokio::test]
1✔
345
    async fn vector_data_with_classification() {
1✔
346
        let measurement = Measurement::classification(
1✔
347
            "foo".to_string(),
1✔
348
            [
1✔
349
                (1, "A".to_string()),
1✔
350
                (2, "B".to_string()),
1✔
351
                (3, "C".to_string()),
1✔
352
            ]
1✔
353
            .into_iter()
1✔
354
            .collect(),
1✔
355
        );
1✔
356

1✔
357
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
358
            vec![
1✔
359
                DataCollection::from_slices(
1✔
360
                    &[] as &[NoGeometry],
1✔
361
                    &[TimeInterval::default(); 8],
1✔
362
                    &[("foo", FeatureData::Int(vec![1, 1, 2, 2, 3, 3, 1, 2]))],
1✔
363
                )
1✔
364
                .unwrap(),
1✔
365
                DataCollection::from_slices(
1✔
366
                    &[] as &[NoGeometry],
1✔
367
                    &[TimeInterval::default(); 4],
1✔
368
                    &[("foo", FeatureData::Int(vec![1, 1, 2, 3]))],
1✔
369
                )
1✔
370
                .unwrap(),
1✔
371
            ],
1✔
372
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
373
        )
1✔
374
        .boxed();
1✔
375

1✔
376
        let pie_chart = PieChart {
1✔
377
            params: PieChartParams::Count {
1✔
378
                column_name: "foo".to_string(),
1✔
379
                donut: false,
1✔
380
            },
1✔
381
            sources: vector_source.into(),
1✔
382
        };
1✔
383

1✔
384
        let execution_context = MockExecutionContext::test_default();
1✔
385

386
        let query_processor = pie_chart
1✔
387
            .boxed()
1✔
388
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
389
            .await
×
390
            .unwrap()
1✔
391
            .query_processor()
1✔
392
            .unwrap()
1✔
393
            .json_vega()
1✔
394
            .unwrap();
1✔
395

396
        let result = query_processor
1✔
397
            .plot_query(
1✔
398
                VectorQueryRectangle {
1✔
399
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
400
                        .unwrap(),
1✔
401
                    time_interval: TimeInterval::default(),
1✔
402
                    spatial_resolution: SpatialResolution::one(),
1✔
403
                },
1✔
404
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
405
            )
1✔
406
            .await
×
407
            .unwrap();
1✔
408

1✔
409
        assert_eq!(
1✔
410
            result,
1✔
411
            geoengine_datatypes::plots::PieChart::new(
1✔
412
                [
1✔
413
                    ("A".to_string(), 5.),
1✔
414
                    ("B".to_string(), 4.),
1✔
415
                    ("C".to_string(), 3.),
1✔
416
                ]
1✔
417
                .into(),
1✔
418
                "foo".to_string(),
1✔
419
                false,
1✔
420
            )
1✔
421
            .unwrap()
1✔
422
            .to_vega_embeddable(false)
1✔
423
            .unwrap()
1✔
424
        );
1✔
425
    }
426

427
    #[tokio::test]
1✔
428
    async fn vector_data_with_nulls() {
1✔
429
        let measurement = Measurement::continuous("foo".to_string(), None);
1✔
430

1✔
431
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
432
            vec![DataCollection::from_slices(
1✔
433
                &[] as &[NoGeometry],
1✔
434
                &[TimeInterval::default(); 6],
1✔
435
                &[(
1✔
436
                    "foo",
1✔
437
                    FeatureData::NullableFloat(vec![
1✔
438
                        Some(1.),
1✔
439
                        Some(2.),
1✔
440
                        None,
1✔
441
                        Some(1.),
1✔
442
                        None,
1✔
443
                        Some(3.),
1✔
444
                    ]),
1✔
445
                )],
1✔
446
            )
1✔
447
            .unwrap()],
1✔
448
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
449
        )
1✔
450
        .boxed();
1✔
451

1✔
452
        let pie_chart = PieChart {
1✔
453
            params: PieChartParams::Count {
1✔
454
                column_name: "foo".to_string(),
1✔
455
                donut: false,
1✔
456
            },
1✔
457
            sources: vector_source.into(),
1✔
458
        };
1✔
459

1✔
460
        let execution_context = MockExecutionContext::test_default();
1✔
461

462
        let query_processor = pie_chart
1✔
463
            .boxed()
1✔
464
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
465
            .await
×
466
            .unwrap()
1✔
467
            .query_processor()
1✔
468
            .unwrap()
1✔
469
            .json_vega()
1✔
470
            .unwrap();
1✔
471

472
        let result = query_processor
1✔
473
            .plot_query(
1✔
474
                VectorQueryRectangle {
1✔
475
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
476
                        .unwrap(),
1✔
477
                    time_interval: TimeInterval::default(),
1✔
478
                    spatial_resolution: SpatialResolution::one(),
1✔
479
                },
1✔
480
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
481
            )
1✔
482
            .await
×
483
            .unwrap();
1✔
484

1✔
485
        assert_eq!(
1✔
486
            result,
1✔
487
            geoengine_datatypes::plots::PieChart::new(
1✔
488
                [
1✔
489
                    ("1".to_string(), 2.),
1✔
490
                    ("2".to_string(), 1.),
1✔
491
                    ("3".to_string(), 1.),
1✔
492
                ]
1✔
493
                .into(),
1✔
494
                "foo".to_string(),
1✔
495
                false,
1✔
496
            )
1✔
497
            .unwrap()
1✔
498
            .to_vega_embeddable(false)
1✔
499
            .unwrap()
1✔
500
        );
1✔
501
    }
502

503
    #[tokio::test]
1✔
504
    #[allow(clippy::too_many_lines)]
505
    async fn text_attribute() {
1✔
506
        let dataset_id = DatasetId::new();
1✔
507
        let dataset_name = NamedData::with_system_name("ne_10m_ports");
1✔
508

1✔
509
        let mut execution_context = MockExecutionContext::test_default();
1✔
510
        execution_context.add_meta_data::<_, _, VectorQueryRectangle>(
1✔
511
            DataId::Internal { dataset_id },
1✔
512
            dataset_name.clone(),
1✔
513
            Box::new(StaticMetaData {
1✔
514
                loading_info: OgrSourceDataset {
1✔
515
                    file_name: test_data!("vector/data/ne_10m_ports/ne_10m_ports.shp").into(),
1✔
516
                    layer_name: "ne_10m_ports".to_string(),
1✔
517
                    data_type: Some(VectorDataType::MultiPoint),
1✔
518
                    time: OgrSourceDatasetTimeType::None,
1✔
519
                    default_geometry: None,
1✔
520
                    columns: Some(OgrSourceColumnSpec {
1✔
521
                        format_specifics: None,
1✔
522
                        x: String::new(),
1✔
523
                        y: None,
1✔
524
                        int: vec!["natlscale".to_string()],
1✔
525
                        float: vec!["scalerank".to_string()],
1✔
526
                        text: vec![
1✔
527
                            "featurecla".to_string(),
1✔
528
                            "name".to_string(),
1✔
529
                            "website".to_string(),
1✔
530
                        ],
1✔
531
                        bool: vec![],
1✔
532
                        datetime: vec![],
1✔
533
                        rename: None,
1✔
534
                    }),
1✔
535
                    force_ogr_time_filter: false,
1✔
536
                    force_ogr_spatial_filter: false,
1✔
537
                    on_error: OgrSourceErrorSpec::Ignore,
1✔
538
                    sql_query: None,
1✔
539
                    attribute_query: None,
1✔
540
                    cache_ttl: CacheTtlSeconds::default(),
1✔
541
                },
1✔
542
                result_descriptor: VectorResultDescriptor {
1✔
543
                    data_type: VectorDataType::MultiPoint,
1✔
544
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
545
                    columns: [
1✔
546
                        (
1✔
547
                            "natlscale".to_string(),
1✔
548
                            VectorColumnInfo {
1✔
549
                                data_type: FeatureDataType::Float,
1✔
550
                                measurement: Measurement::Unitless,
1✔
551
                            },
1✔
552
                        ),
1✔
553
                        (
1✔
554
                            "scalerank".to_string(),
1✔
555
                            VectorColumnInfo {
1✔
556
                                data_type: FeatureDataType::Int,
1✔
557
                                measurement: Measurement::Unitless,
1✔
558
                            },
1✔
559
                        ),
1✔
560
                        (
1✔
561
                            "featurecla".to_string(),
1✔
562
                            VectorColumnInfo {
1✔
563
                                data_type: FeatureDataType::Text,
1✔
564
                                measurement: Measurement::Unitless,
1✔
565
                            },
1✔
566
                        ),
1✔
567
                        (
1✔
568
                            "name".to_string(),
1✔
569
                            VectorColumnInfo {
1✔
570
                                data_type: FeatureDataType::Text,
1✔
571
                                measurement: Measurement::Unitless,
1✔
572
                            },
1✔
573
                        ),
1✔
574
                        (
1✔
575
                            "website".to_string(),
1✔
576
                            VectorColumnInfo {
1✔
577
                                data_type: FeatureDataType::Text,
1✔
578
                                measurement: Measurement::Unitless,
1✔
579
                            },
1✔
580
                        ),
1✔
581
                    ]
1✔
582
                    .iter()
1✔
583
                    .cloned()
1✔
584
                    .collect(),
1✔
585
                    time: None,
1✔
586
                    bbox: None,
1✔
587
                },
1✔
588
                phantom: Default::default(),
1✔
589
            }),
1✔
590
        );
1✔
591

1✔
592
        let pie_chart = PieChart {
1✔
593
            params: PieChartParams::Count {
1✔
594
                column_name: "name".to_string(),
1✔
595
                donut: false,
1✔
596
            },
1✔
597
            sources: OgrSource {
1✔
598
                params: OgrSourceParameters {
1✔
599
                    data: dataset_name.clone(),
1✔
600
                    attribute_projection: None,
1✔
601
                    attribute_filters: None,
1✔
602
                },
1✔
603
            }
1✔
604
            .boxed()
1✔
605
            .into(),
1✔
606
        };
1✔
607

608
        let query_processor = pie_chart
1✔
609
            .boxed()
1✔
610
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
611
            .await
×
612
            .unwrap()
1✔
613
            .query_processor()
1✔
614
            .unwrap()
1✔
615
            .json_vega()
1✔
616
            .unwrap();
1✔
617

618
        let result = query_processor
1✔
619
            .plot_query(
1✔
620
                VectorQueryRectangle {
1✔
621
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
622
                        .unwrap(),
1✔
623
                    time_interval: TimeInterval::default(),
1✔
624
                    spatial_resolution: SpatialResolution::one(),
1✔
625
                },
1✔
626
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
627
            )
1✔
628
            .await
34✔
629
            .unwrap_err();
1✔
630

1✔
631
        assert_eq!(
1✔
632
            result.to_string(),
1✔
633
            "PieChart: The number of slices is too high. Maximum is 32."
1✔
634
        );
1✔
635

636
        let pie_chart = PieChart {
1✔
637
            params: PieChartParams::Count {
1✔
638
                column_name: "name".to_string(),
1✔
639
                donut: false,
1✔
640
            },
1✔
641
            sources: OgrSource {
1✔
642
                params: OgrSourceParameters {
1✔
643
                    data: dataset_name,
1✔
644
                    attribute_projection: None,
1✔
645
                    attribute_filters: Some(vec![AttributeFilter {
1✔
646
                        attribute: "name".to_string(),
1✔
647
                        ranges: vec![("E".to_string()..="F".to_string()).into()],
1✔
648
                        keep_nulls: false,
1✔
649
                    }]),
1✔
650
                },
1✔
651
            }
1✔
652
            .boxed()
1✔
653
            .into(),
1✔
654
        };
1✔
655

656
        let query_processor = pie_chart
1✔
657
            .boxed()
1✔
658
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
659
            .await
×
660
            .unwrap()
1✔
661
            .query_processor()
1✔
662
            .unwrap()
1✔
663
            .json_vega()
1✔
664
            .unwrap();
1✔
665

666
        let result = query_processor
1✔
667
            .plot_query(
1✔
668
                VectorQueryRectangle {
1✔
669
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
670
                        .unwrap(),
1✔
671
                    time_interval: TimeInterval::default(),
1✔
672
                    spatial_resolution: SpatialResolution::one(),
1✔
673
                },
1✔
674
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
675
            )
1✔
676
            .await
16✔
677
            .unwrap();
1✔
678

1✔
679
        assert_eq!(
1✔
680
            result,
1✔
681
            geoengine_datatypes::plots::PieChart::new(
1✔
682
                [
1✔
683
                    ("Esquimalt".to_string(), 1.),
1✔
684
                    ("Eckernforde".to_string(), 1.),
1✔
685
                    ("Escanaba".to_string(), 1.),
1✔
686
                    ("Esperance".to_string(), 1.),
1✔
687
                    ("Eden".to_string(), 1.),
1✔
688
                    ("Esmeraldas".to_string(), 1.),
1✔
689
                    ("Europoort".to_string(), 1.),
1✔
690
                    ("Elat".to_string(), 1.),
1✔
691
                    ("Emden".to_string(), 1.),
1✔
692
                    ("Esbjerg".to_string(), 1.),
1✔
693
                    ("Ensenada".to_string(), 1.),
1✔
694
                    ("East London".to_string(), 1.),
1✔
695
                    ("Erie".to_string(), 1.),
1✔
696
                    ("Eureka".to_string(), 1.),
1✔
697
                ]
1✔
698
                .into(),
1✔
699
                "name".to_string(),
1✔
700
                false,
1✔
701
            )
1✔
702
            .unwrap()
1✔
703
            .to_vega_embeddable(false)
1✔
704
            .unwrap()
1✔
705
        );
1✔
706
    }
707

708
    #[tokio::test]
1✔
709
    async fn empty_feature_collection() {
1✔
710
        let measurement = Measurement::classification(
1✔
711
            "foo".to_string(),
1✔
712
            [(1, "A".to_string())].into_iter().collect(),
1✔
713
        );
1✔
714

1✔
715
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
716
            vec![DataCollection::from_slices(
1✔
717
                &[] as &[NoGeometry],
1✔
718
                &[] as &[TimeInterval],
1✔
719
                &[("foo", FeatureData::Float(vec![]))],
1✔
720
            )
1✔
721
            .unwrap()],
1✔
722
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
723
        )
1✔
724
        .boxed();
1✔
725

1✔
726
        let pie_chart = PieChart {
1✔
727
            params: PieChartParams::Count {
1✔
728
                column_name: "foo".to_string(),
1✔
729
                donut: false,
1✔
730
            },
1✔
731
            sources: vector_source.into(),
1✔
732
        };
1✔
733

1✔
734
        let execution_context = MockExecutionContext::test_default();
1✔
735

736
        let query_processor = pie_chart
1✔
737
            .boxed()
1✔
738
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
739
            .await
×
740
            .unwrap()
1✔
741
            .query_processor()
1✔
742
            .unwrap()
1✔
743
            .json_vega()
1✔
744
            .unwrap();
1✔
745

746
        let result = query_processor
1✔
747
            .plot_query(
1✔
748
                VectorQueryRectangle {
1✔
749
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
750
                        .unwrap(),
1✔
751
                    time_interval: TimeInterval::default(),
1✔
752
                    spatial_resolution: SpatialResolution::one(),
1✔
753
                },
1✔
754
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
755
            )
1✔
756
            .await
×
757
            .unwrap();
1✔
758

1✔
759
        assert_eq!(
1✔
760
            result,
1✔
761
            geoengine_datatypes::plots::PieChart::new(
1✔
762
                Default::default(),
1✔
763
                "foo".to_string(),
1✔
764
                false,
1✔
765
            )
1✔
766
            .unwrap()
1✔
767
            .to_vega_embeddable(false)
1✔
768
            .unwrap()
1✔
769
        );
1✔
770
    }
771

772
    #[tokio::test]
1✔
773
    async fn feature_collection_with_one_feature() {
1✔
774
        let measurement = Measurement::classification(
1✔
775
            "foo".to_string(),
1✔
776
            [(5, "A".to_string())].into_iter().collect(),
1✔
777
        );
1✔
778

1✔
779
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
780
            vec![DataCollection::from_slices(
1✔
781
                &[] as &[NoGeometry],
1✔
782
                &[TimeInterval::default()],
1✔
783
                &[("foo", FeatureData::Float(vec![5.0]))],
1✔
784
            )
1✔
785
            .unwrap()],
1✔
786
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
787
        )
1✔
788
        .boxed();
1✔
789

1✔
790
        let pie_chart = PieChart {
1✔
791
            params: PieChartParams::Count {
1✔
792
                column_name: "foo".to_string(),
1✔
793
                donut: false,
1✔
794
            },
1✔
795
            sources: vector_source.into(),
1✔
796
        };
1✔
797

1✔
798
        let execution_context = MockExecutionContext::test_default();
1✔
799

800
        let query_processor = pie_chart
1✔
801
            .boxed()
1✔
802
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
803
            .await
×
804
            .unwrap()
1✔
805
            .query_processor()
1✔
806
            .unwrap()
1✔
807
            .json_vega()
1✔
808
            .unwrap();
1✔
809

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

1✔
823
        assert_eq!(
1✔
824
            result,
1✔
825
            geoengine_datatypes::plots::PieChart::new(
1✔
826
                [("A".to_string(), 1.),].into(),
1✔
827
                "foo".to_string(),
1✔
828
                false,
1✔
829
            )
1✔
830
            .unwrap()
1✔
831
            .to_vega_embeddable(false)
1✔
832
            .unwrap()
1✔
833
        );
1✔
834
    }
835
}
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