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

geo-engine / geoengine / 10178074589

31 Jul 2024 09:34AM UTC coverage: 91.068% (+0.4%) from 90.682%
10178074589

push

github

web-flow
Merge pull request #973 from geo-engine/remove-XGB-update-toolchain

Remove-XGB-update-toolchain

81 of 88 new or added lines in 29 files covered. (92.05%)

456 existing lines in 119 files now uncovered.

131088 of 143945 relevant lines covered (91.07%)

53581.03 hits per line

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

96.49
/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, BandSelection, BoundingBox2D, ClassificationMeasurement, FeatureDataType,
18
    Measurement, PlotQueryRectangle, RasterQueryRectangle,
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)]
4✔
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(
49
        self: Box<Self>,
50
        path: WorkflowOperatorPath,
51
        context: &dyn ExecutionContext,
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) => {
9✔
57
                ensure!(
4✔
58
                    self.params.column_name.is_none(),
4✔
59
                    error::InvalidOperatorSpec {
9✔
60
                        reason: "Histogram on raster input must not have `columnName` field set"
1✔
61
                            .to_string(),
1✔
62
                    }
1✔
63
                );
9✔
64

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

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

3✔
71
                ensure!(
3✔
72
                    in_desc.bands.len() == 1,
3✔
73
                    crate::error::OperatorDoesNotSupportMultiBandsSourcesYet {
9✔
74
                        operator: ClassHistogram::TYPE_NAME
×
75
                    }
×
76
                );
9✔
77

9✔
78
                let source_measurement = match &in_desc.bands[0].measurement {
9✔
79
                    Measurement::Classification(measurement) => measurement.clone(),
9✔
80
                    _ => {
9✔
81
                        return Err(Error::InvalidOperatorSpec {
9✔
82
                            reason: "Source measurement mut be classification".to_string(),
×
83
                        })
×
84
                    }
9✔
85
                };
9✔
86

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

9✔
113
                let vector_source = vector_source
9✔
114
                    .initialize(path.clone_and_append(0), context)
5✔
115
                    .await?;
9✔
116

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

4✔
141
                let in_desc = vector_source.result_descriptor().clone();
4✔
142

9✔
143
                let source_measurement = match in_desc.column_measurement(column_name) {
9✔
144
                    Some(Measurement::Classification(measurement)) => measurement.clone(),
9✔
145
                    _ => {
9✔
146
                        return Err(Error::InvalidOperatorSpec {
9✔
147
                            reason: "Source measurement mut be classification".to_string(),
×
148
                        })
×
149
                    }
9✔
150
                };
9✔
151

9✔
152
                InitializedClassHistogram::new(
9✔
153
                    name,
4✔
154
                    in_desc.into(),
4✔
155
                    self.params.column_name,
4✔
156
                    source_measurement,
4✔
157
                    vector_source,
4✔
158
                )
4✔
159
                .boxed()
4✔
160
            }
9✔
161
        })
9✔
162
    }
9✔
163

164
    span_fn!(ClassHistogram);
165
}
166

167
/// The initialization of `Histogram`
168
pub struct InitializedClassHistogram<Op> {
169
    name: CanonicOperatorName,
170
    result_descriptor: PlotResultDescriptor,
171
    source_measurement: ClassificationMeasurement,
172
    source: Op,
173
    column_name: Option<String>,
174
}
175

176
impl<Op> InitializedClassHistogram<Op> {
177
    pub fn new(
7✔
178
        name: CanonicOperatorName,
7✔
179
        result_descriptor: PlotResultDescriptor,
7✔
180
        column_name: Option<String>,
7✔
181
        source_measurement: ClassificationMeasurement,
7✔
182
        source: Op,
7✔
183
    ) -> Self {
7✔
184
        Self {
7✔
185
            name,
7✔
186
            result_descriptor,
7✔
187
            source_measurement,
7✔
188
            source,
7✔
189
            column_name,
7✔
190
        }
7✔
191
    }
7✔
192
}
193

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

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

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

208
    fn canonic_name(&self) -> CanonicOperatorName {
×
209
        self.name.clone()
×
210
    }
×
211
}
212

213
impl InitializedPlotOperator for InitializedClassHistogram<Box<dyn InitializedVectorOperator>> {
214
    fn query_processor(&self) -> Result<TypedPlotQueryProcessor> {
4✔
215
        let processor = ClassHistogramVectorQueryProcessor {
4✔
216
            input: self.source.query_processor()?,
4✔
217
            column_name: self.column_name.clone().unwrap_or_default(),
4✔
218
            measurement: self.source_measurement.clone(),
4✔
219
        };
4✔
220

4✔
221
        Ok(TypedPlotQueryProcessor::JsonVega(processor.boxed()))
4✔
222
    }
4✔
223

224
    fn result_descriptor(&self) -> &PlotResultDescriptor {
×
225
        &self.result_descriptor
×
226
    }
×
227

228
    fn canonic_name(&self) -> CanonicOperatorName {
×
229
        self.name.clone()
×
230
    }
×
231
}
232

233
/// A query processor that calculates the Histogram about its raster inputs.
234
pub struct ClassHistogramRasterQueryProcessor {
235
    input: TypedRasterQueryProcessor,
236
    measurement: ClassificationMeasurement,
237
}
238

239
/// A query processor that calculates the Histogram about its vector inputs.
240
pub struct ClassHistogramVectorQueryProcessor {
241
    input: TypedVectorQueryProcessor,
242
    column_name: String,
243
    measurement: ClassificationMeasurement,
244
}
245

246
#[async_trait]
247
impl PlotQueryProcessor for ClassHistogramRasterQueryProcessor {
248
    type OutputFormat = PlotData;
249

250
    fn plot_type(&self) -> &'static str {
×
251
        CLASS_HISTOGRAM_OPERATOR_NAME
×
252
    }
×
253

254
    async fn plot_query<'p>(
255
        &'p self,
256
        query: PlotQueryRectangle,
257
        ctx: &'p dyn QueryContext,
258
    ) -> Result<Self::OutputFormat> {
3✔
259
        self.process(query, ctx).await
3✔
260
    }
3✔
261
}
262

263
#[async_trait]
264
impl PlotQueryProcessor for ClassHistogramVectorQueryProcessor {
265
    type OutputFormat = PlotData;
266

267
    fn plot_type(&self) -> &'static str {
×
268
        CLASS_HISTOGRAM_OPERATOR_NAME
×
269
    }
×
270

271
    async fn plot_query<'p>(
272
        &'p self,
273
        query: PlotQueryRectangle,
274
        ctx: &'p dyn QueryContext,
275
    ) -> Result<Self::OutputFormat> {
4✔
276
        self.process(query, ctx).await
4✔
277
    }
4✔
278
}
279

280
impl ClassHistogramRasterQueryProcessor {
281
    async fn process<'p>(
3✔
282
        &'p self,
3✔
283
        query: PlotQueryRectangle,
3✔
284
        ctx: &'p dyn QueryContext,
3✔
285
    ) -> Result<<ClassHistogramRasterQueryProcessor as PlotQueryProcessor>::OutputFormat> {
3✔
286
        let mut class_counts: HashMap<u8, u64> = self
3✔
287
            .measurement
3✔
288
            .classes
3✔
289
            .keys()
3✔
290
            .map(|key| (*key, 0))
8✔
291
            .collect();
3✔
292

293
        call_on_generic_raster_processor!(&self.input, processor => {
3✔
UNCOV
294
            let mut query = processor.query(RasterQueryRectangle::from_qrect_and_bands(&query, BandSelection::first()), ctx).await?;
×
295

UNCOV
296
            while let Some(tile) = query.next().await {
×
UNCOV
297
                match tile?.grid_array {
×
UNCOV
298
                    geoengine_datatypes::raster::GridOrEmpty::Grid(g) => {
×
299
                        g.masked_element_deref_iterator().for_each(|value_option| {
18✔
300
                            if let Some(v) = value_option {
18✔
301
                                if let Some(count) = class_counts.get_mut(&v.as_()) {
18✔
302
                                    *count += 1;
12✔
303
                                }
12✔
304
                            }
×
305
                        });
18✔
UNCOV
306
                    },
×
UNCOV
307
                    geoengine_datatypes::raster::GridOrEmpty::Empty(_) => (), // ignore no data,
×
308
                }
309
            }
310
        });
311

312
        // TODO: display NO-DATA count?
313

314
        let bar_chart = BarChart::new(
3✔
315
            class_counts
3✔
316
                .into_iter()
3✔
317
                .map(|(class, count)| {
8✔
318
                    (
8✔
319
                        self.measurement
8✔
320
                            .classes
8✔
321
                            .get(&class)
8✔
322
                            .cloned()
8✔
323
                            .unwrap_or_default(),
8✔
324
                        count,
8✔
325
                    )
8✔
326
                })
8✔
327
                .collect(),
3✔
328
            Measurement::Classification(self.measurement.clone()).to_string(),
3✔
329
            "Frequency".to_string(),
3✔
330
        );
3✔
331
        let chart = bar_chart.to_vega_embeddable(false)?;
3✔
332

333
        Ok(chart)
3✔
334
    }
3✔
335
}
336

337
impl ClassHistogramVectorQueryProcessor {
338
    async fn process<'p>(
4✔
339
        &'p self,
4✔
340
        query: PlotQueryRectangle,
4✔
341
        ctx: &'p dyn QueryContext,
4✔
342
    ) -> Result<<ClassHistogramRasterQueryProcessor as PlotQueryProcessor>::OutputFormat> {
4✔
343
        let mut class_counts: HashMap<u8, u64> = self
4✔
344
            .measurement
4✔
345
            .classes
4✔
346
            .keys()
4✔
347
            .map(|key| (*key, 0))
8✔
348
            .collect();
4✔
349

350
        call_on_generic_vector_processor!(&self.input, processor => {
4✔
351
            let mut query = processor.query(query.into(), ctx).await?;
4✔
352

353
            while let Some(collection) = query.next().await {
9✔
354
                let collection = collection?;
5✔
355

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

358
                for v in feature_data.float_options_iter() {
19✔
359
                    match v {
19✔
360
                        None => (), // ignore no data
2✔
361
                        Some(index) => if let Some(count) = class_counts.get_mut(&(index as u8)) {
17✔
362
                            *count += 1;
16✔
363
                        },
16✔
364
                        // else… ignore values that are not in the class list
365
                    }
366

367
                }
368

369
            }
370
        });
371

372
        // TODO: display NO-DATA count?
373

374
        let bar_chart = BarChart::new(
4✔
375
            class_counts
4✔
376
                .into_iter()
4✔
377
                .map(|(class, count)| {
8✔
378
                    (
8✔
379
                        self.measurement
8✔
380
                            .classes
8✔
381
                            .get(&class)
8✔
382
                            .cloned()
8✔
383
                            .unwrap_or_default(),
8✔
384
                        count,
8✔
385
                    )
8✔
386
                })
8✔
387
                .collect(),
4✔
388
            Measurement::Classification(self.measurement.clone()).to_string(),
4✔
389
            "Frequency".to_string(),
4✔
390
        );
4✔
391
        let chart = bar_chart.to_vega_embeddable(false)?;
4✔
392

393
        Ok(chart)
4✔
394
    }
4✔
395
}
396

397
#[cfg(test)]
398
mod tests {
399
    use super::*;
400

401
    use crate::engine::{
402
        ChunkByteSize, MockExecutionContext, MockQueryContext, RasterBandDescriptor,
403
        RasterBandDescriptors, RasterOperator, RasterResultDescriptor, StaticMetaData,
404
        VectorColumnInfo, VectorOperator, VectorResultDescriptor,
405
    };
406
    use crate::mock::{MockFeatureCollectionSource, MockRasterSource, MockRasterSourceParams};
407
    use crate::source::{
408
        OgrSourceColumnSpec, OgrSourceDataset, OgrSourceDatasetTimeType, OgrSourceErrorSpec,
409
    };
410
    use crate::test_data;
411
    use geoengine_datatypes::dataset::{DataId, DatasetId, NamedData};
412
    use geoengine_datatypes::primitives::{
413
        BoundingBox2D, DateTime, FeatureData, NoGeometry, PlotSeriesSelection, SpatialResolution,
414
        TimeInterval, VectorQueryRectangle,
415
    };
416
    use geoengine_datatypes::primitives::{CacheHint, CacheTtlSeconds};
417
    use geoengine_datatypes::raster::{
418
        Grid2D, RasterDataType, RasterTile2D, TileInformation, TilingSpecification,
419
    };
420
    use geoengine_datatypes::spatial_reference::SpatialReference;
421
    use geoengine_datatypes::util::test::TestDefault;
422
    use geoengine_datatypes::util::Identifier;
423
    use geoengine_datatypes::{
424
        collections::{DataCollection, VectorDataType},
425
        primitives::MultiPoint,
426
    };
427
    use serde_json::json;
428

429
    #[test]
430
    fn serialization() {
1✔
431
        let histogram = ClassHistogram {
1✔
432
            params: ClassHistogramParams {
1✔
433
                column_name: Some("foobar".to_string()),
1✔
434
            },
1✔
435
            sources: MockFeatureCollectionSource::<MultiPoint>::multiple(vec![])
1✔
436
                .boxed()
1✔
437
                .into(),
1✔
438
        };
1✔
439

1✔
440
        let serialized = json!({
1✔
441
            "type": "ClassHistogram",
1✔
442
            "params": {
1✔
443
                "columnName": "foobar",
1✔
444
            },
1✔
445
            "sources": {
1✔
446
                "source": {
1✔
447
                    "type": "MockFeatureCollectionSourceMultiPoint",
1✔
448
                    "params": {
1✔
449
                        "collections": [],
1✔
450
                        "spatialReference": "EPSG:4326",
1✔
451
                        "measurements": {},
1✔
452
                    }
1✔
453
                }
1✔
454
            }
1✔
455
        })
1✔
456
        .to_string();
1✔
457

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

1✔
460
        assert_eq!(deserialized.params, histogram.params);
1✔
461
    }
1✔
462

463
    #[tokio::test]
464
    async fn column_name_for_raster_source() {
1✔
465
        let histogram = ClassHistogram {
1✔
466
            params: ClassHistogramParams {
1✔
467
                column_name: Some("foo".to_string()),
1✔
468
            },
1✔
469
            sources: mock_raster_source().into(),
1✔
470
        };
1✔
471

1✔
472
        let execution_context = MockExecutionContext::test_default();
1✔
473

1✔
474
        assert!(histogram
1✔
475
            .boxed()
1✔
476
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
477
            .await
1✔
478
            .is_err());
1✔
479
    }
1✔
480

481
    fn mock_raster_source() -> Box<dyn RasterOperator> {
2✔
482
        MockRasterSource {
2✔
483
            params: MockRasterSourceParams {
2✔
484
                data: vec![RasterTile2D::new_with_tile_info(
2✔
485
                    TimeInterval::default(),
2✔
486
                    TileInformation {
2✔
487
                        global_geo_transform: TestDefault::test_default(),
2✔
488
                        global_tile_position: [0, 0].into(),
2✔
489
                        tile_size_in_pixels: [3, 2].into(),
2✔
490
                    },
2✔
491
                    0,
2✔
492
                    Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
2✔
493
                        .unwrap()
2✔
494
                        .into(),
2✔
495
                    CacheHint::default(),
2✔
496
                )],
2✔
497
                result_descriptor: RasterResultDescriptor {
2✔
498
                    data_type: RasterDataType::U8,
2✔
499
                    spatial_reference: SpatialReference::epsg_4326().into(),
2✔
500
                    time: None,
2✔
501
                    bbox: None,
2✔
502
                    resolution: None,
2✔
503
                    bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
2✔
504
                        "bands".into(),
2✔
505
                        Measurement::classification(
2✔
506
                            "test-class".to_string(),
2✔
507
                            [
2✔
508
                                (1, "A".to_string()),
2✔
509
                                (2, "B".to_string()),
2✔
510
                                (3, "C".to_string()),
2✔
511
                                (4, "D".to_string()),
2✔
512
                                (5, "E".to_string()),
2✔
513
                                (6, "F".to_string()),
2✔
514
                            ]
2✔
515
                            .into_iter()
2✔
516
                            .collect(),
2✔
517
                        ),
2✔
518
                    )])
2✔
519
                    .unwrap(),
2✔
520
                },
2✔
521
            },
2✔
522
        }
2✔
523
        .boxed()
2✔
524
    }
2✔
525

526
    #[tokio::test]
527
    async fn simple_raster() {
1✔
528
        let tile_size_in_pixels = [3, 2].into();
1✔
529
        let tiling_specification = TilingSpecification {
1✔
530
            origin_coordinate: [0.0, 0.0].into(),
1✔
531
            tile_size_in_pixels,
1✔
532
        };
1✔
533
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
534

1✔
535
        let histogram = ClassHistogram {
1✔
536
            params: ClassHistogramParams { column_name: None },
1✔
537
            sources: mock_raster_source().into(),
1✔
538
        };
1✔
539

1✔
540
        let query_processor = histogram
1✔
541
            .boxed()
1✔
542
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
543
            .await
1✔
544
            .unwrap()
1✔
545
            .query_processor()
1✔
546
            .unwrap()
1✔
547
            .json_vega()
1✔
548
            .unwrap();
1✔
549

1✔
550
        let result = query_processor
1✔
551
            .plot_query(
1✔
552
                PlotQueryRectangle {
1✔
553
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
554
                    time_interval: TimeInterval::default(),
1✔
555
                    spatial_resolution: SpatialResolution::one(),
1✔
556
                    attributes: PlotSeriesSelection::all(),
1✔
557
                },
1✔
558
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
559
            )
1✔
560
            .await
1✔
561
            .unwrap();
1✔
562

1✔
563
        assert_eq!(
1✔
564
            result,
1✔
565
            BarChart::new(
1✔
566
                [
1✔
567
                    ("A".to_string(), 1),
1✔
568
                    ("B".to_string(), 1),
1✔
569
                    ("C".to_string(), 1),
1✔
570
                    ("D".to_string(), 1),
1✔
571
                    ("E".to_string(), 1),
1✔
572
                    ("F".to_string(), 1),
1✔
573
                ]
1✔
574
                .into_iter()
1✔
575
                .collect(),
1✔
576
                "test-class".to_string(),
1✔
577
                "Frequency".to_string()
1✔
578
            )
1✔
579
            .to_vega_embeddable(true)
1✔
580
            .unwrap()
1✔
581
        );
1✔
582
    }
1✔
583

584
    #[tokio::test]
585
    async fn vector_data() {
1✔
586
        let measurement = Measurement::classification(
1✔
587
            "foo".to_string(),
1✔
588
            [
1✔
589
                (1, "A".to_string()),
1✔
590
                (2, "B".to_string()),
1✔
591
                (3, "C".to_string()),
1✔
592
            ]
1✔
593
            .into_iter()
1✔
594
            .collect(),
1✔
595
        );
1✔
596

1✔
597
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
598
            vec![
1✔
599
                DataCollection::from_slices(
1✔
600
                    &[] as &[NoGeometry],
1✔
601
                    &[TimeInterval::default(); 8],
1✔
602
                    &[("foo", FeatureData::Int(vec![1, 1, 2, 2, 3, 3, 1, 2]))],
1✔
603
                )
1✔
604
                .unwrap(),
1✔
605
                DataCollection::from_slices(
1✔
606
                    &[] as &[NoGeometry],
1✔
607
                    &[TimeInterval::default(); 4],
1✔
608
                    &[("foo", FeatureData::Int(vec![1, 1, 2, 3]))],
1✔
609
                )
1✔
610
                .unwrap(),
1✔
611
            ],
1✔
612
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
613
        )
1✔
614
        .boxed();
1✔
615

1✔
616
        let histogram = ClassHistogram {
1✔
617
            params: ClassHistogramParams {
1✔
618
                column_name: Some("foo".to_string()),
1✔
619
            },
1✔
620
            sources: vector_source.into(),
1✔
621
        };
1✔
622

1✔
623
        let execution_context = MockExecutionContext::test_default();
1✔
624

1✔
625
        let query_processor = histogram
1✔
626
            .boxed()
1✔
627
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
628
            .await
1✔
629
            .unwrap()
1✔
630
            .query_processor()
1✔
631
            .unwrap()
1✔
632
            .json_vega()
1✔
633
            .unwrap();
1✔
634

1✔
635
        let result = query_processor
1✔
636
            .plot_query(
1✔
637
                PlotQueryRectangle {
1✔
638
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
639
                        .unwrap(),
1✔
640
                    time_interval: TimeInterval::default(),
1✔
641
                    spatial_resolution: SpatialResolution::one(),
1✔
642
                    attributes: PlotSeriesSelection::all(),
1✔
643
                },
1✔
644
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
645
            )
1✔
646
            .await
1✔
647
            .unwrap();
1✔
648

1✔
649
        assert_eq!(
1✔
650
            result,
1✔
651
            BarChart::new(
1✔
652
                [
1✔
653
                    ("A".to_string(), 5),
1✔
654
                    ("B".to_string(), 4),
1✔
655
                    ("C".to_string(), 3),
1✔
656
                ]
1✔
657
                .into_iter()
1✔
658
                .collect(),
1✔
659
                "foo".to_string(),
1✔
660
                "Frequency".to_string()
1✔
661
            )
1✔
662
            .to_vega_embeddable(true)
1✔
663
            .unwrap()
1✔
664
        );
1✔
665
    }
1✔
666

667
    #[tokio::test]
668
    async fn vector_data_with_nulls() {
1✔
669
        let measurement = Measurement::classification(
1✔
670
            "foo".to_string(),
1✔
671
            [
1✔
672
                (1, "A".to_string()),
1✔
673
                (2, "B".to_string()),
1✔
674
                (4, "C".to_string()),
1✔
675
            ]
1✔
676
            .into_iter()
1✔
677
            .collect(),
1✔
678
        );
1✔
679

1✔
680
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
681
            vec![DataCollection::from_slices(
1✔
682
                &[] as &[NoGeometry],
1✔
683
                &[TimeInterval::default(); 6],
1✔
684
                &[(
1✔
685
                    "foo",
1✔
686
                    FeatureData::NullableFloat(vec![
1✔
687
                        Some(1.),
1✔
688
                        Some(2.),
1✔
689
                        None,
1✔
690
                        Some(4.),
1✔
691
                        None,
1✔
692
                        Some(5.),
1✔
693
                    ]),
1✔
694
                )],
1✔
695
            )
1✔
696
            .unwrap()],
1✔
697
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
698
        )
1✔
699
        .boxed();
1✔
700

1✔
701
        let histogram = ClassHistogram {
1✔
702
            params: ClassHistogramParams {
1✔
703
                column_name: Some("foo".to_string()),
1✔
704
            },
1✔
705
            sources: vector_source.into(),
1✔
706
        };
1✔
707

1✔
708
        let execution_context = MockExecutionContext::test_default();
1✔
709

1✔
710
        let query_processor = histogram
1✔
711
            .boxed()
1✔
712
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
713
            .await
1✔
714
            .unwrap()
1✔
715
            .query_processor()
1✔
716
            .unwrap()
1✔
717
            .json_vega()
1✔
718
            .unwrap();
1✔
719

1✔
720
        let result = query_processor
1✔
721
            .plot_query(
1✔
722
                PlotQueryRectangle {
1✔
723
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
724
                        .unwrap(),
1✔
725
                    time_interval: TimeInterval::default(),
1✔
726
                    spatial_resolution: SpatialResolution::one(),
1✔
727
                    attributes: PlotSeriesSelection::all(),
1✔
728
                },
1✔
729
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
730
            )
1✔
731
            .await
1✔
732
            .unwrap();
1✔
733

1✔
734
        assert_eq!(
1✔
735
            result,
1✔
736
            BarChart::new(
1✔
737
                [
1✔
738
                    ("A".to_string(), 1),
1✔
739
                    ("B".to_string(), 1),
1✔
740
                    ("C".to_string(), 1),
1✔
741
                ]
1✔
742
                .into_iter()
1✔
743
                .collect(),
1✔
744
                "foo".to_string(),
1✔
745
                "Frequency".to_string()
1✔
746
            )
1✔
747
            .to_vega_embeddable(true)
1✔
748
            .unwrap()
1✔
749
        );
1✔
750
    }
1✔
751

752
    #[tokio::test]
753
    #[allow(clippy::too_many_lines)]
754
    async fn text_attribute() {
1✔
755
        let dataset_id = DatasetId::new();
1✔
756
        let dataset_name = NamedData::with_system_name("ne_10m_ports");
1✔
757

1✔
758
        let workflow = serde_json::json!({
1✔
759
            "type": "Histogram",
1✔
760
            "params": {
1✔
761
                "columnName": "featurecla",
1✔
762
            },
1✔
763
            "sources": {
1✔
764
                "source": {
1✔
765
                    "type": "OgrSource",
1✔
766
                    "params": {
1✔
767
                        "data": dataset_name.clone(),
1✔
768
                        "attributeProjection": null
1✔
769
                    },
1✔
770
                }
1✔
771
            }
1✔
772
        });
1✔
773
        let histogram: ClassHistogram = serde_json::from_value(workflow).unwrap();
1✔
774

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

1✔
858
        if let Err(Error::InvalidOperatorSpec { reason }) = histogram
1✔
859
            .boxed()
1✔
860
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
861
            .await
1✔
862
        {
1✔
863
            assert_eq!(reason, "column `featurecla` must be numerical");
1✔
864
        } else {
1✔
865
            panic!("we currently don't support text features, but this went through");
1✔
866
        }
1✔
867
    }
1✔
868

869
    #[tokio::test]
870
    async fn no_data_raster() {
1✔
871
        let tile_size_in_pixels = [3, 2].into();
1✔
872
        let tiling_specification = TilingSpecification {
1✔
873
            origin_coordinate: [0.0, 0.0].into(),
1✔
874
            tile_size_in_pixels,
1✔
875
        };
1✔
876
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
877

1✔
878
        let measurement = Measurement::classification(
1✔
879
            "foo".to_string(),
1✔
880
            [(1, "A".to_string())].into_iter().collect(),
1✔
881
        );
1✔
882

1✔
883
        let histogram = ClassHistogram {
1✔
884
            params: ClassHistogramParams { column_name: None },
1✔
885
            sources: MockRasterSource {
1✔
886
                params: MockRasterSourceParams {
1✔
887
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
888
                        TimeInterval::default(),
1✔
889
                        TileInformation {
1✔
890
                            global_geo_transform: TestDefault::test_default(),
1✔
891
                            global_tile_position: [0, 0].into(),
1✔
892
                            tile_size_in_pixels,
1✔
893
                        },
1✔
894
                        0,
1✔
895
                        Grid2D::new(tile_size_in_pixels, vec![0, 0, 0, 0, 0, 0])
1✔
896
                            .unwrap()
1✔
897
                            .into(),
1✔
898
                        CacheHint::default(),
1✔
899
                    )],
1✔
900
                    result_descriptor: RasterResultDescriptor {
1✔
901
                        data_type: RasterDataType::U8,
1✔
902
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
903
                        time: None,
1✔
904
                        bbox: None,
1✔
905
                        resolution: None,
1✔
906
                        bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
907
                            "band".into(),
1✔
908
                            measurement,
1✔
909
                        )])
1✔
910
                        .unwrap(),
1✔
911
                    },
1✔
912
                },
1✔
913
            }
1✔
914
            .boxed()
1✔
915
            .into(),
1✔
916
        };
1✔
917

1✔
918
        let query_processor = histogram
1✔
919
            .boxed()
1✔
920
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
921
            .await
1✔
922
            .unwrap()
1✔
923
            .query_processor()
1✔
924
            .unwrap()
1✔
925
            .json_vega()
1✔
926
            .unwrap();
1✔
927

1✔
928
        let result = query_processor
1✔
929
            .plot_query(
1✔
930
                PlotQueryRectangle {
1✔
931
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
932
                    time_interval: TimeInterval::default(),
1✔
933
                    spatial_resolution: SpatialResolution::one(),
1✔
934
                    attributes: PlotSeriesSelection::all(),
1✔
935
                },
1✔
936
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
937
            )
1✔
938
            .await
1✔
939
            .unwrap();
1✔
940

1✔
941
        assert_eq!(
1✔
942
            result,
1✔
943
            BarChart::new(
1✔
944
                [("A".to_string(), 0)].into_iter().collect(),
1✔
945
                "foo".to_string(),
1✔
946
                "Frequency".to_string()
1✔
947
            )
1✔
948
            .to_vega_embeddable(true)
1✔
949
            .unwrap()
1✔
950
        );
1✔
951
    }
1✔
952

953
    #[tokio::test]
954
    async fn empty_feature_collection() {
1✔
955
        let measurement = Measurement::classification(
1✔
956
            "foo".to_string(),
1✔
957
            [(1, "A".to_string())].into_iter().collect(),
1✔
958
        );
1✔
959

1✔
960
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
961
            vec![DataCollection::from_slices(
1✔
962
                &[] as &[NoGeometry],
1✔
963
                &[] as &[TimeInterval],
1✔
964
                &[("foo", FeatureData::Float(vec![]))],
1✔
965
            )
1✔
966
            .unwrap()],
1✔
967
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
968
        )
1✔
969
        .boxed();
1✔
970

1✔
971
        let histogram = ClassHistogram {
1✔
972
            params: ClassHistogramParams {
1✔
973
                column_name: Some("foo".to_string()),
1✔
974
            },
1✔
975
            sources: vector_source.into(),
1✔
976
        };
1✔
977

1✔
978
        let execution_context = MockExecutionContext::test_default();
1✔
979

1✔
980
        let query_processor = histogram
1✔
981
            .boxed()
1✔
982
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
983
            .await
1✔
984
            .unwrap()
1✔
985
            .query_processor()
1✔
986
            .unwrap()
1✔
987
            .json_vega()
1✔
988
            .unwrap();
1✔
989

1✔
990
        let result = query_processor
1✔
991
            .plot_query(
1✔
992
                PlotQueryRectangle {
1✔
993
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
994
                        .unwrap(),
1✔
995
                    time_interval: TimeInterval::default(),
1✔
996
                    spatial_resolution: SpatialResolution::one(),
1✔
997
                    attributes: PlotSeriesSelection::all(),
1✔
998
                },
1✔
999
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1000
            )
1✔
1001
            .await
1✔
1002
            .unwrap();
1✔
1003

1✔
1004
        assert_eq!(
1✔
1005
            result,
1✔
1006
            BarChart::new(
1✔
1007
                [("A".to_string(), 0)].into_iter().collect(),
1✔
1008
                "foo".to_string(),
1✔
1009
                "Frequency".to_string()
1✔
1010
            )
1✔
1011
            .to_vega_embeddable(true)
1✔
1012
            .unwrap()
1✔
1013
        );
1✔
1014
    }
1✔
1015

1016
    #[tokio::test]
1017
    async fn feature_collection_with_one_feature() {
1✔
1018
        let measurement = Measurement::classification(
1✔
1019
            "foo".to_string(),
1✔
1020
            [(5, "A".to_string())].into_iter().collect(),
1✔
1021
        );
1✔
1022

1✔
1023
        let vector_source = MockFeatureCollectionSource::with_collections_and_measurements(
1✔
1024
            vec![DataCollection::from_slices(
1✔
1025
                &[] as &[NoGeometry],
1✔
1026
                &[TimeInterval::default()],
1✔
1027
                &[("foo", FeatureData::Float(vec![5.0]))],
1✔
1028
            )
1✔
1029
            .unwrap()],
1✔
1030
            [("foo".to_string(), measurement)].into_iter().collect(),
1✔
1031
        )
1✔
1032
        .boxed();
1✔
1033

1✔
1034
        let histogram = ClassHistogram {
1✔
1035
            params: ClassHistogramParams {
1✔
1036
                column_name: Some("foo".to_string()),
1✔
1037
            },
1✔
1038
            sources: vector_source.into(),
1✔
1039
        };
1✔
1040

1✔
1041
        let execution_context = MockExecutionContext::test_default();
1✔
1042

1✔
1043
        let query_processor = histogram
1✔
1044
            .boxed()
1✔
1045
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1046
            .await
1✔
1047
            .unwrap()
1✔
1048
            .query_processor()
1✔
1049
            .unwrap()
1✔
1050
            .json_vega()
1✔
1051
            .unwrap();
1✔
1052

1✔
1053
        let result = query_processor
1✔
1054
            .plot_query(
1✔
1055
                PlotQueryRectangle {
1✔
1056
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
1057
                        .unwrap(),
1✔
1058
                    time_interval: TimeInterval::default(),
1✔
1059
                    spatial_resolution: SpatialResolution::one(),
1✔
1060
                    attributes: PlotSeriesSelection::all(),
1✔
1061
                },
1✔
1062
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1063
            )
1✔
1064
            .await
1✔
1065
            .unwrap();
1✔
1066

1✔
1067
        assert_eq!(
1✔
1068
            result,
1✔
1069
            BarChart::new(
1✔
1070
                [("A".to_string(), 1)].into_iter().collect(),
1✔
1071
                "foo".to_string(),
1✔
1072
                "Frequency".to_string()
1✔
1073
            )
1✔
1074
            .to_vega_embeddable(true)
1✔
1075
            .unwrap()
1✔
1076
        );
1✔
1077
    }
1✔
1078

1079
    #[tokio::test]
1080
    async fn single_value_raster_stream() {
1✔
1081
        let tile_size_in_pixels = [3, 2].into();
1✔
1082
        let tiling_specification = TilingSpecification {
1✔
1083
            origin_coordinate: [0.0, 0.0].into(),
1✔
1084
            tile_size_in_pixels,
1✔
1085
        };
1✔
1086
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1087

1✔
1088
        let measurement = Measurement::classification(
1✔
1089
            "foo".to_string(),
1✔
1090
            [(4, "D".to_string())].into_iter().collect(),
1✔
1091
        );
1✔
1092

1✔
1093
        let histogram = ClassHistogram {
1✔
1094
            params: ClassHistogramParams { column_name: None },
1✔
1095
            sources: MockRasterSource {
1✔
1096
                params: MockRasterSourceParams {
1✔
1097
                    data: vec![RasterTile2D::new_with_tile_info(
1✔
1098
                        TimeInterval::default(),
1✔
1099
                        TileInformation {
1✔
1100
                            global_geo_transform: TestDefault::test_default(),
1✔
1101
                            global_tile_position: [0, 0].into(),
1✔
1102
                            tile_size_in_pixels,
1✔
1103
                        },
1✔
1104
                        0,
1✔
1105
                        Grid2D::new(tile_size_in_pixels, vec![4; 6]).unwrap().into(),
1✔
1106
                        CacheHint::default(),
1✔
1107
                    )],
1✔
1108
                    result_descriptor: RasterResultDescriptor {
1✔
1109
                        data_type: RasterDataType::U8,
1✔
1110
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1111
                        time: None,
1✔
1112
                        bbox: None,
1✔
1113
                        resolution: None,
1✔
1114
                        bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
1115
                            "band".into(),
1✔
1116
                            measurement,
1✔
1117
                        )])
1✔
1118
                        .unwrap(),
1✔
1119
                    },
1✔
1120
                },
1✔
1121
            }
1✔
1122
            .boxed()
1✔
1123
            .into(),
1✔
1124
        };
1✔
1125

1✔
1126
        let query_processor = histogram
1✔
1127
            .boxed()
1✔
1128
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1129
            .await
1✔
1130
            .unwrap()
1✔
1131
            .query_processor()
1✔
1132
            .unwrap()
1✔
1133
            .json_vega()
1✔
1134
            .unwrap();
1✔
1135

1✔
1136
        let result = query_processor
1✔
1137
            .plot_query(
1✔
1138
                PlotQueryRectangle {
1✔
1139
                    spatial_bounds: BoundingBox2D::new((0., -3.).into(), (2., 0.).into()).unwrap(),
1✔
1140
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1141
                        2013, 12, 1, 12, 0, 0,
1✔
1142
                    ))
1✔
1143
                    .unwrap(),
1✔
1144
                    spatial_resolution: SpatialResolution::one(),
1✔
1145
                    attributes: PlotSeriesSelection::all(),
1✔
1146
                },
1✔
1147
                &MockQueryContext::new(ChunkByteSize::MIN),
1✔
1148
            )
1✔
1149
            .await
1✔
1150
            .unwrap();
1✔
1151

1✔
1152
        assert_eq!(
1✔
1153
            result,
1✔
1154
            BarChart::new(
1✔
1155
                [("D".to_string(), 6)].into_iter().collect(),
1✔
1156
                "foo".to_string(),
1✔
1157
                "Frequency".to_string()
1✔
1158
            )
1✔
1159
            .to_vega_embeddable(true)
1✔
1160
            .unwrap()
1✔
1161
        );
1✔
1162
    }
1✔
1163
}
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