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

geo-engine / geoengine / 3929938005

pending completion
3929938005

push

github

GitHub
Merge #713

84930 of 96741 relevant lines covered (87.79%)

79640.1 hits per line

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

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

28
pub const CLASS_HISTOGRAM_OPERATOR_NAME: &str = "ClassHistogram";
29

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

36
impl OperatorName for ClassHistogram {
37
    const TYPE_NAME: &'static str = "ClassHistogram";
38
}
39

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

48
#[typetag::serde]
×
49
#[async_trait]
50
impl PlotOperator for ClassHistogram {
51
    async fn _initialize(
9✔
52
        self: Box<Self>,
9✔
53
        context: &dyn ExecutionContext,
9✔
54
    ) -> Result<Box<dyn InitializedPlotOperator>> {
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 initialized = raster_source.initialize(context).await?;
3✔
66

67
                let in_desc = initialized.result_descriptor();
3✔
68

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

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

103
                let vector_source = vector_source.initialize(context).await?;
5✔
104

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

4✔
129
                let in_desc = vector_source.result_descriptor().clone();
4✔
130

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

140
                InitializedClassHistogram::new(
4✔
141
                    in_desc.into(),
4✔
142
                    self.params.column_name,
4✔
143
                    source_measurement,
4✔
144
                    vector_source,
4✔
145
                )
4✔
146
                .boxed()
4✔
147
            }
148
        })
149
    }
18✔
150

151
    span_fn!(ClassHistogram);
×
152
}
153

154
/// The initialization of `Histogram`
155
pub struct InitializedClassHistogram<Op> {
156
    result_descriptor: PlotResultDescriptor,
157
    source_measurement: ClassificationMeasurement,
158
    source: Op,
159
    column_name: Option<String>,
160
}
161

162
impl<Op> InitializedClassHistogram<Op> {
163
    pub fn new(
7✔
164
        result_descriptor: PlotResultDescriptor,
7✔
165
        column_name: Option<String>,
7✔
166
        source_measurement: ClassificationMeasurement,
7✔
167
        source: Op,
7✔
168
    ) -> Self {
7✔
169
        Self {
7✔
170
            result_descriptor,
7✔
171
            source_measurement,
7✔
172
            source,
7✔
173
            column_name,
7✔
174
        }
7✔
175
    }
7✔
176
}
177

178
impl InitializedPlotOperator for InitializedClassHistogram<Box<dyn InitializedRasterOperator>> {
179
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
3✔
180
        let processor = ClassHistogramRasterQueryProcessor {
3✔
181
            input: self.source.query_processor()?,
3✔
182
            measurement: self.source_measurement.clone(),
3✔
183
        };
3✔
184

3✔
185
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
3✔
186
    }
3✔
187

188
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
189
        &self.result_descriptor
×
190
    }
×
191
}
192

193
impl InitializedPlotOperator for InitializedClassHistogram<Box<dyn InitializedVectorOperator>> {
194
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
4✔
195
        let processor = ClassHistogramVectorQueryProcessor {
4✔
196
            input: self.source.query_processor()?,
4✔
197
            column_name: self.column_name.clone().unwrap_or_default(),
4✔
198
            measurement: self.source_measurement.clone(),
4✔
199
        };
4✔
200

4✔
201
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
4✔
202
    }
4✔
203

204
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
205
        &self.result_descriptor
×
206
    }
×
207
}
208

209
/// A query processor that calculates the Histogram about its raster inputs.
210
pub struct ClassHistogramRasterQueryProcessor {
211
    input: TypedRasterQueryProcessor,
212
    measurement: ClassificationMeasurement,
213
}
214

215
/// A query processor that calculates the Histogram about its vector inputs.
216
pub struct ClassHistogramVectorQueryProcessor {
217
    input: TypedVectorQueryProcessor,
218
    column_name: String,
219
    measurement: ClassificationMeasurement,
220
}
221

222
#[async_trait]
223
impl PlotQueryProcessor for ClassHistogramRasterQueryProcessor {
224
    type OutputFormat = PlotData;
225

226
    fn plot_type(&self) -> &'static str {
×
227
        CLASS_HISTOGRAM_OPERATOR_NAME
×
228
    }
×
229

230
    async fn plot_query<'p>(
3✔
231
        &'p self,
3✔
232
        query: VectorQueryRectangle,
3✔
233
        ctx: &'p dyn QueryContext,
3✔
234
    ) -> Result<Self::OutputFormat> {
3✔
235
        self.process(query, ctx).await
3✔
236
    }
6✔
237
}
238

239
#[async_trait]
240
impl PlotQueryProcessor for ClassHistogramVectorQueryProcessor {
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>(
4✔
248
        &'p self,
4✔
249
        query: VectorQueryRectangle,
4✔
250
        ctx: &'p dyn QueryContext,
4✔
251
    ) -> Result<Self::OutputFormat> {
4✔
252
        self.process(query, ctx).await
4✔
253
    }
8✔
254
}
255

256
impl ClassHistogramRasterQueryProcessor {
257
    async fn process<'p>(
3✔
258
        &'p self,
3✔
259
        query: VectorQueryRectangle,
3✔
260
        ctx: &'p dyn QueryContext,
3✔
261
    ) -> Result<<ClassHistogramRasterQueryProcessor as PlotQueryProcessor>::OutputFormat> {
3✔
262
        let mut class_counts: HashMap<u8, u64> = self
3✔
263
            .measurement
3✔
264
            .classes
3✔
265
            .keys()
3✔
266
            .map(|key| (*key, 0))
8✔
267
            .collect();
3✔
268

3✔
269
        call_on_generic_raster_processor!(&self.input, processor => {
3✔
270
            let mut query = processor.query(query.into(), ctx).await?;
3✔
271

272
            while let Some(tile) = query.next().await {
6✔
273
                match tile?.grid_array {
3✔
274
                    geoengine_datatypes::raster::GridOrEmpty::Grid(g) => {
3✔
275
                        g.masked_element_deref_iterator().for_each(|value_option| {
3✔
276
                            if let Some(v) = value_option {
18✔
277
                                if let Some(count) = class_counts.get_mut(&v.as_()) {
18✔
278
                                    *count += 1;
12✔
279
                                }
12✔
280
                            }
×
281
                        });
18✔
282
                    },
283
                    geoengine_datatypes::raster::GridOrEmpty::Empty(_) => (), // ignore no data,
×
284
                }
285
            }
286
        });
287

288
        // TODO: display NO-DATA count?
289

290
        let bar_chart = BarChart::new(
3✔
291
            class_counts
3✔
292
                .into_iter()
3✔
293
                .map(|(class, count)| {
8✔
294
                    (
8✔
295
                        self.measurement
8✔
296
                            .classes
8✔
297
                            .get(&class)
8✔
298
                            .cloned()
8✔
299
                            .unwrap_or_default(),
8✔
300
                        count,
8✔
301
                    )
8✔
302
                })
8✔
303
                .collect(),
3✔
304
            Measurement::Classification(self.measurement.clone()).to_string(),
3✔
305
            "Frequency".to_string(),
3✔
306
        );
3✔
307
        let chart = bar_chart.to_vega_embeddable(false)?;
3✔
308

309
        Ok(chart)
3✔
310
    }
3✔
311
}
312

313
impl ClassHistogramVectorQueryProcessor {
314
    async fn process<'p>(
4✔
315
        &'p self,
4✔
316
        query: VectorQueryRectangle,
4✔
317
        ctx: &'p dyn QueryContext,
4✔
318
    ) -> Result<<ClassHistogramRasterQueryProcessor as PlotQueryProcessor>::OutputFormat> {
4✔
319
        let mut class_counts: HashMap<u8, u64> = self
4✔
320
            .measurement
4✔
321
            .classes
4✔
322
            .keys()
4✔
323
            .map(|key| (*key, 0))
8✔
324
            .collect();
4✔
325

4✔
326
        call_on_generic_vector_processor!(&self.input, processor => {
4✔
327
            let mut query = processor.query(query, ctx).await?;
4✔
328

329
            while let Some(collection) = query.next().await {
9✔
330
                let collection = collection?;
5✔
331

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

334
                for v in feature_data.float_options_iter() {
19✔
335
                    match v {
19✔
336
                        None => (), // ignore no data
2✔
337
                        Some(index) => if let Some(count) = class_counts.get_mut(&(index as u8)) {
17✔
338
                            *count += 1;
16✔
339
                        },
1✔
340
                        // else… ignore values that are not in the class list
341
                    }
342

343
                }
344

345
            }
346
        });
347

348
        // TODO: display NO-DATA count?
349

350
        let bar_chart = BarChart::new(
4✔
351
            class_counts
4✔
352
                .into_iter()
4✔
353
                .map(|(class, count)| {
8✔
354
                    (
8✔
355
                        self.measurement
8✔
356
                            .classes
8✔
357
                            .get(&class)
8✔
358
                            .cloned()
8✔
359
                            .unwrap_or_default(),
8✔
360
                        count,
8✔
361
                    )
8✔
362
                })
8✔
363
                .collect(),
4✔
364
            Measurement::Classification(self.measurement.clone()).to_string(),
4✔
365
            "Frequency".to_string(),
4✔
366
        );
4✔
367
        let chart = bar_chart.to_vega_embeddable(false)?;
4✔
368

369
        Ok(chart)
4✔
370
    }
4✔
371
}
372

373
#[cfg(test)]
374
mod tests {
375
    use super::*;
376

377
    use crate::engine::{
378
        ChunkByteSize, MockExecutionContext, MockQueryContext, RasterOperator,
379
        RasterResultDescriptor, StaticMetaData, VectorColumnInfo, VectorOperator,
380
        VectorResultDescriptor,
381
    };
382
    use crate::mock::{MockFeatureCollectionSource, MockRasterSource, MockRasterSourceParams};
383
    use crate::source::{
384
        OgrSourceColumnSpec, OgrSourceDataset, OgrSourceDatasetTimeType, OgrSourceErrorSpec,
385
    };
386
    use crate::test_data;
387
    use geoengine_datatypes::dataset::{DataId, DatasetId};
388
    use geoengine_datatypes::primitives::{
389
        BoundingBox2D, DateTime, FeatureData, NoGeometry, SpatialResolution, TimeInterval,
390
    };
391
    use geoengine_datatypes::raster::{
392
        Grid2D, RasterDataType, RasterTile2D, TileInformation, TilingSpecification,
393
    };
394
    use geoengine_datatypes::spatial_reference::SpatialReference;
395
    use geoengine_datatypes::util::test::TestDefault;
396
    use geoengine_datatypes::util::Identifier;
397
    use geoengine_datatypes::{
398
        collections::{DataCollection, VectorDataType},
399
        primitives::MultiPoint,
400
    };
401
    use serde_json::json;
402

403
    #[test]
1✔
404
    fn serialization() {
1✔
405
        let histogram = ClassHistogram {
1✔
406
            params: ClassHistogramParams {
1✔
407
                column_name: Some("foobar".to_string()),
1✔
408
            },
1✔
409
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
410
                .boxed()
1✔
411
                .into(),
1✔
412
        };
1✔
413

1✔
414
        let serialized = json!({
1✔
415
            "type": "ClassHistogram",
1✔
416
            "params": {
1✔
417
                "columnName": "foobar",
1✔
418
            },
1✔
419
            "sources": {
1✔
420
                "source": {
1✔
421
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
422
                    "params": {
1✔
423
                        "collections": [],
1✔
424
                        "spatialReference": "EPSG:4326",
1✔
425
                        "measurements": {},
1✔
426
                    }
1✔
427
                }
1✔
428
            }
1✔
429
        })
1✔
430
        .to_string();
1✔
431

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

1✔
434
        assert_eq!(deserialized.params, histogram.params);
1✔
435
    }
1✔
436

437
    #[tokio::test]
1✔
438
    async fn column_name_for_raster_source() {
1✔
439
        let histogram = ClassHistogram {
1✔
440
            params: ClassHistogramParams {
1✔
441
                column_name: Some("foo".to_string()),
1✔
442
            },
1✔
443
            sources: mock_raster_source().into(),
1✔
444
        };
1✔
445

1✔
446
        let execution_context = MockExecutionContext::test_default();
1✔
447

448
        assert!(histogram
1✔
449
            .boxed()
1✔
450
            .initialize(&execution_context)
1✔
451
            .await
×
452
            .is_err());
1✔
453
    }
454

455
    fn mock_raster_source() -> Box<dyn RasterOperator> {
2✔
456
        MockRasterSource {
2✔
457
            params: MockRasterSourceParams {
2✔
458
                data: vec![RasterTile2D::new_with_tile_info(
2✔
459
                    TimeInterval::default(),
2✔
460
                    TileInformation {
2✔
461
                        global_geo_transform: TestDefault::test_default(),
2✔
462
                        global_tile_position: [0, 0].into(),
2✔
463
                        tile_size_in_pixels: [3, 2].into(),
2✔
464
                    },
2✔
465
                    Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
2✔
466
                        .unwrap()
2✔
467
                        .into(),
2✔
468
                )],
2✔
469
                result_descriptor: RasterResultDescriptor {
2✔
470
                    data_type: RasterDataType::U8,
2✔
471
                    spatial_reference: SpatialReference::epsg_4326().into(),
2✔
472
                    measurement: Measurement::classification(
2✔
473
                        "test-class".to_string(),
2✔
474
                        [
2✔
475
                            (1, "A".to_string()),
2✔
476
                            (2, "B".to_string()),
2✔
477
                            (3, "C".to_string()),
2✔
478
                            (4, "D".to_string()),
2✔
479
                            (5, "E".to_string()),
2✔
480
                            (6, "F".to_string()),
2✔
481
                        ]
2✔
482
                        .into_iter()
2✔
483
                        .collect(),
2✔
484
                    ),
2✔
485
                    time: None,
2✔
486
                    bbox: None,
2✔
487
                    resolution: None,
2✔
488
                },
2✔
489
            },
2✔
490
        }
2✔
491
        .boxed()
2✔
492
    }
2✔
493

494
    #[tokio::test]
1✔
495
    async fn simple_raster() {
1✔
496
        let tile_size_in_pixels = [3, 2].into();
1✔
497
        let tiling_specification = TilingSpecification {
1✔
498
            origin_coordinate: [0.0, 0.0].into(),
1✔
499
            tile_size_in_pixels,
1✔
500
        };
1✔
501
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
502

1✔
503
        let histogram = ClassHistogram {
1✔
504
            params: ClassHistogramParams { column_name: None },
1✔
505
            sources: mock_raster_source().into(),
1✔
506
        };
1✔
507

508
        let query_processor = histogram
1✔
509
            .boxed()
1✔
510
            .initialize(&execution_context)
1✔
511
            .await
×
512
            .unwrap()
1✔
513
            .query_processor()
1✔
514
            .unwrap()
1✔
515
            .json_vega()
1✔
516
            .unwrap();
1✔
517

518
        let result = query_processor
1✔
519
            .plot_query(
1✔
520
                VectorQueryRectangle {
1✔
521
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
522
                    time_interval: TimeInterval::default(),
1✔
523
                    spatial_resolution: SpatialResolution::one(),
1✔
524
                },
1✔
525
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
526
            )
1✔
527
            .await
×
528
            .unwrap();
1✔
529

1✔
530
        assert_eq!(
1✔
531
            result,
1✔
532
            BarChart::new(
1✔
533
                [
1✔
534
                    ("A".to_string(), 1),
1✔
535
                    ("B".to_string(), 1),
1✔
536
                    ("C".to_string(), 1),
1✔
537
                    ("D".to_string(), 1),
1✔
538
                    ("E".to_string(), 1),
1✔
539
                    ("F".to_string(), 1),
1✔
540
                ]
1✔
541
                .into_iter()
1✔
542
                .collect(),
1✔
543
                "test-class".to_string(),
1✔
544
                "Frequency".to_string()
1✔
545
            )
1✔
546
            .to_vega_embeddable(true)
1✔
547
            .unwrap()
1✔
548
        );
1✔
549
    }
550

551
    #[tokio::test]
1✔
552
    async fn vector_data() {
1✔
553
        let measurement = Measurement::classification(
1✔
554
            "foo".to_string(),
1✔
555
            [
1✔
556
                (1, "A".to_string()),
1✔
557
                (2, "B".to_string()),
1✔
558
                (3, "C".to_string()),
1✔
559
            ]
1✔
560
            .into_iter()
1✔
561
            .collect(),
1✔
562
        );
1✔
563

1✔
564
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
565
            vec![
1✔
566
                DataCollection::from_slices(
1✔
567
                    &[] as &[NoGeometry],
1✔
568
                    &[TimeInterval::default(); 8],
1✔
569
                    &[("foo", FeatureData::Int(vec![1, 1, 2, 2, 3, 3, 1, 2]))],
1✔
570
                )
1✔
571
                .unwrap(),
1✔
572
                DataCollection::from_slices(
1✔
573
                    &[] as &[NoGeometry],
1✔
574
                    &[TimeInterval::default(); 4],
1✔
575
                    &[("foo", FeatureData::Int(vec![1, 1, 2, 3]))],
1✔
576
                )
1✔
577
                .unwrap(),
1✔
578
            ],
1✔
579
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
580
        )
1✔
581
        .boxed();
1✔
582

1✔
583
        let histogram = ClassHistogram {
1✔
584
            params: ClassHistogramParams {
1✔
585
                column_name: Some("foo".to_string()),
1✔
586
            },
1✔
587
            sources: vector_source.into(),
1✔
588
        };
1✔
589

1✔
590
        let execution_context = MockExecutionContext::test_default();
1✔
591

592
        let query_processor = histogram
1✔
593
            .boxed()
1✔
594
            .initialize(&execution_context)
1✔
595
            .await
×
596
            .unwrap()
1✔
597
            .query_processor()
1✔
598
            .unwrap()
1✔
599
            .json_vega()
1✔
600
            .unwrap();
1✔
601

602
        let result = query_processor
1✔
603
            .plot_query(
1✔
604
                VectorQueryRectangle {
1✔
605
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
606
                        .unwrap(),
1✔
607
                    time_interval: TimeInterval::default(),
1✔
608
                    spatial_resolution: SpatialResolution::one(),
1✔
609
                },
1✔
610
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
611
            )
1✔
612
            .await
×
613
            .unwrap();
1✔
614

1✔
615
        assert_eq!(
1✔
616
            result,
1✔
617
            BarChart::new(
1✔
618
                [
1✔
619
                    ("A".to_string(), 5),
1✔
620
                    ("B".to_string(), 4),
1✔
621
                    ("C".to_string(), 3),
1✔
622
                ]
1✔
623
                .into_iter()
1✔
624
                .collect(),
1✔
625
                "foo".to_string(),
1✔
626
                "Frequency".to_string()
1✔
627
            )
1✔
628
            .to_vega_embeddable(true)
1✔
629
            .unwrap()
1✔
630
        );
1✔
631
    }
632

633
    #[tokio::test]
1✔
634
    async fn vector_data_with_nulls() {
1✔
635
        let measurement = Measurement::classification(
1✔
636
            "foo".to_string(),
1✔
637
            [
1✔
638
                (1, "A".to_string()),
1✔
639
                (2, "B".to_string()),
1✔
640
                (4, "C".to_string()),
1✔
641
            ]
1✔
642
            .into_iter()
1✔
643
            .collect(),
1✔
644
        );
1✔
645

1✔
646
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
647
            vec![DataCollection::from_slices(
1✔
648
                &[] as &[NoGeometry],
1✔
649
                &[TimeInterval::default(); 6],
1✔
650
                &[(
1✔
651
                    "foo",
1✔
652
                    FeatureData::NullableFloat(vec![
1✔
653
                        Some(1.),
1✔
654
                        Some(2.),
1✔
655
                        None,
1✔
656
                        Some(4.),
1✔
657
                        None,
1✔
658
                        Some(5.),
1✔
659
                    ]),
1✔
660
                )],
1✔
661
            )
1✔
662
            .unwrap()],
1✔
663
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
664
        )
1✔
665
        .boxed();
1✔
666

1✔
667
        let histogram = ClassHistogram {
1✔
668
            params: ClassHistogramParams {
1✔
669
                column_name: Some("foo".to_string()),
1✔
670
            },
1✔
671
            sources: vector_source.into(),
1✔
672
        };
1✔
673

1✔
674
        let execution_context = MockExecutionContext::test_default();
1✔
675

676
        let query_processor = histogram
1✔
677
            .boxed()
1✔
678
            .initialize(&execution_context)
1✔
679
            .await
×
680
            .unwrap()
1✔
681
            .query_processor()
1✔
682
            .unwrap()
1✔
683
            .json_vega()
1✔
684
            .unwrap();
1✔
685

686
        let result = query_processor
1✔
687
            .plot_query(
1✔
688
                VectorQueryRectangle {
1✔
689
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
690
                        .unwrap(),
1✔
691
                    time_interval: TimeInterval::default(),
1✔
692
                    spatial_resolution: SpatialResolution::one(),
1✔
693
                },
1✔
694
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
695
            )
1✔
696
            .await
×
697
            .unwrap();
1✔
698

1✔
699
        assert_eq!(
1✔
700
            result,
1✔
701
            BarChart::new(
1✔
702
                [
1✔
703
                    ("A".to_string(), 1),
1✔
704
                    ("B".to_string(), 1),
1✔
705
                    ("C".to_string(), 1),
1✔
706
                ]
1✔
707
                .into_iter()
1✔
708
                .collect(),
1✔
709
                "foo".to_string(),
1✔
710
                "Frequency".to_string()
1✔
711
            )
1✔
712
            .to_vega_embeddable(true)
1✔
713
            .unwrap()
1✔
714
        );
1✔
715
    }
716

717
    #[tokio::test]
1✔
718
    #[allow(clippy::too_many_lines)]
719
    async fn text_attribute() {
1✔
720
        let dataset_id = DatasetId::new();
1✔
721

1✔
722
        let workflow = serde_json::json!({
1✔
723
            "type": "Histogram",
1✔
724
            "params": {
1✔
725
                "columnName": "featurecla",
1✔
726
            },
1✔
727
            "sources": {
1✔
728
                "source": {
1✔
729
                    "type": "OgrSource",
1✔
730
                    "params": {
1✔
731
                        "data": {
1✔
732
                            "type": "internal",
1✔
733
                            "datasetId": dataset_id
1✔
734
                        },
1✔
735
                        "attributeProjection": null
1✔
736
                    },
1✔
737
                }
1✔
738
            }
1✔
739
        });
1✔
740
        let histogram: ClassHistogram = serde_json::from_value(workflow).unwrap();
1✔
741

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

823
        if let Err(Error::InvalidOperatorSpec { reason }) =
1✔
824
            histogram.boxed().initialize(&execution_context).await
1✔
825
        {
826
            assert_eq!(reason, "column `featurecla` must be numerical");
1✔
827
        } else {
828
            panic!("we currently don't support text features, but this went through");
×
829
        }
830
    }
831

832
    #[tokio::test]
1✔
833
    async fn no_data_raster() {
1✔
834
        let tile_size_in_pixels = [3, 2].into();
1✔
835
        let tiling_specification = TilingSpecification {
1✔
836
            origin_coordinate: [0.0, 0.0].into(),
1✔
837
            tile_size_in_pixels,
1✔
838
        };
1✔
839
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
840

1✔
841
        let measurement = Measurement::classification(
1✔
842
            "foo".to_string(),
1✔
843
            [(1, "A".to_string())].into_iter().collect(),
1✔
844
        );
1✔
845

1✔
846
        let histogram = ClassHistogram {
1✔
847
            params: ClassHistogramParams { column_name: None },
1✔
848
            sources: MockRasterSource {
1✔
849
                params: MockRasterSourceParams {
1✔
850
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
851
                        TimeInterval::default(),
1✔
852
                        TileInformation {
1✔
853
                            global_geo_transform: TestDefault::test_default(),
1✔
854
                            global_tile_position: [0, 0].into(),
1✔
855
                            tile_size_in_pixels,
1✔
856
                        },
1✔
857
                        Grid2D::new(tile_size_in_pixels, vec![0, 0, 0, 0, 0, 0])
1✔
858
                            .unwrap()
1✔
859
                            .into(),
1✔
860
                    )],
1✔
861
                    result_descriptor: RasterResultDescriptor {
1✔
862
                        data_type: RasterDataType::U8,
1✔
863
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
864
                        measurement,
1✔
865
                        time: None,
1✔
866
                        bbox: None,
1✔
867
                        resolution: None,
1✔
868
                    },
1✔
869
                },
1✔
870
            }
1✔
871
            .boxed()
1✔
872
            .into(),
1✔
873
        };
1✔
874

875
        let query_processor = histogram
1✔
876
            .boxed()
1✔
877
            .initialize(&execution_context)
1✔
878
            .await
×
879
            .unwrap()
1✔
880
            .query_processor()
1✔
881
            .unwrap()
1✔
882
            .json_vega()
1✔
883
            .unwrap();
1✔
884

885
        let result = query_processor
1✔
886
            .plot_query(
1✔
887
                VectorQueryRectangle {
1✔
888
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
889
                    time_interval: TimeInterval::default(),
1✔
890
                    spatial_resolution: SpatialResolution::one(),
1✔
891
                },
1✔
892
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
893
            )
1✔
894
            .await
×
895
            .unwrap();
1✔
896

1✔
897
        assert_eq!(
1✔
898
            result,
1✔
899
            BarChart::new(
1✔
900
                [("A".to_string(), 0)].into_iter().collect(),
1✔
901
                "foo".to_string(),
1✔
902
                "Frequency".to_string()
1✔
903
            )
1✔
904
            .to_vega_embeddable(true)
1✔
905
            .unwrap()
1✔
906
        );
1✔
907
    }
908

909
    #[tokio::test]
1✔
910
    async fn empty_feature_collection() {
1✔
911
        let measurement = Measurement::classification(
1✔
912
            "foo".to_string(),
1✔
913
            [(1, "A".to_string())].into_iter().collect(),
1✔
914
        );
1✔
915

1✔
916
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
917
            vec![DataCollection::from_slices(
1✔
918
                &[] as &[NoGeometry],
1✔
919
                &[] as &[TimeInterval],
1✔
920
                &[("foo", FeatureData::Float(vec![]))],
1✔
921
            )
1✔
922
            .unwrap()],
1✔
923
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
924
        )
1✔
925
        .boxed();
1✔
926

1✔
927
        let histogram = ClassHistogram {
1✔
928
            params: ClassHistogramParams {
1✔
929
                column_name: Some("foo".to_string()),
1✔
930
            },
1✔
931
            sources: vector_source.into(),
1✔
932
        };
1✔
933

1✔
934
        let execution_context = MockExecutionContext::test_default();
1✔
935

936
        let query_processor = histogram
1✔
937
            .boxed()
1✔
938
            .initialize(&execution_context)
1✔
939
            .await
×
940
            .unwrap()
1✔
941
            .query_processor()
1✔
942
            .unwrap()
1✔
943
            .json_vega()
1✔
944
            .unwrap();
1✔
945

946
        let result = query_processor
1✔
947
            .plot_query(
1✔
948
                VectorQueryRectangle {
1✔
949
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
950
                        .unwrap(),
1✔
951
                    time_interval: TimeInterval::default(),
1✔
952
                    spatial_resolution: SpatialResolution::one(),
1✔
953
                },
1✔
954
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
955
            )
1✔
956
            .await
×
957
            .unwrap();
1✔
958

1✔
959
        assert_eq!(
1✔
960
            result,
1✔
961
            BarChart::new(
1✔
962
                [("A".to_string(), 0)].into_iter().collect(),
1✔
963
                "foo".to_string(),
1✔
964
                "Frequency".to_string()
1✔
965
            )
1✔
966
            .to_vega_embeddable(true)
1✔
967
            .unwrap()
1✔
968
        );
1✔
969
    }
970

971
    #[tokio::test]
1✔
972
    async fn feature_collection_with_one_feature() {
1✔
973
        let measurement = Measurement::classification(
1✔
974
            "foo".to_string(),
1✔
975
            [(5, "A".to_string())].into_iter().collect(),
1✔
976
        );
1✔
977

1✔
978
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
979
            vec![DataCollection::from_slices(
1✔
980
                &[] as &[NoGeometry],
1✔
981
                &[TimeInterval::default()],
1✔
982
                &[("foo", FeatureData::Float(vec![5.0]))],
1✔
983
            )
1✔
984
            .unwrap()],
1✔
985
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
986
        )
1✔
987
        .boxed();
1✔
988

1✔
989
        let histogram = ClassHistogram {
1✔
990
            params: ClassHistogramParams {
1✔
991
                column_name: Some("foo".to_string()),
1✔
992
            },
1✔
993
            sources: vector_source.into(),
1✔
994
        };
1✔
995

1✔
996
        let execution_context = MockExecutionContext::test_default();
1✔
997

998
        let query_processor = histogram
1✔
999
            .boxed()
1✔
1000
            .initialize(&execution_context)
1✔
1001
            .await
×
1002
            .unwrap()
1✔
1003
            .query_processor()
1✔
1004
            .unwrap()
1✔
1005
            .json_vega()
1✔
1006
            .unwrap();
1✔
1007

1008
        let result = query_processor
1✔
1009
            .plot_query(
1✔
1010
                VectorQueryRectangle {
1✔
1011
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1012
                        .unwrap(),
1✔
1013
                    time_interval: TimeInterval::default(),
1✔
1014
                    spatial_resolution: SpatialResolution::one(),
1✔
1015
                },
1✔
1016
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1017
            )
1✔
1018
            .await
×
1019
            .unwrap();
1✔
1020

1✔
1021
        assert_eq!(
1✔
1022
            result,
1✔
1023
            BarChart::new(
1✔
1024
                [("A".to_string(), 1)].into_iter().collect(),
1✔
1025
                "foo".to_string(),
1✔
1026
                "Frequency".to_string()
1✔
1027
            )
1✔
1028
            .to_vega_embeddable(true)
1✔
1029
            .unwrap()
1✔
1030
        );
1✔
1031
    }
1032

1033
    #[tokio::test]
1✔
1034
    async fn single_value_raster_stream() {
1✔
1035
        let tile_size_in_pixels = [3, 2].into();
1✔
1036
        let tiling_specification = TilingSpecification {
1✔
1037
            origin_coordinate: [0.0, 0.0].into(),
1✔
1038
            tile_size_in_pixels,
1✔
1039
        };
1✔
1040
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1041

1✔
1042
        let measurement = Measurement::classification(
1✔
1043
            "foo".to_string(),
1✔
1044
            [(4, "D".to_string())].into_iter().collect(),
1✔
1045
        );
1✔
1046

1✔
1047
        let histogram = ClassHistogram {
1✔
1048
            params: ClassHistogramParams { column_name: None },
1✔
1049
            sources: MockRasterSource {
1✔
1050
                params: MockRasterSourceParams {
1✔
1051
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1052
                        TimeInterval::default(),
1✔
1053
                        TileInformation {
1✔
1054
                            global_geo_transform: TestDefault::test_default(),
1✔
1055
                            global_tile_position: [0, 0].into(),
1✔
1056
                            tile_size_in_pixels,
1✔
1057
                        },
1✔
1058
                        Grid2D::new(tile_size_in_pixels, vec![4; 6]).unwrap().into(),
1✔
1059
                    )],
1✔
1060
                    result_descriptor: RasterResultDescriptor {
1✔
1061
                        data_type: RasterDataType::U8,
1✔
1062
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1063
                        measurement,
1✔
1064
                        time: None,
1✔
1065
                        bbox: None,
1✔
1066
                        resolution: None,
1✔
1067
                    },
1✔
1068
                },
1✔
1069
            }
1✔
1070
            .boxed()
1✔
1071
            .into(),
1✔
1072
        };
1✔
1073

1074
        let query_processor = histogram
1✔
1075
            .boxed()
1✔
1076
            .initialize(&execution_context)
1✔
1077
            .await
×
1078
            .unwrap()
1✔
1079
            .query_processor()
1✔
1080
            .unwrap()
1✔
1081
            .json_vega()
1✔
1082
            .unwrap();
1✔
1083

1084
        let result = query_processor
1✔
1085
            .plot_query(
1✔
1086
                VectorQueryRectangle {
1✔
1087
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
1088
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1089
                        2013, 12, 1, 12, 0, 0,
1✔
1090
                    ))
1✔
1091
                    .unwrap(),
1✔
1092
                    spatial_resolution: SpatialResolution::one(),
1✔
1093
                },
1✔
1094
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1095
            )
1✔
1096
            .await
×
1097
            .unwrap();
1✔
1098

1✔
1099
        assert_eq!(
1✔
1100
            result,
1✔
1101
            BarChart::new(
1✔
1102
                [("D".to_string(), 6)].into_iter().collect(),
1✔
1103
                "foo".to_string(),
1✔
1104
                "Frequency".to_string()
1✔
1105
            )
1✔
1106
            .to_vega_embeddable(true)
1✔
1107
            .unwrap()
1✔
1108
        );
1✔
1109
    }
1110
}
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