• 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

94.59
/operators/src/plot/class_histogram.rs
1
use crate::engine::{
2
    CanonicOperatorName, ExecutionContext, InitializedPlotOperator, InitializedRasterOperator,
3
    InitializedVectorOperator, Operator, OperatorName, PlotOperator, PlotQueryProcessor,
4
    PlotResultDescriptor, QueryContext, SingleRasterOrVectorSource, TypedPlotQueryProcessor,
5
    TypedRasterQueryProcessor, TypedVectorQueryProcessor,
6
};
7
use crate::engine::{QueryProcessor, WorkflowOperatorPath};
8
use crate::error;
9
use crate::error::Error;
10
use crate::util::input::RasterOrVectorOperator;
11
use crate::util::Result;
12
use async_trait::async_trait;
13
use futures::StreamExt;
14
use geoengine_datatypes::collections::FeatureCollectionInfos;
15
use geoengine_datatypes::plots::{BarChart, Plot, PlotData};
16
use geoengine_datatypes::primitives::{
17
    AxisAlignedRectangle, BoundingBox2D, ClassificationMeasurement, FeatureDataType, Measurement,
18
    VectorQueryRectangle,
19
};
20
use num_traits::AsPrimitive;
21
use serde::{Deserialize, Serialize};
22
use snafu::{ensure, OptionExt};
23
use std::collections::HashMap;
24

25
pub const CLASS_HISTOGRAM_OPERATOR_NAME: &str = "ClassHistogram";
26

27
/// A class histogram plot about either a raster or a vector input.
28
///
29
/// For vector inputs, it calculates the histogram on one of its attributes.
30
///
31
pub type ClassHistogram = Operator<ClassHistogramParams, SingleRasterOrVectorSource>;
32

33
impl OperatorName for ClassHistogram {
34
    const TYPE_NAME: &'static str = "ClassHistogram";
35
}
36

37
/// The parameter spec for `Histogram`
38
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9✔
39
#[serde(rename_all = "camelCase")]
40
pub struct ClassHistogramParams {
41
    /// Name of the (numeric) attribute to compute the histogram on. Fails if set for rasters.
42
    pub column_name: Option<String>,
43
}
44

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

9✔
55
        Ok(match self.sources.source {
9✔
56
            RasterOrVectorOperator::Raster(raster_source) => {
4✔
57
                ensure!(
4✔
58
                    self.params.column_name.is_none(),
4✔
59
                    error::InvalidOperatorSpec {
1✔
60
                        reason: "Histogram on raster input must not have `columnName` field set"
1✔
61
                            .to_string(),
1✔
62
                    }
1✔
63
                );
64

65
                let raster_source = raster_source
3✔
66
                    .initialize(path.clone_and_append(0), context)
3✔
67
                    .await?;
×
68

69
                let in_desc = raster_source.result_descriptor();
3✔
70

71
                let source_measurement = match &in_desc.measurement {
3✔
72
                    Measurement::Classification(measurement) => measurement.clone(),
3✔
73
                    _ => {
74
                        return Err(Error::InvalidOperatorSpec {
×
75
                            reason: "Source measurement mut be classification".to_string(),
×
76
                        })
×
77
                    }
78
                };
79

80
                InitializedClassHistogram::new(
3✔
81
                    name,
3✔
82
                    PlotResultDescriptor {
3✔
83
                        spatial_reference: in_desc.spatial_reference,
3✔
84
                        time: in_desc.time,
3✔
85
                        // converting `SpatialPartition2D` to `BoundingBox2D` is ok here, because is makes the covered area only larger
3✔
86
                        bbox: in_desc
3✔
87
                            .bbox
3✔
88
                            .and_then(|p| BoundingBox2D::new(p.lower_left(), p.upper_right()).ok()),
3✔
89
                    },
3✔
90
                    self.params.column_name,
3✔
91
                    source_measurement,
3✔
92
                    raster_source,
3✔
93
                )
3✔
94
                .boxed()
3✔
95
            }
96
            RasterOrVectorOperator::Vector(vector_source) => {
5✔
97
                let column_name =
5✔
98
                    self.params
5✔
99
                        .column_name
5✔
100
                        .as_ref()
5✔
101
                        .context(error::InvalidOperatorSpec {
5✔
102
                            reason: "Histogram on vector input is missing `columnName` field"
5✔
103
                                .to_string(),
5✔
104
                        })?;
5✔
105

106
                let vector_source = vector_source
5✔
107
                    .initialize(path.clone_and_append(0), context)
5✔
108
                    .await?;
×
109

110
                match vector_source
5✔
111
                    .result_descriptor()
5✔
112
                    .column_data_type(column_name)
5✔
113
                {
114
                    None => {
115
                        return Err(Error::ColumnDoesNotExist {
×
116
                            column: column_name.to_string(),
×
117
                        });
×
118
                    }
119
                    Some(FeatureDataType::Text | FeatureDataType::DateTime) => {
120
                        return Err(Error::InvalidOperatorSpec {
1✔
121
                            reason: format!("column `{column_name}` must be numerical"),
1✔
122
                        });
1✔
123
                    }
124
                    Some(
125
                        FeatureDataType::Int
126
                        | FeatureDataType::Float
127
                        | FeatureDataType::Bool
128
                        | FeatureDataType::Category,
129
                    ) => {
4✔
130
                        // okay
4✔
131
                    }
4✔
132
                }
4✔
133

4✔
134
                let in_desc = vector_source.result_descriptor().clone();
4✔
135

136
                let source_measurement = match in_desc.column_measurement(column_name) {
4✔
137
                    Some(Measurement::Classification(measurement)) => measurement.clone(),
4✔
138
                    _ => {
139
                        return Err(Error::InvalidOperatorSpec {
×
140
                            reason: "Source measurement mut be classification".to_string(),
×
141
                        })
×
142
                    }
143
                };
144

145
                InitializedClassHistogram::new(
4✔
146
                    name,
4✔
147
                    in_desc.into(),
4✔
148
                    self.params.column_name,
4✔
149
                    source_measurement,
4✔
150
                    vector_source,
4✔
151
                )
4✔
152
                .boxed()
4✔
153
            }
154
        })
155
    }
18✔
156

157
    span_fn!(ClassHistogram);
×
158
}
159

160
/// The initialization of `Histogram`
161
pub struct InitializedClassHistogram<Op> {
162
    name: CanonicOperatorName,
163
    result_descriptor: PlotResultDescriptor,
164
    source_measurement: ClassificationMeasurement,
165
    source: Op,
166
    column_name: Option<String>,
167
}
168

169
impl<Op> InitializedClassHistogram<Op> {
170
    pub fn new(
7✔
171
        name: CanonicOperatorName,
7✔
172
        result_descriptor: PlotResultDescriptor,
7✔
173
        column_name: Option<String>,
7✔
174
        source_measurement: ClassificationMeasurement,
7✔
175
        source: Op,
7✔
176
    ) -> Self {
7✔
177
        Self {
7✔
178
            name,
7✔
179
            result_descriptor,
7✔
180
            source_measurement,
7✔
181
            source,
7✔
182
            column_name,
7✔
183
        }
7✔
184
    }
7✔
185
}
186

187
impl InitializedPlotOperator for InitializedClassHistogram<Box<dyn InitializedRasterOperator>> {
188
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
3✔
189
        let processor = ClassHistogramRasterQueryProcessor {
3✔
190
            input: self.source.query_processor()?,
3✔
191
            measurement: self.source_measurement.clone(),
3✔
192
        };
3✔
193

3✔
194
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
3✔
195
    }
3✔
196

197
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
198
        &self.result_descriptor
×
199
    }
×
200

201
    fn canonic_name(&self) -> CanonicOperatorName {
×
202
        self.name.clone()
×
203
    }
×
204
}
205

206
impl InitializedPlotOperator for InitializedClassHistogram<Box<dyn InitializedVectorOperator>> {
207
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
4✔
208
        let processor = ClassHistogramVectorQueryProcessor {
4✔
209
            input: self.source.query_processor()?,
4✔
210
            column_name: self.column_name.clone().unwrap_or_default(),
4✔
211
            measurement: self.source_measurement.clone(),
4✔
212
        };
4✔
213

4✔
214
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
4✔
215
    }
4✔
216

217
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
218
        &self.result_descriptor
×
219
    }
×
220

221
    fn canonic_name(&self) -> CanonicOperatorName {
×
222
        self.name.clone()
×
223
    }
×
224
}
225

226
/// A query processor that calculates the Histogram about its raster inputs.
227
pub struct ClassHistogramRasterQueryProcessor {
228
    input: TypedRasterQueryProcessor,
229
    measurement: ClassificationMeasurement,
230
}
231

232
/// A query processor that calculates the Histogram about its vector inputs.
233
pub struct ClassHistogramVectorQueryProcessor {
234
    input: TypedVectorQueryProcessor,
235
    column_name: String,
236
    measurement: ClassificationMeasurement,
237
}
238

239
#[async_trait]
240
impl PlotQueryProcessor for ClassHistogramRasterQueryProcessor {
241
    type OutputFormat = PlotData;
242

243
    fn plot_type(&self) -> &'static str {
×
244
        CLASS_HISTOGRAM_OPERATOR_NAME
×
245
    }
×
246

247
    async fn plot_query<'p>(
3✔
248
        &'p self,
3✔
249
        query: VectorQueryRectangle,
3✔
250
        ctx: &'p dyn QueryContext,
3✔
251
    ) -> Result<Self::OutputFormat> {
3✔
252
        self.process(query, ctx).await
3✔
253
    }
6✔
254
}
255

256
#[async_trait]
257
impl PlotQueryProcessor for ClassHistogramVectorQueryProcessor {
258
    type OutputFormat = PlotData;
259

260
    fn plot_type(&self) -> &'static str {
×
261
        CLASS_HISTOGRAM_OPERATOR_NAME
×
262
    }
×
263

264
    async fn plot_query<'p>(
4✔
265
        &'p self,
4✔
266
        query: VectorQueryRectangle,
4✔
267
        ctx: &'p dyn QueryContext,
4✔
268
    ) -> Result<Self::OutputFormat> {
4✔
269
        self.process(query, ctx).await
4✔
270
    }
8✔
271
}
272

273
impl ClassHistogramRasterQueryProcessor {
274
    async fn process<'p>(
3✔
275
        &'p self,
3✔
276
        query: VectorQueryRectangle,
3✔
277
        ctx: &'p dyn QueryContext,
3✔
278
    ) -> Result<<ClassHistogramRasterQueryProcessor as PlotQueryProcessor>::OutputFormat> {
3✔
279
        let mut class_counts: HashMap<u8, u64> = self
3✔
280
            .measurement
3✔
281
            .classes
3✔
282
            .keys()
3✔
283
            .map(|key| (*key, 0))
8✔
284
            .collect();
3✔
285

3✔
286
        call_on_generic_raster_processor!(&self.input, processor => {
3✔
287
            let mut query = processor.query(query.into(), ctx).await?;
3✔
288

289
            while let Some(tile) = query.next().await {
15✔
290
                match tile?.grid_array {
12✔
291
                    geoengine_datatypes::raster::GridOrEmpty::Grid(g) => {
3✔
292
                        g.masked_element_deref_iterator().for_each(|value_option| {
3✔
293
                            if let Some(v) = value_option {
18✔
294
                                if let Some(count) = class_counts.get_mut(&v.as_()) {
18✔
295
                                    *count += 1;
12✔
296
                                }
12✔
297
                            }
×
298
                        });
18✔
299
                    },
300
                    geoengine_datatypes::raster::GridOrEmpty::Empty(_) => (), // ignore no data,
9✔
301
                }
302
            }
303
        });
304

305
        // TODO: display NO-DATA count?
306

307
        let bar_chart = BarChart::new(
3✔
308
            class_counts
3✔
309
                .into_iter()
3✔
310
                .map(|(class, count)| {
8✔
311
                    (
8✔
312
                        self.measurement
8✔
313
                            .classes
8✔
314
                            .get(&class)
8✔
315
                            .cloned()
8✔
316
                            .unwrap_or_default(),
8✔
317
                        count,
8✔
318
                    )
8✔
319
                })
8✔
320
                .collect(),
3✔
321
            Measurement::Classification(self.measurement.clone()).to_string(),
3✔
322
            "Frequency".to_string(),
3✔
323
        );
3✔
324
        let chart = bar_chart.to_vega_embeddable(false)?;
3✔
325

326
        Ok(chart)
3✔
327
    }
3✔
328
}
329

330
impl ClassHistogramVectorQueryProcessor {
331
    async fn process<'p>(
4✔
332
        &'p self,
4✔
333
        query: VectorQueryRectangle,
4✔
334
        ctx: &'p dyn QueryContext,
4✔
335
    ) -> Result<<ClassHistogramRasterQueryProcessor as PlotQueryProcessor>::OutputFormat> {
4✔
336
        let mut class_counts: HashMap<u8, u64> = self
4✔
337
            .measurement
4✔
338
            .classes
4✔
339
            .keys()
4✔
340
            .map(|key| (*key, 0))
8✔
341
            .collect();
4✔
342

4✔
343
        call_on_generic_vector_processor!(&self.input, processor => {
4✔
344
            let mut query = processor.query(query, ctx).await?;
4✔
345

346
            while let Some(collection) = query.next().await {
9✔
347
                let collection = collection?;
5✔
348

349
                let feature_data = collection.data(&self.column_name).expect("checked in param");
5✔
350

351
                for v in feature_data.float_options_iter() {
19✔
352
                    match v {
19✔
353
                        None => (), // ignore no data
2✔
354
                        Some(index) => if let Some(count) = class_counts.get_mut(&(index as u8)) {
17✔
355
                            *count += 1;
16✔
356
                        },
1✔
357
                        // else… ignore values that are not in the class list
358
                    }
359

360
                }
361

362
            }
363
        });
364

365
        // TODO: display NO-DATA count?
366

367
        let bar_chart = BarChart::new(
4✔
368
            class_counts
4✔
369
                .into_iter()
4✔
370
                .map(|(class, count)| {
8✔
371
                    (
8✔
372
                        self.measurement
8✔
373
                            .classes
8✔
374
                            .get(&class)
8✔
375
                            .cloned()
8✔
376
                            .unwrap_or_default(),
8✔
377
                        count,
8✔
378
                    )
8✔
379
                })
8✔
380
                .collect(),
4✔
381
            Measurement::Classification(self.measurement.clone()).to_string(),
4✔
382
            "Frequency".to_string(),
4✔
383
        );
4✔
384
        let chart = bar_chart.to_vega_embeddable(false)?;
4✔
385

386
        Ok(chart)
4✔
387
    }
4✔
388
}
389

390
#[cfg(test)]
391
mod tests {
392
    use super::*;
393

394
    use crate::engine::{
395
        ChunkByteSize, MockExecutionContext, MockQueryContext, RasterOperator,
396
        RasterResultDescriptor, StaticMetaData, VectorColumnInfo, VectorOperator,
397
        VectorResultDescriptor,
398
    };
399
    use crate::mock::{MockFeatureCollectionSource, MockRasterSource, MockRasterSourceParams};
400
    use crate::source::{
401
        OgrSourceColumnSpec, OgrSourceDataset, OgrSourceDatasetTimeType, OgrSourceErrorSpec,
402
    };
403
    use crate::test_data;
404
    use geoengine_datatypes::dataset::{DataId, DatasetId};
405
    use geoengine_datatypes::primitives::{
406
        BoundingBox2D, DateTime, FeatureData, NoGeometry, SpatialResolution, TimeInterval,
407
    };
408
    use geoengine_datatypes::raster::{
409
        Grid2D, RasterDataType, RasterTile2D, TileInformation, TilingSpecification,
410
    };
411
    use geoengine_datatypes::spatial_reference::SpatialReference;
412
    use geoengine_datatypes::util::test::TestDefault;
413
    use geoengine_datatypes::util::Identifier;
414
    use geoengine_datatypes::{
415
        collections::{DataCollection, VectorDataType},
416
        primitives::MultiPoint,
417
    };
418
    use serde_json::json;
419

420
    #[test]
1✔
421
    fn serialization() {
1✔
422
        let histogram = ClassHistogram {
1✔
423
            params: ClassHistogramParams {
1✔
424
                column_name: Some("foobar".to_string()),
1✔
425
            },
1✔
426
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
427
                .boxed()
1✔
428
                .into(),
1✔
429
        };
1✔
430

1✔
431
        let serialized = json!({
1✔
432
            "type": "ClassHistogram",
1✔
433
            "params": {
1✔
434
                "columnName": "foobar",
1✔
435
            },
1✔
436
            "sources": {
1✔
437
                "source": {
1✔
438
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
439
                    "params": {
1✔
440
                        "collections": [],
1✔
441
                        "spatialReference": "EPSG:4326",
1✔
442
                        "measurements": {},
1✔
443
                    }
1✔
444
                }
1✔
445
            }
1✔
446
        })
1✔
447
        .to_string();
1✔
448

1✔
449
        let deserialized: ClassHistogram = serde_json::from_str(&serialized).unwrap();
1✔
450

1✔
451
        assert_eq!(deserialized.params, histogram.params);
1✔
452
    }
1✔
453

454
    #[tokio::test]
1✔
455
    async fn column_name_for_raster_source() {
1✔
456
        let histogram = ClassHistogram {
1✔
457
            params: ClassHistogramParams {
1✔
458
                column_name: Some("foo".to_string()),
1✔
459
            },
1✔
460
            sources: mock_raster_source().into(),
1✔
461
        };
1✔
462

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

465
        assert!(histogram
1✔
466
            .boxed()
1✔
467
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
468
            .await
×
469
            .is_err());
1✔
470
    }
471

472
    fn mock_raster_source() -> Box<dyn RasterOperator> {
2✔
473
        MockRasterSource {
2✔
474
            params: MockRasterSourceParams {
2✔
475
                data: vec![RasterTile2D::new_with_tile_info(
2✔
476
                    TimeInterval::default(),
2✔
477
                    TileInformation {
2✔
478
                        global_geo_transform: TestDefault::test_default(),
2✔
479
                        global_tile_position: [0, 0].into(),
2✔
480
                        tile_size_in_pixels: [3, 2].into(),
2✔
481
                    },
2✔
482
                    Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
2✔
483
                        .unwrap()
2✔
484
                        .into(),
2✔
485
                )],
2✔
486
                result_descriptor: RasterResultDescriptor {
2✔
487
                    data_type: RasterDataType::U8,
2✔
488
                    spatial_reference: SpatialReference::epsg_4326().into(),
2✔
489
                    measurement: Measurement::classification(
2✔
490
                        "test-class".to_string(),
2✔
491
                        [
2✔
492
                            (1, "A".to_string()),
2✔
493
                            (2, "B".to_string()),
2✔
494
                            (3, "C".to_string()),
2✔
495
                            (4, "D".to_string()),
2✔
496
                            (5, "E".to_string()),
2✔
497
                            (6, "F".to_string()),
2✔
498
                        ]
2✔
499
                        .into_iter()
2✔
500
                        .collect(),
2✔
501
                    ),
2✔
502
                    time: None,
2✔
503
                    bbox: None,
2✔
504
                    resolution: None,
2✔
505
                },
2✔
506
            },
2✔
507
        }
2✔
508
        .boxed()
2✔
509
    }
2✔
510

511
    #[tokio::test]
1✔
512
    async fn simple_raster() {
1✔
513
        let tile_size_in_pixels = [3, 2].into();
1✔
514
        let tiling_specification = TilingSpecification {
1✔
515
            origin_coordinate: [0.0, 0.0].into(),
1✔
516
            tile_size_in_pixels,
1✔
517
        };
1✔
518
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
519

1✔
520
        let histogram = ClassHistogram {
1✔
521
            params: ClassHistogramParams { column_name: None },
1✔
522
            sources: mock_raster_source().into(),
1✔
523
        };
1✔
524

525
        let query_processor = histogram
1✔
526
            .boxed()
1✔
527
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
528
            .await
×
529
            .unwrap()
1✔
530
            .query_processor()
1✔
531
            .unwrap()
1✔
532
            .json_vega()
1✔
533
            .unwrap();
1✔
534

535
        let result = query_processor
1✔
536
            .plot_query(
1✔
537
                VectorQueryRectangle {
1✔
538
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
539
                    time_interval: TimeInterval::default(),
1✔
540
                    spatial_resolution: SpatialResolution::one(),
1✔
541
                },
1✔
542
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
543
            )
1✔
544
            .await
×
545
            .unwrap();
1✔
546

1✔
547
        assert_eq!(
1✔
548
            result,
1✔
549
            BarChart::new(
1✔
550
                [
1✔
551
                    ("A".to_string(), 1),
1✔
552
                    ("B".to_string(), 1),
1✔
553
                    ("C".to_string(), 1),
1✔
554
                    ("D".to_string(), 1),
1✔
555
                    ("E".to_string(), 1),
1✔
556
                    ("F".to_string(), 1),
1✔
557
                ]
1✔
558
                .into_iter()
1✔
559
                .collect(),
1✔
560
                "test-class".to_string(),
1✔
561
                "Frequency".to_string()
1✔
562
            )
1✔
563
            .to_vega_embeddable(true)
1✔
564
            .unwrap()
1✔
565
        );
1✔
566
    }
567

568
    #[tokio::test]
1✔
569
    async fn vector_data() {
1✔
570
        let measurement = Measurement::classification(
1✔
571
            "foo".to_string(),
1✔
572
            [
1✔
573
                (1, "A".to_string()),
1✔
574
                (2, "B".to_string()),
1✔
575
                (3, "C".to_string()),
1✔
576
            ]
1✔
577
            .into_iter()
1✔
578
            .collect(),
1✔
579
        );
1✔
580

1✔
581
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
582
            vec![
1✔
583
                DataCollection::from_slices(
1✔
584
                    &[] as &[NoGeometry],
1✔
585
                    &[TimeInterval::default(); 8],
1✔
586
                    &[("foo", FeatureData::Int(vec![1, 1, 2, 2, 3, 3, 1, 2]))],
1✔
587
                )
1✔
588
                .unwrap(),
1✔
589
                DataCollection::from_slices(
1✔
590
                    &[] as &[NoGeometry],
1✔
591
                    &[TimeInterval::default(); 4],
1✔
592
                    &[("foo", FeatureData::Int(vec![1, 1, 2, 3]))],
1✔
593
                )
1✔
594
                .unwrap(),
1✔
595
            ],
1✔
596
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
597
        )
1✔
598
        .boxed();
1✔
599

1✔
600
        let histogram = ClassHistogram {
1✔
601
            params: ClassHistogramParams {
1✔
602
                column_name: Some("foo".to_string()),
1✔
603
            },
1✔
604
            sources: vector_source.into(),
1✔
605
        };
1✔
606

1✔
607
        let execution_context = MockExecutionContext::test_default();
1✔
608

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

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

1✔
632
        assert_eq!(
1✔
633
            result,
1✔
634
            BarChart::new(
1✔
635
                [
1✔
636
                    ("A".to_string(), 5),
1✔
637
                    ("B".to_string(), 4),
1✔
638
                    ("C".to_string(), 3),
1✔
639
                ]
1✔
640
                .into_iter()
1✔
641
                .collect(),
1✔
642
                "foo".to_string(),
1✔
643
                "Frequency".to_string()
1✔
644
            )
1✔
645
            .to_vega_embeddable(true)
1✔
646
            .unwrap()
1✔
647
        );
1✔
648
    }
649

650
    #[tokio::test]
1✔
651
    async fn vector_data_with_nulls() {
1✔
652
        let measurement = Measurement::classification(
1✔
653
            "foo".to_string(),
1✔
654
            [
1✔
655
                (1, "A".to_string()),
1✔
656
                (2, "B".to_string()),
1✔
657
                (4, "C".to_string()),
1✔
658
            ]
1✔
659
            .into_iter()
1✔
660
            .collect(),
1✔
661
        );
1✔
662

1✔
663
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
664
            vec![DataCollection::from_slices(
1✔
665
                &[] as &[NoGeometry],
1✔
666
                &[TimeInterval::default(); 6],
1✔
667
                &[(
1✔
668
                    "foo",
1✔
669
                    FeatureData::NullableFloat(vec![
1✔
670
                        Some(1.),
1✔
671
                        Some(2.),
1✔
672
                        None,
1✔
673
                        Some(4.),
1✔
674
                        None,
1✔
675
                        Some(5.),
1✔
676
                    ]),
1✔
677
                )],
1✔
678
            )
1✔
679
            .unwrap()],
1✔
680
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
681
        )
1✔
682
        .boxed();
1✔
683

1✔
684
        let histogram = ClassHistogram {
1✔
685
            params: ClassHistogramParams {
1✔
686
                column_name: Some("foo".to_string()),
1✔
687
            },
1✔
688
            sources: vector_source.into(),
1✔
689
        };
1✔
690

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

693
        let query_processor = histogram
1✔
694
            .boxed()
1✔
695
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
696
            .await
×
697
            .unwrap()
1✔
698
            .query_processor()
1✔
699
            .unwrap()
1✔
700
            .json_vega()
1✔
701
            .unwrap();
1✔
702

703
        let result = query_processor
1✔
704
            .plot_query(
1✔
705
                VectorQueryRectangle {
1✔
706
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
707
                        .unwrap(),
1✔
708
                    time_interval: TimeInterval::default(),
1✔
709
                    spatial_resolution: SpatialResolution::one(),
1✔
710
                },
1✔
711
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
712
            )
1✔
713
            .await
×
714
            .unwrap();
1✔
715

1✔
716
        assert_eq!(
1✔
717
            result,
1✔
718
            BarChart::new(
1✔
719
                [
1✔
720
                    ("A".to_string(), 1),
1✔
721
                    ("B".to_string(), 1),
1✔
722
                    ("C".to_string(), 1),
1✔
723
                ]
1✔
724
                .into_iter()
1✔
725
                .collect(),
1✔
726
                "foo".to_string(),
1✔
727
                "Frequency".to_string()
1✔
728
            )
1✔
729
            .to_vega_embeddable(true)
1✔
730
            .unwrap()
1✔
731
        );
1✔
732
    }
733

734
    #[tokio::test]
1✔
735
    #[allow(clippy::too_many_lines)]
736
    async fn text_attribute() {
1✔
737
        let dataset_id = DatasetId::new();
1✔
738

1✔
739
        let workflow = serde_json::json!({
1✔
740
            "type": "Histogram",
1✔
741
            "params": {
1✔
742
                "columnName": "featurecla",
1✔
743
            },
1✔
744
            "sources": {
1✔
745
                "source": {
1✔
746
                    "type": "OgrSource",
1✔
747
                    "params": {
1✔
748
                        "data": {
1✔
749
                            "type": "internal",
1✔
750
                            "datasetId": dataset_id
1✔
751
                        },
1✔
752
                        "attributeProjection": null
1✔
753
                    },
1✔
754
                }
1✔
755
            }
1✔
756
        });
1✔
757
        let histogram: ClassHistogram = serde_json::from_value(workflow).unwrap();
1✔
758

1✔
759
        let mut execution_context = MockExecutionContext::test_default();
1✔
760
        execution_context.add_meta_data::<_, _, VectorQueryRectangle>(
1✔
761
            DataId::Internal { dataset_id },
1✔
762
            Box::new(StaticMetaData {
1✔
763
                loading_info: OgrSourceDataset {
1✔
764
                    file_name: test_data!("vector/data/ne_10m_ports/ne_10m_ports.shp").into(),
1✔
765
                    layer_name: "ne_10m_ports".to_string(),
1✔
766
                    data_type: Some(VectorDataType::MultiPoint),
1✔
767
                    time: OgrSourceDatasetTimeType::None,
1✔
768
                    default_geometry: None,
1✔
769
                    columns: Some(OgrSourceColumnSpec {
1✔
770
                        format_specifics: None,
1✔
771
                        x: String::new(),
1✔
772
                        y: None,
1✔
773
                        int: vec!["natlscale".to_string()],
1✔
774
                        float: vec!["scalerank".to_string()],
1✔
775
                        text: vec![
1✔
776
                            "featurecla".to_string(),
1✔
777
                            "name".to_string(),
1✔
778
                            "website".to_string(),
1✔
779
                        ],
1✔
780
                        bool: vec![],
1✔
781
                        datetime: vec![],
1✔
782
                        rename: None,
1✔
783
                    }),
1✔
784
                    force_ogr_time_filter: false,
1✔
785
                    force_ogr_spatial_filter: false,
1✔
786
                    on_error: OgrSourceErrorSpec::Ignore,
1✔
787
                    sql_query: None,
1✔
788
                    attribute_query: None,
1✔
789
                },
1✔
790
                result_descriptor: VectorResultDescriptor {
1✔
791
                    data_type: VectorDataType::MultiPoint,
1✔
792
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
793
                    columns: [
1✔
794
                        (
1✔
795
                            "natlscale".to_string(),
1✔
796
                            VectorColumnInfo {
1✔
797
                                data_type: FeatureDataType::Float,
1✔
798
                                measurement: Measurement::Unitless,
1✔
799
                            },
1✔
800
                        ),
1✔
801
                        (
1✔
802
                            "scalerank".to_string(),
1✔
803
                            VectorColumnInfo {
1✔
804
                                data_type: FeatureDataType::Int,
1✔
805
                                measurement: Measurement::Unitless,
1✔
806
                            },
1✔
807
                        ),
1✔
808
                        (
1✔
809
                            "featurecla".to_string(),
1✔
810
                            VectorColumnInfo {
1✔
811
                                data_type: FeatureDataType::Text,
1✔
812
                                measurement: Measurement::Unitless,
1✔
813
                            },
1✔
814
                        ),
1✔
815
                        (
1✔
816
                            "name".to_string(),
1✔
817
                            VectorColumnInfo {
1✔
818
                                data_type: FeatureDataType::Text,
1✔
819
                                measurement: Measurement::Unitless,
1✔
820
                            },
1✔
821
                        ),
1✔
822
                        (
1✔
823
                            "website".to_string(),
1✔
824
                            VectorColumnInfo {
1✔
825
                                data_type: FeatureDataType::Text,
1✔
826
                                measurement: Measurement::Unitless,
1✔
827
                            },
1✔
828
                        ),
1✔
829
                    ]
1✔
830
                    .iter()
1✔
831
                    .cloned()
1✔
832
                    .collect(),
1✔
833
                    time: None,
1✔
834
                    bbox: None,
1✔
835
                },
1✔
836
                phantom: Default::default(),
1✔
837
            }),
1✔
838
        );
1✔
839

840
        if let Err(Error::InvalidOperatorSpec { reason }) = histogram
1✔
841
            .boxed()
1✔
842
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
843
            .await
×
844
        {
845
            assert_eq!(reason, "column `featurecla` must be numerical");
1✔
846
        } else {
847
            panic!("we currently don't support text features, but this went through");
×
848
        }
849
    }
850

851
    #[tokio::test]
1✔
852
    async fn no_data_raster() {
1✔
853
        let tile_size_in_pixels = [3, 2].into();
1✔
854
        let tiling_specification = TilingSpecification {
1✔
855
            origin_coordinate: [0.0, 0.0].into(),
1✔
856
            tile_size_in_pixels,
1✔
857
        };
1✔
858
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
859

1✔
860
        let measurement = Measurement::classification(
1✔
861
            "foo".to_string(),
1✔
862
            [(1, "A".to_string())].into_iter().collect(),
1✔
863
        );
1✔
864

1✔
865
        let histogram = ClassHistogram {
1✔
866
            params: ClassHistogramParams { column_name: None },
1✔
867
            sources: MockRasterSource {
1✔
868
                params: MockRasterSourceParams {
1✔
869
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
870
                        TimeInterval::default(),
1✔
871
                        TileInformation {
1✔
872
                            global_geo_transform: TestDefault::test_default(),
1✔
873
                            global_tile_position: [0, 0].into(),
1✔
874
                            tile_size_in_pixels,
1✔
875
                        },
1✔
876
                        Grid2D::new(tile_size_in_pixels, vec![0, 0, 0, 0, 0, 0])
1✔
877
                            .unwrap()
1✔
878
                            .into(),
1✔
879
                    )],
1✔
880
                    result_descriptor: RasterResultDescriptor {
1✔
881
                        data_type: RasterDataType::U8,
1✔
882
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
883
                        measurement,
1✔
884
                        time: None,
1✔
885
                        bbox: None,
1✔
886
                        resolution: None,
1✔
887
                    },
1✔
888
                },
1✔
889
            }
1✔
890
            .boxed()
1✔
891
            .into(),
1✔
892
        };
1✔
893

894
        let query_processor = histogram
1✔
895
            .boxed()
1✔
896
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
897
            .await
×
898
            .unwrap()
1✔
899
            .query_processor()
1✔
900
            .unwrap()
1✔
901
            .json_vega()
1✔
902
            .unwrap();
1✔
903

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

1✔
916
        assert_eq!(
1✔
917
            result,
1✔
918
            BarChart::new(
1✔
919
                [("A".to_string(), 0)].into_iter().collect(),
1✔
920
                "foo".to_string(),
1✔
921
                "Frequency".to_string()
1✔
922
            )
1✔
923
            .to_vega_embeddable(true)
1✔
924
            .unwrap()
1✔
925
        );
1✔
926
    }
927

928
    #[tokio::test]
1✔
929
    async fn empty_feature_collection() {
1✔
930
        let measurement = Measurement::classification(
1✔
931
            "foo".to_string(),
1✔
932
            [(1, "A".to_string())].into_iter().collect(),
1✔
933
        );
1✔
934

1✔
935
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
936
            vec![DataCollection::from_slices(
1✔
937
                &[] as &[NoGeometry],
1✔
938
                &[] as &[TimeInterval],
1✔
939
                &[("foo", FeatureData::Float(vec![]))],
1✔
940
            )
1✔
941
            .unwrap()],
1✔
942
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
943
        )
1✔
944
        .boxed();
1✔
945

1✔
946
        let histogram = ClassHistogram {
1✔
947
            params: ClassHistogramParams {
1✔
948
                column_name: Some("foo".to_string()),
1✔
949
            },
1✔
950
            sources: vector_source.into(),
1✔
951
        };
1✔
952

1✔
953
        let execution_context = MockExecutionContext::test_default();
1✔
954

955
        let query_processor = histogram
1✔
956
            .boxed()
1✔
957
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
958
            .await
×
959
            .unwrap()
1✔
960
            .query_processor()
1✔
961
            .unwrap()
1✔
962
            .json_vega()
1✔
963
            .unwrap();
1✔
964

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

1✔
978
        assert_eq!(
1✔
979
            result,
1✔
980
            BarChart::new(
1✔
981
                [("A".to_string(), 0)].into_iter().collect(),
1✔
982
                "foo".to_string(),
1✔
983
                "Frequency".to_string()
1✔
984
            )
1✔
985
            .to_vega_embeddable(true)
1✔
986
            .unwrap()
1✔
987
        );
1✔
988
    }
989

990
    #[tokio::test]
1✔
991
    async fn feature_collection_with_one_feature() {
1✔
992
        let measurement = Measurement::classification(
1✔
993
            "foo".to_string(),
1✔
994
            [(5, "A".to_string())].into_iter().collect(),
1✔
995
        );
1✔
996

1✔
997
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
998
            vec![DataCollection::from_slices(
1✔
999
                &[] as &[NoGeometry],
1✔
1000
                &[TimeInterval::default()],
1✔
1001
                &[("foo", FeatureData::Float(vec![5.0]))],
1✔
1002
            )
1✔
1003
            .unwrap()],
1✔
1004
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
1005
        )
1✔
1006
        .boxed();
1✔
1007

1✔
1008
        let histogram = ClassHistogram {
1✔
1009
            params: ClassHistogramParams {
1✔
1010
                column_name: Some("foo".to_string()),
1✔
1011
            },
1✔
1012
            sources: vector_source.into(),
1✔
1013
        };
1✔
1014

1✔
1015
        let execution_context = MockExecutionContext::test_default();
1✔
1016

1017
        let query_processor = histogram
1✔
1018
            .boxed()
1✔
1019
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1020
            .await
×
1021
            .unwrap()
1✔
1022
            .query_processor()
1✔
1023
            .unwrap()
1✔
1024
            .json_vega()
1✔
1025
            .unwrap();
1✔
1026

1027
        let result = query_processor
1✔
1028
            .plot_query(
1✔
1029
                VectorQueryRectangle {
1✔
1030
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1031
                        .unwrap(),
1✔
1032
                    time_interval: TimeInterval::default(),
1✔
1033
                    spatial_resolution: SpatialResolution::one(),
1✔
1034
                },
1✔
1035
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1036
            )
1✔
1037
            .await
×
1038
            .unwrap();
1✔
1039

1✔
1040
        assert_eq!(
1✔
1041
            result,
1✔
1042
            BarChart::new(
1✔
1043
                [("A".to_string(), 1)].into_iter().collect(),
1✔
1044
                "foo".to_string(),
1✔
1045
                "Frequency".to_string()
1✔
1046
            )
1✔
1047
            .to_vega_embeddable(true)
1✔
1048
            .unwrap()
1✔
1049
        );
1✔
1050
    }
1051

1052
    #[tokio::test]
1✔
1053
    async fn single_value_raster_stream() {
1✔
1054
        let tile_size_in_pixels = [3, 2].into();
1✔
1055
        let tiling_specification = TilingSpecification {
1✔
1056
            origin_coordinate: [0.0, 0.0].into(),
1✔
1057
            tile_size_in_pixels,
1✔
1058
        };
1✔
1059
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1060

1✔
1061
        let measurement = Measurement::classification(
1✔
1062
            "foo".to_string(),
1✔
1063
            [(4, "D".to_string())].into_iter().collect(),
1✔
1064
        );
1✔
1065

1✔
1066
        let histogram = ClassHistogram {
1✔
1067
            params: ClassHistogramParams { column_name: None },
1✔
1068
            sources: MockRasterSource {
1✔
1069
                params: MockRasterSourceParams {
1✔
1070
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1071
                        TimeInterval::default(),
1✔
1072
                        TileInformation {
1✔
1073
                            global_geo_transform: TestDefault::test_default(),
1✔
1074
                            global_tile_position: [0, 0].into(),
1✔
1075
                            tile_size_in_pixels,
1✔
1076
                        },
1✔
1077
                        Grid2D::new(tile_size_in_pixels, vec![4; 6]).unwrap().into(),
1✔
1078
                    )],
1✔
1079
                    result_descriptor: RasterResultDescriptor {
1✔
1080
                        data_type: RasterDataType::U8,
1✔
1081
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1082
                        measurement,
1✔
1083
                        time: None,
1✔
1084
                        bbox: None,
1✔
1085
                        resolution: None,
1✔
1086
                    },
1✔
1087
                },
1✔
1088
            }
1✔
1089
            .boxed()
1✔
1090
            .into(),
1✔
1091
        };
1✔
1092

1093
        let query_processor = histogram
1✔
1094
            .boxed()
1✔
1095
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1096
            .await
×
1097
            .unwrap()
1✔
1098
            .query_processor()
1✔
1099
            .unwrap()
1✔
1100
            .json_vega()
1✔
1101
            .unwrap();
1✔
1102

1103
        let result = query_processor
1✔
1104
            .plot_query(
1✔
1105
                VectorQueryRectangle {
1✔
1106
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
1107
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1108
                        2013, 12, 1, 12, 0, 0,
1✔
1109
                    ))
1✔
1110
                    .unwrap(),
1✔
1111
                    spatial_resolution: SpatialResolution::one(),
1✔
1112
                },
1✔
1113
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1114
            )
1✔
1115
            .await
×
1116
            .unwrap();
1✔
1117

1✔
1118
        assert_eq!(
1✔
1119
            result,
1✔
1120
            BarChart::new(
1✔
1121
                [("D".to_string(), 6)].into_iter().collect(),
1✔
1122
                "foo".to_string(),
1✔
1123
                "Frequency".to_string()
1✔
1124
            )
1✔
1125
            .to_vega_embeddable(true)
1✔
1126
            .unwrap()
1✔
1127
        );
1✔
1128
    }
1129
}
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