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

geo-engine / geoengine / 18554766227

16 Oct 2025 08:12AM UTC coverage: 88.843% (+0.3%) from 88.543%
18554766227

push

github

web-flow
build: update dependencies (#1081)

* update sqlfluff

* clippy autofix

* manual clippy fixes

* removal of unused code

* update deps

* upgrade packages

* enable cargo lints

* make sqlfluff happy

* fix chrono parsin error

* clippy

* byte_size

* fix image cmp with tiffs

* remove debug

177 of 205 new or added lines in 38 files covered. (86.34%)

41 existing lines in 20 files now uncovered.

106415 of 119779 relevant lines covered (88.84%)

84190.21 hits per line

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

99.26
/operators/src/processing/raster_vector_join/aggregated.rs
1
use futures::stream::BoxStream;
2
use futures::{StreamExt, TryStreamExt};
3

4
use geoengine_datatypes::collections::{
5
    FeatureCollection, FeatureCollectionInfos, FeatureCollectionModifications,
6
};
7
use geoengine_datatypes::primitives::{
8
    BandSelection, CacheHint, ColumnSelection, RasterQueryRectangle,
9
};
10
use geoengine_datatypes::raster::{GridIndexAccess, Pixel, RasterDataType};
11
use geoengine_datatypes::util::arrow::ArrowTyped;
12

13
use crate::engine::{
14
    QueryContext, QueryProcessor, RasterQueryProcessor, VectorQueryProcessor,
15
    VectorResultDescriptor,
16
};
17
use crate::processing::raster_vector_join::TemporalAggregationMethod;
18
use crate::processing::raster_vector_join::aggregator::{
19
    Aggregator, FirstValueFloatAggregator, FirstValueIntAggregator, MeanValueAggregator,
20
    TypedAggregator,
21
};
22
use crate::util::Result;
23
use async_trait::async_trait;
24
use geoengine_datatypes::primitives::{BoundingBox2D, Geometry, VectorQueryRectangle};
25

26
use super::util::{CoveredPixels, FeatureTimeSpanIter, PixelCoverCreator};
27
use super::{FeatureAggregationMethod, RasterInput, create_feature_aggregator};
28

29
pub struct RasterVectorAggregateJoinProcessor<G> {
30
    collection: Box<dyn VectorQueryProcessor<VectorType = FeatureCollection<G>>>,
31
    result_descriptor: VectorResultDescriptor,
32
    raster_inputs: Vec<RasterInput>,
33
    feature_aggregation: FeatureAggregationMethod,
34
    feature_aggregation_ignore_no_data: bool,
35
    temporal_aggregation: TemporalAggregationMethod,
36
    temporal_aggregation_ignore_no_data: bool,
37
}
38

39
impl<G> RasterVectorAggregateJoinProcessor<G>
40
where
41
    G: Geometry + ArrowTyped,
42
    FeatureCollection<G>: PixelCoverCreator<G>,
43
{
44
    #[allow(clippy::too_many_arguments)]
45
    pub fn new(
5✔
46
        collection: Box<dyn VectorQueryProcessor<VectorType = FeatureCollection<G>>>,
5✔
47
        result_descriptor: VectorResultDescriptor,
5✔
48
        raster_inputs: Vec<RasterInput>,
5✔
49
        feature_aggregation: FeatureAggregationMethod,
5✔
50
        feature_aggregation_ignore_no_data: bool,
5✔
51
        temporal_aggregation: TemporalAggregationMethod,
5✔
52
        temporal_aggregation_ignore_no_data: bool,
5✔
53
    ) -> Self {
5✔
54
        Self {
5✔
55
            collection,
5✔
56
            result_descriptor,
5✔
57
            raster_inputs,
5✔
58
            feature_aggregation,
5✔
59
            feature_aggregation_ignore_no_data,
5✔
60
            temporal_aggregation,
5✔
61
            temporal_aggregation_ignore_no_data,
5✔
62
        }
5✔
63
    }
5✔
64

65
    #[allow(clippy::too_many_arguments, clippy::too_many_lines)] // TODO: refactor to reduce arguments
66
    async fn extract_raster_values<P: Pixel>(
9✔
67
        collection: &FeatureCollection<G>,
9✔
68
        raster_processor: &dyn RasterQueryProcessor<RasterType = P>,
9✔
69
        column_names: &[String],
9✔
70
        feature_aggreation: FeatureAggregationMethod,
9✔
71
        feature_aggregation_ignore_no_data: bool,
9✔
72
        temporal_aggregation: TemporalAggregationMethod,
9✔
73
        temporal_aggregation_ignore_no_data: bool,
9✔
74
        query: VectorQueryRectangle,
9✔
75
        ctx: &dyn QueryContext,
9✔
76
    ) -> Result<FeatureCollection<G>> {
9✔
77
        let mut temporal_band_aggregators = (0..column_names.len())
9✔
78
            .map(|_| {
10✔
79
                Self::create_aggregator::<P>(
10✔
80
                    collection.len(),
10✔
81
                    temporal_aggregation,
10✔
82
                    temporal_aggregation_ignore_no_data,
10✔
83
                )
84
            })
10✔
85
            .collect::<Vec<_>>();
9✔
86

87
        let collection = collection.sort_by_time_asc()?;
9✔
88

89
        let covered_pixels = collection.create_covered_pixels();
9✔
90

91
        let collection = covered_pixels.collection_ref();
9✔
92

93
        let mut cache_hint = CacheHint::max_duration();
9✔
94

95
        for time_span in FeatureTimeSpanIter::new(collection.time_intervals()) {
9✔
96
            let query = VectorQueryRectangle {
9✔
97
                spatial_bounds: query.spatial_bounds,
9✔
98
                time_interval: time_span.time_interval,
9✔
99
                spatial_resolution: query.spatial_resolution,
9✔
100
                attributes: ColumnSelection::all(),
9✔
101
            };
9✔
102

103
            let mut rasters = raster_processor
9✔
104
                .raster_query(
9✔
105
                    RasterQueryRectangle::from_qrect_and_bands(&query, BandSelection::first()),
9✔
106
                    ctx,
9✔
107
                )
9✔
108
                .await?;
9✔
109

110
            // TODO: optimize geo access (only specific tiles, etc.)
111

112
            let mut feature_band_aggregators = (0..column_names.len())
9✔
113
                .map(|_| {
10✔
114
                    create_feature_aggregator::<P>(
10✔
115
                        collection.len(),
10✔
116
                        feature_aggreation,
10✔
117
                        feature_aggregation_ignore_no_data,
10✔
118
                    )
119
                })
10✔
120
                .collect::<Vec<_>>();
9✔
121

122
            let mut time_end = None;
9✔
123

124
            while let Some(raster) = rasters.next().await {
225✔
125
                let raster = raster?;
218✔
126
                let band = raster.band as usize;
218✔
127

128
                if let Some(end) = time_end
218✔
129
                    && end != raster.time.end()
209✔
130
                {
131
                    // new time slice => consume old aggregator and create new one
132

133
                    let new_feature_agg = create_feature_aggregator::<P>(
7✔
134
                        collection.len(),
7✔
135
                        feature_aggreation,
7✔
136
                        feature_aggregation_ignore_no_data,
7✔
137
                    );
138

139
                    let olg_feature_agg =
7✔
140
                        std::mem::replace(&mut feature_band_aggregators[band], new_feature_agg);
7✔
141

142
                    temporal_band_aggregators[band].add_feature_data(
7✔
143
                        olg_feature_agg.into_data(),
7✔
144
                        time_span.time_interval.duration_ms(), // TODO: use individual feature duration?
7✔
NEW
145
                    )?;
×
146

147
                    if temporal_band_aggregators[band].is_satisfied() {
7✔
148
                        break;
2✔
149
                    }
5✔
150
                }
211✔
151
                time_end = Some(raster.time.end());
216✔
152

153
                for feature_index in time_span.feature_index_start..=time_span.feature_index_end {
672✔
154
                    // TODO: don't do random access but use a single iterator
155
                    let mut satisfied = false;
672✔
156
                    for grid_idx in covered_pixels.covered_pixels(feature_index, &raster) {
672✔
157
                        // try to get the pixel if the coordinate is within the current tile
158
                        if let Ok(pixel) = raster.get_at_grid_index(grid_idx) {
73✔
159
                            // finally, attach value to feature
160
                            if let Some(data) = pixel {
73✔
161
                                feature_band_aggregators[band].add_value(feature_index, data, 1);
69✔
162
                            } else {
69✔
163
                                // TODO: weigh by area?
4✔
164
                                feature_band_aggregators[band].add_null(feature_index);
4✔
165
                            }
4✔
166

167
                            if feature_band_aggregators[band].is_satisfied() {
73✔
168
                                satisfied = true;
8✔
169
                                break;
8✔
170
                            }
65✔
171
                        }
×
172
                    }
173

174
                    if satisfied {
672✔
175
                        break;
8✔
176
                    }
664✔
177
                }
178

179
                cache_hint.merge_with(&raster.cache_hint);
216✔
180
            }
181
            for (band, feature_aggregator) in feature_band_aggregators.into_iter().enumerate() {
10✔
182
                temporal_band_aggregators[band].add_feature_data(
10✔
183
                    feature_aggregator.into_data(),
10✔
184
                    time_span.time_interval.duration_ms(), // TODO: use individual feature duration?
10✔
185
                )?;
×
186
            }
187

188
            if (0..column_names.len()).all(|band| temporal_band_aggregators[band].is_satisfied()) {
9✔
189
                break;
4✔
190
            }
5✔
191
        }
192

193
        let feature_data = temporal_band_aggregators
9✔
194
            .into_iter()
9✔
195
            .map(TypedAggregator::into_data);
9✔
196

197
        let columns = column_names
9✔
198
            .iter()
9✔
199
            .map(String::as_str)
9✔
200
            .zip(feature_data)
9✔
201
            .collect::<Vec<_>>();
9✔
202

203
        let mut new_collection = collection
9✔
204
            .add_columns(&columns)
9✔
205
            .map_err(Into::<crate::error::Error>::into)?;
9✔
206

207
        new_collection.cache_hint = cache_hint;
9✔
208

209
        Ok(new_collection)
9✔
210
    }
9✔
211

212
    fn create_aggregator<P: Pixel>(
10✔
213
        number_of_features: usize,
10✔
214
        aggregation: TemporalAggregationMethod,
10✔
215
        ignore_no_data: bool,
10✔
216
    ) -> TypedAggregator {
10✔
217
        match aggregation {
10✔
218
            TemporalAggregationMethod::First => match P::TYPE {
3✔
219
                RasterDataType::U8
220
                | RasterDataType::U16
221
                | RasterDataType::U32
222
                | RasterDataType::U64
223
                | RasterDataType::I8
224
                | RasterDataType::I16
225
                | RasterDataType::I32
226
                | RasterDataType::I64 => {
227
                    FirstValueIntAggregator::new(number_of_features, ignore_no_data).into_typed()
3✔
228
                }
229
                RasterDataType::F32 | RasterDataType::F64 => {
230
                    FirstValueFloatAggregator::new(number_of_features, ignore_no_data).into_typed()
×
231
                }
232
            },
233
            TemporalAggregationMethod::Mean => {
234
                MeanValueAggregator::new(number_of_features, ignore_no_data).into_typed()
7✔
235
            }
236
            TemporalAggregationMethod::None => {
237
                unreachable!("this type of aggregator does not lead to this kind of processor")
×
238
            }
239
        }
240
    }
10✔
241
}
242

243
#[async_trait]
244
impl<G> QueryProcessor for RasterVectorAggregateJoinProcessor<G>
245
where
246
    G: Geometry + ArrowTyped + 'static,
247
    FeatureCollection<G>: PixelCoverCreator<G>,
248
{
249
    type Output = FeatureCollection<G>;
250
    type SpatialBounds = BoundingBox2D;
251
    type Selection = ColumnSelection;
252
    type ResultDescription = VectorResultDescriptor;
253

254
    async fn _query<'a>(
255
        &'a self,
256
        query: VectorQueryRectangle,
257
        ctx: &'a dyn QueryContext,
258
    ) -> Result<BoxStream<'a, Result<Self::Output>>> {
5✔
259
        let stream = self
260
            .collection
261
            .query(query.clone(), ctx)
262
            .await?
263
            .and_then(move |mut collection| {
5✔
264
                let query = query.clone();
5✔
265
                async move {
5✔
266
                    for raster_input in &self.raster_inputs {
10✔
267
                        collection = call_on_generic_raster_processor!(&raster_input.processor, raster => {
5✔
268
                            Self::extract_raster_values(
4✔
269
                                &collection,
4✔
270
                                raster,
4✔
271
                                &raster_input.column_names,
4✔
272
                                self.feature_aggregation,
4✔
273
                                self.feature_aggregation_ignore_no_data,
4✔
274
                                self.temporal_aggregation,
4✔
275
                                self.temporal_aggregation_ignore_no_data,
4✔
276
                                query.clone(),
4✔
277
                                ctx
4✔
278
                            ).await?
×
279
                        });
280
                    }
281

282
                    Ok(collection)
5✔
283
                }
5✔
284
            })
5✔
285
            .boxed();
286

287
        Ok(stream)
288
    }
5✔
289

290
    fn result_descriptor(&self) -> &Self::ResultDescription {
11✔
291
        &self.result_descriptor
11✔
292
    }
11✔
293
}
294

295
#[cfg(test)]
296
mod tests {
297
    use super::*;
298

299
    use crate::engine::{
300
        ChunkByteSize, MockExecutionContext, RasterBandDescriptor, RasterBandDescriptors,
301
        RasterResultDescriptor, VectorColumnInfo, VectorOperator, WorkflowOperatorPath,
302
    };
303
    use crate::engine::{MockQueryContext, RasterOperator};
304
    use crate::mock::{MockFeatureCollectionSource, MockRasterSource, MockRasterSourceParams};
305
    use geoengine_datatypes::collections::{
306
        ChunksEqualIgnoringCacheHint, MultiPointCollection, MultiPolygonCollection, VectorDataType,
307
    };
308
    use geoengine_datatypes::primitives::MultiPolygon;
309
    use geoengine_datatypes::primitives::{CacheHint, FeatureData, FeatureDataType, Measurement};
310
    use geoengine_datatypes::raster::{Grid2D, RasterTile2D, TileInformation};
311
    use geoengine_datatypes::spatial_reference::{SpatialReference, SpatialReferenceOption};
312
    use geoengine_datatypes::util::test::TestDefault;
313
    use geoengine_datatypes::{
314
        primitives::{BoundingBox2D, FeatureDataRef, MultiPoint, SpatialResolution, TimeInterval},
315
        raster::TilingSpecification,
316
    };
317

318
    #[tokio::test]
319
    async fn extract_raster_values_single_raster() {
1✔
320
        let raster_tile = RasterTile2D::<u8>::new_with_tile_info(
1✔
321
            TimeInterval::default(),
1✔
322
            TileInformation {
1✔
323
                global_geo_transform: TestDefault::test_default(),
1✔
324
                global_tile_position: [0, 0].into(),
1✔
325
                tile_size_in_pixels: [3, 2].into(),
1✔
326
            },
1✔
327
            0,
328
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
329
                .unwrap()
1✔
330
                .into(),
1✔
331
            CacheHint::default(),
1✔
332
        );
333

334
        let raster_source = MockRasterSource {
1✔
335
            params: MockRasterSourceParams {
1✔
336
                data: vec![raster_tile],
1✔
337
                result_descriptor: RasterResultDescriptor {
1✔
338
                    data_type: RasterDataType::U8,
1✔
339
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
340
                    time: None,
1✔
341
                    bbox: None,
1✔
342
                    resolution: None,
1✔
343
                    bands: RasterBandDescriptors::new_single_band(),
1✔
344
                },
1✔
345
            },
1✔
346
        }
1✔
347
        .boxed();
1✔
348

349
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
350
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
351
        );
352

353
        let raster_source = raster_source
1✔
354
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
355
            .await
1✔
356
            .unwrap();
1✔
357

358
        let points = MultiPointCollection::from_data(
1✔
359
            MultiPoint::many(vec![
1✔
360
                (0.0, 0.0),
1✔
361
                (1.0, 0.0),
1✔
362
                (0.0, -1.0),
1✔
363
                (1.0, -1.0),
1✔
364
                (0.0, -2.0),
1✔
365
                (1.0, -2.0),
1✔
366
            ])
367
            .unwrap(),
1✔
368
            vec![TimeInterval::default(); 6],
1✔
369
            Default::default(),
1✔
370
            CacheHint::default(),
1✔
371
        )
372
        .unwrap();
1✔
373

374
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
375
            &points,
1✔
376
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
377
            &["foo".to_string()],
1✔
378
            FeatureAggregationMethod::First,
1✔
379
            false,
1✔
380
            TemporalAggregationMethod::First,
1✔
381
            false,
1✔
382
            VectorQueryRectangle {
1✔
383
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (2.0, 0.).into()).unwrap(),
1✔
384
                time_interval: Default::default(),
1✔
385
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
386
                attributes: ColumnSelection::all(),
1✔
387
            },
1✔
388
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
389
        )
1✔
390
        .await
1✔
391
        .unwrap();
1✔
392

393
        if let FeatureDataRef::Int(extracted_data) = result.data("foo").unwrap() {
1✔
394
            assert_eq!(extracted_data.as_ref(), &[1, 2, 3, 4, 5, 6]);
1✔
395
        } else {
1✔
396
            unreachable!();
1✔
397
        }
1✔
398
    }
1✔
399

400
    #[tokio::test]
401
    #[allow(clippy::float_cmp)]
402
    async fn extract_raster_values_two_raster_timesteps() {
1✔
403
        let raster_tile_a = RasterTile2D::<u8>::new_with_tile_info(
1✔
404
            TimeInterval::new(0, 10).unwrap(),
1✔
405
            TileInformation {
1✔
406
                global_geo_transform: TestDefault::test_default(),
1✔
407
                global_tile_position: [0, 0].into(),
1✔
408
                tile_size_in_pixels: [3, 2].into(),
1✔
409
            },
1✔
410
            0,
411
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
412
                .unwrap()
1✔
413
                .into(),
1✔
414
            CacheHint::default(),
1✔
415
        );
416
        let raster_tile_b = RasterTile2D::new_with_tile_info(
1✔
417
            TimeInterval::new(10, 20).unwrap(),
1✔
418
            TileInformation {
1✔
419
                global_geo_transform: TestDefault::test_default(),
1✔
420
                global_tile_position: [0, 0].into(),
1✔
421
                tile_size_in_pixels: [3, 2].into(),
1✔
422
            },
1✔
423
            0,
424
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
425
                .unwrap()
1✔
426
                .into(),
1✔
427
            CacheHint::default(),
1✔
428
        );
429

430
        let raster_source = MockRasterSource {
1✔
431
            params: MockRasterSourceParams {
1✔
432
                data: vec![raster_tile_a, raster_tile_b],
1✔
433
                result_descriptor: RasterResultDescriptor {
1✔
434
                    data_type: RasterDataType::U8,
1✔
435
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
436
                    time: None,
1✔
437
                    bbox: None,
1✔
438
                    resolution: None,
1✔
439
                    bands: RasterBandDescriptors::new_single_band(),
1✔
440
                },
1✔
441
            },
1✔
442
        }
1✔
443
        .boxed();
1✔
444

445
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
446
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
447
        );
448

449
        let raster_source = raster_source
1✔
450
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
451
            .await
1✔
452
            .unwrap();
1✔
453

454
        let points = MultiPointCollection::from_data(
1✔
455
            MultiPoint::many(vec![
1✔
456
                (0.0, 0.0),
1✔
457
                (1.0, 0.0),
1✔
458
                (0.0, -1.0),
1✔
459
                (1.0, -1.0),
1✔
460
                (0.0, -2.0),
1✔
461
                (1.0, -2.0),
1✔
462
            ])
463
            .unwrap(),
1✔
464
            vec![TimeInterval::new(0, 20).unwrap(); 6],
1✔
465
            Default::default(),
1✔
466
            CacheHint::default(),
1✔
467
        )
468
        .unwrap();
1✔
469

470
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
471
            &points,
1✔
472
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
473
            &["foo".to_string()],
1✔
474
            FeatureAggregationMethod::First,
1✔
475
            false,
1✔
476
            TemporalAggregationMethod::Mean,
1✔
477
            false,
1✔
478
            VectorQueryRectangle {
1✔
479
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (2.0, 0.0).into()).unwrap(),
1✔
480
                time_interval: TimeInterval::new(0, 20).unwrap(),
1✔
481
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
482
                attributes: ColumnSelection::all(),
1✔
483
            },
1✔
484
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
485
        )
1✔
486
        .await
1✔
487
        .unwrap();
1✔
488

489
        if let FeatureDataRef::Float(extracted_data) = result.data("foo").unwrap() {
1✔
490
            assert_eq!(extracted_data.as_ref(), &[3.5, 3.5, 3.5, 3.5, 3.5, 3.5]);
1✔
491
        } else {
1✔
492
            unreachable!();
1✔
493
        }
1✔
494
    }
1✔
495

496
    #[tokio::test]
497
    #[allow(clippy::float_cmp, clippy::too_many_lines)]
498
    async fn extract_raster_values_two_spatial_tiles_per_time_step() {
1✔
499
        let raster_tile_a_0 = RasterTile2D::<u8>::new_with_tile_info(
1✔
500
            TimeInterval::new(0, 10).unwrap(),
1✔
501
            TileInformation {
1✔
502
                global_geo_transform: TestDefault::test_default(),
1✔
503
                global_tile_position: [0, 0].into(),
1✔
504
                tile_size_in_pixels: [3, 2].into(),
1✔
505
            },
1✔
506
            0,
507
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
508
                .unwrap()
1✔
509
                .into(),
1✔
510
            CacheHint::default(),
1✔
511
        );
512
        let raster_tile_a_1 = RasterTile2D::new_with_tile_info(
1✔
513
            TimeInterval::new(0, 10).unwrap(),
1✔
514
            TileInformation {
1✔
515
                global_geo_transform: TestDefault::test_default(),
1✔
516
                global_tile_position: [0, 1].into(),
1✔
517
                tile_size_in_pixels: [3, 2].into(),
1✔
518
            },
1✔
519
            0,
520
            Grid2D::new([3, 2].into(), vec![60, 50, 40, 30, 20, 10])
1✔
521
                .unwrap()
1✔
522
                .into(),
1✔
523
            CacheHint::default(),
1✔
524
        );
525
        let raster_tile_b_0 = RasterTile2D::new_with_tile_info(
1✔
526
            TimeInterval::new(10, 20).unwrap(),
1✔
527
            TileInformation {
1✔
528
                global_geo_transform: TestDefault::test_default(),
1✔
529
                global_tile_position: [0, 0].into(),
1✔
530
                tile_size_in_pixels: [3, 2].into(),
1✔
531
            },
1✔
532
            0,
533
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
534
                .unwrap()
1✔
535
                .into(),
1✔
536
            CacheHint::default(),
1✔
537
        );
538
        let raster_tile_b_1 = RasterTile2D::new_with_tile_info(
1✔
539
            TimeInterval::new(10, 20).unwrap(),
1✔
540
            TileInformation {
1✔
541
                global_geo_transform: TestDefault::test_default(),
1✔
542
                global_tile_position: [0, 1].into(),
1✔
543
                tile_size_in_pixels: [3, 2].into(),
1✔
544
            },
1✔
545
            0,
546
            Grid2D::new([3, 2].into(), vec![10, 20, 30, 40, 50, 60])
1✔
547
                .unwrap()
1✔
548
                .into(),
1✔
549
            CacheHint::default(),
1✔
550
        );
551

552
        let raster_source = MockRasterSource {
1✔
553
            params: MockRasterSourceParams {
1✔
554
                data: vec![
1✔
555
                    raster_tile_a_0,
1✔
556
                    raster_tile_a_1,
1✔
557
                    raster_tile_b_0,
1✔
558
                    raster_tile_b_1,
1✔
559
                ],
1✔
560
                result_descriptor: RasterResultDescriptor {
1✔
561
                    data_type: RasterDataType::U8,
1✔
562
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
563
                    time: None,
1✔
564
                    bbox: None,
1✔
565
                    resolution: None,
1✔
566
                    bands: RasterBandDescriptors::new_single_band(),
1✔
567
                },
1✔
568
            },
1✔
569
        }
1✔
570
        .boxed();
1✔
571

572
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
573
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
574
        );
575

576
        let raster_source = raster_source
1✔
577
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
578
            .await
1✔
579
            .unwrap();
1✔
580

581
        let points = MultiPointCollection::from_data(
1✔
582
            MultiPoint::many(vec![
1✔
583
                vec![(0.0, 0.0), (2.0, 0.0)],
1✔
584
                vec![(1.0, 0.0), (3.0, 0.0)],
1✔
585
            ])
586
            .unwrap(),
1✔
587
            vec![TimeInterval::new(0, 20).unwrap(); 2],
1✔
588
            Default::default(),
1✔
589
            CacheHint::default(),
1✔
590
        )
591
        .unwrap();
1✔
592

593
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
594
            &points,
1✔
595
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
596
            &["foo".to_string()],
1✔
597
            FeatureAggregationMethod::Mean,
1✔
598
            false,
1✔
599
            TemporalAggregationMethod::Mean,
1✔
600
            false,
1✔
601
            VectorQueryRectangle {
1✔
602
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (4.0, 0.0).into()).unwrap(),
1✔
603
                time_interval: TimeInterval::new(0, 20).unwrap(),
1✔
604
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
605
                attributes: ColumnSelection::all(),
1✔
606
            },
1✔
607
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
608
        )
1✔
609
        .await
1✔
610
        .unwrap();
1✔
611

612
        if let FeatureDataRef::Float(extracted_data) = result.data("foo").unwrap() {
1✔
613
            assert_eq!(
1✔
614
                extracted_data.as_ref(),
1✔
615
                &[(6. + 60. + 1. + 10.) / 4., (5. + 50. + 2. + 20.) / 4.]
1✔
616
            );
1✔
617
        } else {
1✔
618
            unreachable!();
1✔
619
        }
1✔
620
    }
1✔
621

622
    #[tokio::test]
623
    #[allow(clippy::too_many_lines)]
624
    #[allow(clippy::float_cmp)]
625
    async fn polygons() {
1✔
626
        let raster_tile_a_0 = RasterTile2D::<u8>::new_with_tile_info(
1✔
627
            TimeInterval::new(0, 10).unwrap(),
1✔
628
            TileInformation {
1✔
629
                global_geo_transform: TestDefault::test_default(),
1✔
630
                global_tile_position: [0, 0].into(),
1✔
631
                tile_size_in_pixels: [3, 2].into(),
1✔
632
            },
1✔
633
            0,
634
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
635
                .unwrap()
1✔
636
                .into(),
1✔
637
            CacheHint::default(),
1✔
638
        );
639
        let raster_tile_a_1 = RasterTile2D::new_with_tile_info(
1✔
640
            TimeInterval::new(0, 10).unwrap(),
1✔
641
            TileInformation {
1✔
642
                global_geo_transform: TestDefault::test_default(),
1✔
643
                global_tile_position: [0, 1].into(),
1✔
644
                tile_size_in_pixels: [3, 2].into(),
1✔
645
            },
1✔
646
            0,
647
            Grid2D::new([3, 2].into(), vec![60, 50, 40, 30, 20, 10])
1✔
648
                .unwrap()
1✔
649
                .into(),
1✔
650
            CacheHint::default(),
1✔
651
        );
652
        let raster_tile_a_2 = RasterTile2D::new_with_tile_info(
1✔
653
            TimeInterval::new(0, 10).unwrap(),
1✔
654
            TileInformation {
1✔
655
                global_geo_transform: TestDefault::test_default(),
1✔
656
                global_tile_position: [0, 2].into(),
1✔
657
                tile_size_in_pixels: [3, 2].into(),
1✔
658
            },
1✔
659
            0,
660
            Grid2D::new([3, 2].into(), vec![160, 150, 140, 130, 120, 110])
1✔
661
                .unwrap()
1✔
662
                .into(),
1✔
663
            CacheHint::default(),
1✔
664
        );
665
        let raster_tile_b_0 = RasterTile2D::new_with_tile_info(
1✔
666
            TimeInterval::new(10, 20).unwrap(),
1✔
667
            TileInformation {
1✔
668
                global_geo_transform: TestDefault::test_default(),
1✔
669
                global_tile_position: [0, 0].into(),
1✔
670
                tile_size_in_pixels: [3, 2].into(),
1✔
671
            },
1✔
672
            0,
673
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
674
                .unwrap()
1✔
675
                .into(),
1✔
676
            CacheHint::default(),
1✔
677
        );
678
        let raster_tile_b_1 = RasterTile2D::new_with_tile_info(
1✔
679
            TimeInterval::new(10, 20).unwrap(),
1✔
680
            TileInformation {
1✔
681
                global_geo_transform: TestDefault::test_default(),
1✔
682
                global_tile_position: [0, 1].into(),
1✔
683
                tile_size_in_pixels: [3, 2].into(),
1✔
684
            },
1✔
685
            0,
686
            Grid2D::new([3, 2].into(), vec![10, 20, 30, 40, 50, 60])
1✔
687
                .unwrap()
1✔
688
                .into(),
1✔
689
            CacheHint::default(),
1✔
690
        );
691
        let raster_tile_b_2 = RasterTile2D::new_with_tile_info(
1✔
692
            TimeInterval::new(10, 20).unwrap(),
1✔
693
            TileInformation {
1✔
694
                global_geo_transform: TestDefault::test_default(),
1✔
695
                global_tile_position: [0, 2].into(),
1✔
696
                tile_size_in_pixels: [3, 2].into(),
1✔
697
            },
1✔
698
            0,
699
            Grid2D::new([3, 2].into(), vec![110, 120, 130, 140, 150, 160])
1✔
700
                .unwrap()
1✔
701
                .into(),
1✔
702
            CacheHint::default(),
1✔
703
        );
704

705
        let raster_source = MockRasterSource {
1✔
706
            params: MockRasterSourceParams {
1✔
707
                data: vec![
1✔
708
                    raster_tile_a_0,
1✔
709
                    raster_tile_a_1,
1✔
710
                    raster_tile_a_2,
1✔
711
                    raster_tile_b_0,
1✔
712
                    raster_tile_b_1,
1✔
713
                    raster_tile_b_2,
1✔
714
                ],
1✔
715
                result_descriptor: RasterResultDescriptor {
1✔
716
                    data_type: RasterDataType::U8,
1✔
717
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
718
                    time: None,
1✔
719
                    bbox: None,
1✔
720
                    resolution: None,
1✔
721
                    bands: RasterBandDescriptors::new_single_band(),
1✔
722
                },
1✔
723
            },
1✔
724
        }
1✔
725
        .boxed();
1✔
726

727
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
728
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
729
        );
730

731
        let raster_source = raster_source
1✔
732
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
733
            .await
1✔
734
            .unwrap();
1✔
735

736
        let polygons = MultiPolygonCollection::from_data(
1✔
737
            vec![
1✔
738
                MultiPolygon::new(vec![vec![vec![
1✔
739
                    (0.5, -0.5).into(),
1✔
740
                    (4., -1.).into(),
1✔
741
                    (0.5, -2.5).into(),
1✔
742
                    (0.5, -0.5).into(),
1✔
743
                ]]])
744
                .unwrap(),
1✔
745
            ],
746
            vec![TimeInterval::new(0, 20).unwrap(); 1],
1✔
747
            Default::default(),
1✔
748
            CacheHint::default(),
1✔
749
        )
750
        .unwrap();
1✔
751

752
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
753
            &polygons,
1✔
754
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
755
            &["foo".to_string()],
1✔
756
            FeatureAggregationMethod::Mean,
1✔
757
            false,
1✔
758
            TemporalAggregationMethod::Mean,
1✔
759
            false,
1✔
760
            VectorQueryRectangle {
1✔
761
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (4.0, 0.0).into()).unwrap(),
1✔
762
                time_interval: TimeInterval::new(0, 20).unwrap(),
1✔
763
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
764
                attributes: ColumnSelection::all(),
1✔
765
            },
1✔
766
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
767
        )
1✔
768
        .await
1✔
769
        .unwrap();
1✔
770

771
        if let FeatureDataRef::Float(extracted_data) = result.data("foo").unwrap() {
1✔
772
            assert_eq!(
1✔
773
                extracted_data.as_ref(),
1✔
774
                &[(3. + 1. + 40. + 30. + 140. + 4. + 6. + 30. + 40. + 130.) / 10.]
1✔
775
            );
1✔
776
        } else {
1✔
777
            unreachable!();
1✔
778
        }
1✔
779
    }
1✔
780

781
    #[tokio::test]
782
    #[allow(clippy::float_cmp)]
783
    #[allow(clippy::too_many_lines)]
784
    async fn polygons_multi_band() {
1✔
785
        let raster_tile_a_0_band_0 = RasterTile2D::new_with_tile_info(
1✔
786
            TimeInterval::new(0, 10).unwrap(),
1✔
787
            TileInformation {
1✔
788
                global_geo_transform: TestDefault::test_default(),
1✔
789
                global_tile_position: [0, 0].into(),
1✔
790
                tile_size_in_pixels: [3, 2].into(),
1✔
791
            },
1✔
792
            0,
793
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
794
                .unwrap()
1✔
795
                .into(),
1✔
796
            CacheHint::default(),
1✔
797
        );
798
        let raster_tile_a_0_band_1 = RasterTile2D::new_with_tile_info(
1✔
799
            TimeInterval::new(0, 10).unwrap(),
1✔
800
            TileInformation {
1✔
801
                global_geo_transform: TestDefault::test_default(),
1✔
802
                global_tile_position: [0, 0].into(),
1✔
803
                tile_size_in_pixels: [3, 2].into(),
1✔
804
            },
1✔
805
            1,
806
            Grid2D::new([3, 2].into(), vec![255, 254, 253, 251, 250, 249])
1✔
807
                .unwrap()
1✔
808
                .into(),
1✔
809
            CacheHint::default(),
1✔
810
        );
811

812
        let raster_tile_a_1_band_0 = RasterTile2D::new_with_tile_info(
1✔
813
            TimeInterval::new(0, 10).unwrap(),
1✔
814
            TileInformation {
1✔
815
                global_geo_transform: TestDefault::test_default(),
1✔
816
                global_tile_position: [0, 1].into(),
1✔
817
                tile_size_in_pixels: [3, 2].into(),
1✔
818
            },
1✔
819
            0,
820
            Grid2D::new([3, 2].into(), vec![60, 50, 40, 30, 20, 10])
1✔
821
                .unwrap()
1✔
822
                .into(),
1✔
823
            CacheHint::default(),
1✔
824
        );
825
        let raster_tile_a_1_band_1 = RasterTile2D::new_with_tile_info(
1✔
826
            TimeInterval::new(0, 10).unwrap(),
1✔
827
            TileInformation {
1✔
828
                global_geo_transform: TestDefault::test_default(),
1✔
829
                global_tile_position: [0, 1].into(),
1✔
830
                tile_size_in_pixels: [3, 2].into(),
1✔
831
            },
1✔
832
            1,
833
            Grid2D::new([3, 2].into(), vec![160, 150, 140, 130, 120, 110])
1✔
834
                .unwrap()
1✔
835
                .into(),
1✔
836
            CacheHint::default(),
1✔
837
        );
838

839
        let raster_tile_a_2_band_0 = RasterTile2D::new_with_tile_info(
1✔
840
            TimeInterval::new(0, 10).unwrap(),
1✔
841
            TileInformation {
1✔
842
                global_geo_transform: TestDefault::test_default(),
1✔
843
                global_tile_position: [0, 2].into(),
1✔
844
                tile_size_in_pixels: [3, 2].into(),
1✔
845
            },
1✔
846
            0,
847
            Grid2D::new([3, 2].into(), vec![600, 500, 400, 300, 200, 100])
1✔
848
                .unwrap()
1✔
849
                .into(),
1✔
850
            CacheHint::default(),
1✔
851
        );
852
        let raster_tile_a_2_band_1 = RasterTile2D::new_with_tile_info(
1✔
853
            TimeInterval::new(0, 10).unwrap(),
1✔
854
            TileInformation {
1✔
855
                global_geo_transform: TestDefault::test_default(),
1✔
856
                global_tile_position: [0, 2].into(),
1✔
857
                tile_size_in_pixels: [3, 2].into(),
1✔
858
            },
1✔
859
            1,
860
            Grid2D::new([3, 2].into(), vec![610, 510, 410, 310, 210, 110])
1✔
861
                .unwrap()
1✔
862
                .into(),
1✔
863
            CacheHint::default(),
1✔
864
        );
865

866
        let raster_tile_b_0_band_0 = RasterTile2D::new_with_tile_info(
1✔
867
            TimeInterval::new(10, 20).unwrap(),
1✔
868
            TileInformation {
1✔
869
                global_geo_transform: TestDefault::test_default(),
1✔
870
                global_tile_position: [0, 0].into(),
1✔
871
                tile_size_in_pixels: [3, 2].into(),
1✔
872
            },
1✔
873
            0,
874
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
875
                .unwrap()
1✔
876
                .into(),
1✔
877
            CacheHint::default(),
1✔
878
        );
879
        let raster_tile_b_0_band_1 = RasterTile2D::new_with_tile_info(
1✔
880
            TimeInterval::new(10, 20).unwrap(),
1✔
881
            TileInformation {
1✔
882
                global_geo_transform: TestDefault::test_default(),
1✔
883
                global_tile_position: [0, 0].into(),
1✔
884
                tile_size_in_pixels: [3, 2].into(),
1✔
885
            },
1✔
886
            1,
887
            Grid2D::new([3, 2].into(), vec![11, 22, 33, 44, 55, 66])
1✔
888
                .unwrap()
1✔
889
                .into(),
1✔
890
            CacheHint::default(),
1✔
891
        );
892
        let raster_tile_b_1_band_0 = RasterTile2D::new_with_tile_info(
1✔
893
            TimeInterval::new(10, 20).unwrap(),
1✔
894
            TileInformation {
1✔
895
                global_geo_transform: TestDefault::test_default(),
1✔
896
                global_tile_position: [0, 1].into(),
1✔
897
                tile_size_in_pixels: [3, 2].into(),
1✔
898
            },
1✔
899
            0,
900
            Grid2D::new([3, 2].into(), vec![10, 20, 30, 40, 50, 60])
1✔
901
                .unwrap()
1✔
902
                .into(),
1✔
903
            CacheHint::default(),
1✔
904
        );
905
        let raster_tile_b_1_band_1 = RasterTile2D::new_with_tile_info(
1✔
906
            TimeInterval::new(10, 20).unwrap(),
1✔
907
            TileInformation {
1✔
908
                global_geo_transform: TestDefault::test_default(),
1✔
909
                global_tile_position: [0, 1].into(),
1✔
910
                tile_size_in_pixels: [3, 2].into(),
1✔
911
            },
1✔
912
            1,
913
            Grid2D::new([3, 2].into(), vec![100, 220, 300, 400, 500, 600])
1✔
914
                .unwrap()
1✔
915
                .into(),
1✔
916
            CacheHint::default(),
1✔
917
        );
918

919
        let raster_tile_b_2_band_0 = RasterTile2D::new_with_tile_info(
1✔
920
            TimeInterval::new(10, 20).unwrap(),
1✔
921
            TileInformation {
1✔
922
                global_geo_transform: TestDefault::test_default(),
1✔
923
                global_tile_position: [0, 2].into(),
1✔
924
                tile_size_in_pixels: [3, 2].into(),
1✔
925
            },
1✔
926
            0,
927
            Grid2D::new([3, 2].into(), vec![100, 200, 300, 400, 500, 600])
1✔
928
                .unwrap()
1✔
929
                .into(),
1✔
930
            CacheHint::default(),
1✔
931
        );
932
        let raster_tile_b_2_band_1 = RasterTile2D::new_with_tile_info(
1✔
933
            TimeInterval::new(10, 20).unwrap(),
1✔
934
            TileInformation {
1✔
935
                global_geo_transform: TestDefault::test_default(),
1✔
936
                global_tile_position: [0, 2].into(),
1✔
937
                tile_size_in_pixels: [3, 2].into(),
1✔
938
            },
1✔
939
            1,
940
            Grid2D::new([3, 2].into(), vec![101, 201, 301, 401, 501, 601])
1✔
941
                .unwrap()
1✔
942
                .into(),
1✔
943
            CacheHint::default(),
1✔
944
        );
945

946
        let raster_source = MockRasterSource {
1✔
947
            params: MockRasterSourceParams {
1✔
948
                data: vec![
1✔
949
                    raster_tile_a_0_band_0,
1✔
950
                    raster_tile_a_0_band_1,
1✔
951
                    raster_tile_a_1_band_0,
1✔
952
                    raster_tile_a_1_band_1,
1✔
953
                    raster_tile_a_2_band_0,
1✔
954
                    raster_tile_a_2_band_1,
1✔
955
                    raster_tile_b_0_band_0,
1✔
956
                    raster_tile_b_0_band_1,
1✔
957
                    raster_tile_b_1_band_0,
1✔
958
                    raster_tile_b_1_band_1,
1✔
959
                    raster_tile_b_2_band_0,
1✔
960
                    raster_tile_b_2_band_1,
1✔
961
                ],
1✔
962
                result_descriptor: RasterResultDescriptor {
1✔
963
                    data_type: RasterDataType::U16,
1✔
964
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
965
                    time: None,
1✔
966
                    bbox: None,
1✔
967
                    resolution: None,
1✔
968
                    bands: RasterBandDescriptors::new(vec![
1✔
969
                        RasterBandDescriptor::new_unitless("band_0".into()),
1✔
970
                        RasterBandDescriptor::new_unitless("band_1".into()),
1✔
971
                    ])
1✔
972
                    .unwrap(),
1✔
973
                },
1✔
974
            },
1✔
975
        }
1✔
976
        .boxed();
1✔
977

978
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
979
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
980
        );
981

982
        let raster = raster_source
1✔
983
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
984
            .await
1✔
985
            .unwrap()
1✔
986
            .query_processor()
1✔
987
            .unwrap();
1✔
988

989
        let polygons = MultiPolygonCollection::from_data(
1✔
990
            vec![
1✔
991
                MultiPolygon::new(vec![vec![vec![
1✔
992
                    (0.5, -0.5).into(),
1✔
993
                    (4., -1.).into(),
1✔
994
                    (0.5, -2.5).into(),
1✔
995
                    (0.5, -0.5).into(),
1✔
996
                ]]])
997
                .unwrap(),
1✔
998
            ],
999
            vec![TimeInterval::new(0, 20).unwrap(); 1],
1✔
1000
            Default::default(),
1✔
1001
            CacheHint::default(),
1✔
1002
        )
1003
        .unwrap();
1✔
1004

1005
        let polygons = MockFeatureCollectionSource::single(polygons).boxed();
1✔
1006

1007
        let points = polygons
1✔
1008
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1009
            .await
1✔
1010
            .unwrap()
1✔
1011
            .query_processor()
1✔
1012
            .unwrap()
1✔
1013
            .multi_polygon()
1✔
1014
            .unwrap();
1✔
1015

1016
        let processor = RasterVectorAggregateJoinProcessor::new(
1✔
1017
            points,
1✔
1018
            VectorResultDescriptor {
1✔
1019
                data_type: VectorDataType::MultiPoint,
1✔
1020
                spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1021
                columns: [(
1✔
1022
                    "ndvi".to_string(),
1✔
1023
                    VectorColumnInfo {
1✔
1024
                        data_type: FeatureDataType::Int,
1✔
1025
                        measurement: Measurement::Unitless,
1✔
1026
                    },
1✔
1027
                )]
1✔
1028
                .into_iter()
1✔
1029
                .collect(),
1✔
1030
                time: None,
1✔
1031
                bbox: None,
1✔
1032
            },
1✔
1033
            vec![RasterInput {
1✔
1034
                processor: raster,
1✔
1035
                column_names: vec!["foo".to_owned(), "foo_1".to_owned()],
1✔
1036
            }],
1✔
1037
            FeatureAggregationMethod::Mean,
1✔
1038
            false,
1039
            TemporalAggregationMethod::Mean,
1✔
1040
            false,
1041
        );
1042

1043
        let mut result = processor
1✔
1044
            .query(
1✔
1045
                VectorQueryRectangle {
1✔
1046
                    spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (4.0, 0.0).into())
1✔
1047
                        .unwrap(),
1✔
1048
                    time_interval: TimeInterval::new_unchecked(0, 20),
1✔
1049
                    spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
1050
                    attributes: ColumnSelection::all(),
1✔
1051
                },
1✔
1052
                &MockQueryContext::new(ChunkByteSize::MAX),
1✔
1053
            )
1✔
1054
            .await
1✔
1055
            .unwrap()
1✔
1056
            .map(Result::unwrap)
1✔
1057
            .collect::<Vec<MultiPolygonCollection>>()
1✔
1058
            .await;
1✔
1059

1060
        assert_eq!(result.len(), 1);
1✔
1061

1062
        let result = result.remove(0);
1✔
1063

1064
        assert!(
1✔
1065
            result.chunks_equal_ignoring_cache_hint(
1✔
1066
                &MultiPolygonCollection::from_slices(
1✔
1067
                    &[MultiPolygon::new(vec![vec![vec![
1✔
1068
                        (0.5, -0.5).into(),
1✔
1069
                        (4., -1.).into(),
1✔
1070
                        (0.5, -2.5).into(),
1✔
1071
                        (0.5, -0.5).into(),
1✔
1072
                    ]]])
1✔
1073
                    .unwrap(),],
1✔
1074
                    &[TimeInterval::new(0, 20).unwrap()],
1✔
1075
                    &[
1✔
1076
                        (
1✔
1077
                            "foo",
1✔
1078
                            FeatureData::Float(vec![f64::midpoint(
1✔
1079
                                (3. + 1. + 40. + 30. + 400.) / 5.,
1✔
1080
                                (4. + 6. + 30. + 40. + 300.) / 5.
1✔
1081
                            )])
1✔
1082
                        ),
1✔
1083
                        (
1✔
1084
                            "foo_1",
1✔
1085
                            FeatureData::Float(vec![f64::midpoint(
1✔
1086
                                (251. + 249. + 140. + 130. + 410.) / 5.,
1✔
1087
                                (44. + 66. + 300. + 400. + 301.) / 5.
1✔
1088
                            )])
1✔
1089
                        )
1✔
1090
                    ],
1✔
1091
                )
1✔
1092
                .unwrap()
1✔
1093
            )
1✔
1094
        );
1✔
1095
    }
1✔
1096
}
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