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

geo-engine / geoengine / 11911118784

19 Nov 2024 10:06AM UTC coverage: 90.448% (-0.2%) from 90.687%
11911118784

push

github

web-flow
Merge pull request #994 from geo-engine/workspace-dependencies

use workspace dependencies, update toolchain, use global lock in expression

9 of 11 new or added lines in 6 files covered. (81.82%)

369 existing lines in 74 files now uncovered.

132871 of 146904 relevant lines covered (90.45%)

54798.62 hits per line

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

96.96
/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, PlotQueryRectangle};
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)]
3✔
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(
52
        self: Box<Self>,
53
        path: WorkflowOperatorPath,
54
        context: &dyn ExecutionContext,
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✔
UNCOV
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.clone_from(&column_name);
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>(
174
        &'p self,
175
        query: PlotQueryRectangle,
176
        ctx: &'p dyn QueryContext,
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>(
52✔
185
    feature_data: &'f FeatureDataRef,
52✔
186
    class_mapping: Option<&'f HashMap<u8, String>>,
52✔
187
) -> Box<dyn Iterator<Item = String> + 'f> {
52✔
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: PlotQueryRectangle,
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

226
        // TODO: parallelize
227

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

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

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

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

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

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

246
                if slices.len() > MAX_NUMBER_OF_SLICES {
5✔
247
                    return Err(PieChartError::TooManySlices.into());
×
248
                }
5✔
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)]
1✔
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::{
296
        BoundingBox2D, FeatureData, FeatureDataType, NoGeometry, PlotSeriesSelection,
297
        SpatialResolution, TimeInterval,
298
    };
299
    use geoengine_datatypes::primitives::{CacheTtlSeconds, VectorQueryRectangle};
300
    use geoengine_datatypes::spatial_reference::SpatialReference;
301
    use geoengine_datatypes::util::test::TestDefault;
302
    use geoengine_datatypes::util::Identifier;
303
    use geoengine_datatypes::{
304
        collections::{DataCollection, VectorDataType},
305
        primitives::MultiPoint,
306
    };
307
    use serde_json::json;
308

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
462
        let execution_context = MockExecutionContext::test_default();
1✔
463

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

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

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

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

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

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

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

1✔
621
        let result = query_processor
1✔
622
            .plot_query(
1✔
623
                PlotQueryRectangle {
1✔
624
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
625
                        .unwrap(),
1✔
626
                    time_interval: TimeInterval::default(),
1✔
627
                    spatial_resolution: SpatialResolution::one(),
1✔
628
                    attributes: PlotSeriesSelection::all(),
1✔
629
                },
1✔
630
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
631
            )
1✔
632
            .await
34✔
633
            .unwrap_err();
1✔
634

1✔
635
        assert_eq!(
1✔
636
            result.to_string(),
1✔
637
            "PieChart error: The number of slices is too high. Maximum is 32."
1✔
638
        );
1✔
639

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

1✔
660
        let query_processor = pie_chart
1✔
661
            .boxed()
1✔
662
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
663
            .await
1✔
664
            .unwrap()
1✔
665
            .query_processor()
1✔
666
            .unwrap()
1✔
667
            .json_vega()
1✔
668
            .unwrap();
1✔
669

1✔
670
        let result = query_processor
1✔
671
            .plot_query(
1✔
672
                PlotQueryRectangle {
1✔
673
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
674
                        .unwrap(),
1✔
675
                    time_interval: TimeInterval::default(),
1✔
676
                    spatial_resolution: SpatialResolution::one(),
1✔
677
                    attributes: PlotSeriesSelection::all(),
1✔
678
                },
1✔
679
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
680
            )
1✔
681
            .await
16✔
682
            .unwrap();
1✔
683

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

713
    #[tokio::test]
714
    async fn empty_feature_collection() {
1✔
715
        let measurement = Measurement::classification(
1✔
716
            "foo".to_string(),
1✔
717
            [(1, "A".to_string())].into_iter().collect(),
1✔
718
        );
1✔
719

1✔
720
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
721
            vec![DataCollection::from_slices(
1✔
722
                &[] as &[NoGeometry],
1✔
723
                &[] as &[TimeInterval],
1✔
724
                &[("foo", FeatureData::Float(vec![]))],
1✔
725
            )
1✔
726
            .unwrap()],
1✔
727
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
728
        )
1✔
729
        .boxed();
1✔
730

1✔
731
        let pie_chart = PieChart {
1✔
732
            params: PieChartParams::Count {
1✔
733
                column_name: "foo".to_string(),
1✔
734
                donut: false,
1✔
735
            },
1✔
736
            sources: vector_source.into(),
1✔
737
        };
1✔
738

1✔
739
        let execution_context = MockExecutionContext::test_default();
1✔
740

1✔
741
        let query_processor = pie_chart
1✔
742
            .boxed()
1✔
743
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
744
            .await
1✔
745
            .unwrap()
1✔
746
            .query_processor()
1✔
747
            .unwrap()
1✔
748
            .json_vega()
1✔
749
            .unwrap();
1✔
750

1✔
751
        let result = query_processor
1✔
752
            .plot_query(
1✔
753
                PlotQueryRectangle {
1✔
754
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
755
                        .unwrap(),
1✔
756
                    time_interval: TimeInterval::default(),
1✔
757
                    spatial_resolution: SpatialResolution::one(),
1✔
758
                    attributes: PlotSeriesSelection::all(),
1✔
759
                },
1✔
760
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
761
            )
1✔
762
            .await
1✔
763
            .unwrap();
1✔
764

1✔
765
        assert_eq!(
1✔
766
            result,
1✔
767
            geoengine_datatypes::plots::PieChart::new(
1✔
768
                Default::default(),
1✔
769
                "foo".to_string(),
1✔
770
                false,
1✔
771
            )
1✔
772
            .unwrap()
1✔
773
            .to_vega_embeddable(false)
1✔
774
            .unwrap()
1✔
775
        );
1✔
776
    }
1✔
777

778
    #[tokio::test]
779
    async fn feature_collection_with_one_feature() {
1✔
780
        let measurement = Measurement::classification(
1✔
781
            "foo".to_string(),
1✔
782
            [(5, "A".to_string())].into_iter().collect(),
1✔
783
        );
1✔
784

1✔
785
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
786
            vec![DataCollection::from_slices(
1✔
787
                &[] as &[NoGeometry],
1✔
788
                &[TimeInterval::default()],
1✔
789
                &[("foo", FeatureData::Float(vec![5.0]))],
1✔
790
            )
1✔
791
            .unwrap()],
1✔
792
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
793
        )
1✔
794
        .boxed();
1✔
795

1✔
796
        let pie_chart = PieChart {
1✔
797
            params: PieChartParams::Count {
1✔
798
                column_name: "foo".to_string(),
1✔
799
                donut: false,
1✔
800
            },
1✔
801
            sources: vector_source.into(),
1✔
802
        };
1✔
803

1✔
804
        let execution_context = MockExecutionContext::test_default();
1✔
805

1✔
806
        let query_processor = pie_chart
1✔
807
            .boxed()
1✔
808
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
809
            .await
1✔
810
            .unwrap()
1✔
811
            .query_processor()
1✔
812
            .unwrap()
1✔
813
            .json_vega()
1✔
814
            .unwrap();
1✔
815

1✔
816
        let result = query_processor
1✔
817
            .plot_query(
1✔
818
                PlotQueryRectangle {
1✔
819
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
820
                        .unwrap(),
1✔
821
                    time_interval: TimeInterval::default(),
1✔
822
                    spatial_resolution: SpatialResolution::one(),
1✔
823
                    attributes: PlotSeriesSelection::all(),
1✔
824
                },
1✔
825
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
826
            )
1✔
827
            .await
1✔
828
            .unwrap();
1✔
829

1✔
830
        assert_eq!(
1✔
831
            result,
1✔
832
            geoengine_datatypes::plots::PieChart::new(
1✔
833
                [("A".to_string(), 1.),].into(),
1✔
834
                "foo".to_string(),
1✔
835
                false,
1✔
836
            )
1✔
837
            .unwrap()
1✔
838
            .to_vega_embeddable(false)
1✔
839
            .unwrap()
1✔
840
        );
1✔
841
    }
1✔
842
}
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