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

geo-engine / geoengine / 5006008836

pending completion
5006008836

push

github

GitHub
Merge #785 #787

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

96010 of 107707 relevant lines covered (89.14%)

72676.46 hits per line

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

95.2
/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
6✔
66
                    .column_measurement(&column_name) else {
6✔
67
                        return Err(Error::ColumnDoesNotExist {
×
68
                            column: column_name,
×
69
                        });
×
70
                    };
71

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

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

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

99
    span_fn!(PieChart);
×
100
}
101

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

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

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

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

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

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

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

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

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

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

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

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

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

6✔
227
        // TODO: parallelize
6✔
228

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

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

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

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

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

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

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

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

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

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

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

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

283
    use super::*;
284

285
    use crate::engine::{
286
        ChunkByteSize, MockExecutionContext, MockQueryContext, StaticMetaData, VectorColumnInfo,
287
        VectorOperator, VectorResultDescriptor,
288
    };
289
    use crate::mock::MockFeatureCollectionSource;
290
    use crate::source::{
291
        AttributeFilter, OgrSource, OgrSourceColumnSpec, OgrSourceDataset,
292
        OgrSourceDatasetTimeType, OgrSourceErrorSpec, OgrSourceParameters,
293
    };
294
    use crate::test_data;
295
    use geoengine_datatypes::dataset::{DataId, DatasetId};
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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