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

geo-engine / geoengine / 3929938005

pending completion
3929938005

push

github

GitHub
Merge #713

84930 of 96741 relevant lines covered (87.79%)

79640.1 hits per line

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

84.56
/operators/src/processing/raster_vector_join/aggregator.rs
1
use crate::error;
2
use crate::error::Error;
3
use crate::util::Result;
4
use geoengine_datatypes::primitives::{FeatureData, FeatureDataType};
5
use geoengine_datatypes::raster::Pixel;
6
use num_traits::AsPrimitive;
7
use snafu::ensure;
8

9
/// Aggregating raster pixel values for features
10
pub trait Aggregator {
11
    type Output: Pixel;
12

13
    fn new(number_of_features: usize) -> Self;
14

15
    // TODO: add values for slice
16
    fn add_value<P>(&mut self, feature_idx: usize, pixel: P, weight: u64)
17
    where
18
        P: Pixel + AsPrimitive<Self::Output>;
19

20
    fn add_null(&mut self, feature_idx: usize);
21

22
    fn feature_data_type() -> FeatureDataType;
23

24
    fn data(&self) -> &[Self::Output];
25

26
    fn nulls(&self) -> &[bool];
27

28
    fn into_data(self) -> Vec<Option<Self::Output>>;
29

30
    fn into_typed(self) -> TypedAggregator;
31

32
    /// Whether an aggregator needs no more values for producing the outcome
33
    fn is_satisfied(&self) -> bool;
34

35
    /// Add all values from `data` to the aggregator. Fails if the data length doesn't match
36
    /// the aggregator or if the data is not compatible with the aggregator.
37
    fn add_feature_data(&mut self, data: FeatureData, weight: u64) -> Result<()>;
38
}
39

40
/// An aggregator wrapper for different return types
41
pub enum TypedAggregator {
42
    FirstValueFloat(FirstValueFloatAggregator),
43
    FirstValueInt(FirstValueIntAggregator),
44
    MeanNumber(MeanValueAggregator),
45
}
46

47
impl TypedAggregator {
48
    pub fn add_value<P>(&mut self, feature_idx: usize, pixel: P, weight: u64)
92✔
49
    where
92✔
50
        P: Pixel + AsPrimitive<f64> + AsPrimitive<i64>,
92✔
51
    {
92✔
52
        match self {
92✔
53
            TypedAggregator::FirstValueFloat(aggregator) => {
×
54
                aggregator.add_value(feature_idx, pixel, weight);
×
55
            }
×
56
            TypedAggregator::FirstValueInt(aggregator) => {
60✔
57
                aggregator.add_value(feature_idx, pixel, weight);
60✔
58
            }
60✔
59
            TypedAggregator::MeanNumber(aggregator) => {
32✔
60
                aggregator.add_value(feature_idx, pixel, weight);
32✔
61
            }
32✔
62
        }
63
    }
92✔
64

65
    pub fn add_null(&mut self, feature_idx: usize) {
7✔
66
        match self {
7✔
67
            TypedAggregator::FirstValueFloat(aggregator) => aggregator.add_null(feature_idx),
×
68
            TypedAggregator::FirstValueInt(aggregator) => aggregator.add_null(feature_idx),
7✔
69
            TypedAggregator::MeanNumber(aggregator) => aggregator.add_null(feature_idx),
×
70
        }
71
    }
7✔
72

73
    pub fn into_data(self) -> FeatureData {
33✔
74
        match self {
33✔
75
            TypedAggregator::FirstValueFloat(aggregator) => {
×
76
                FeatureData::NullableFloat(aggregator.into_data())
×
77
            }
78
            TypedAggregator::FirstValueInt(aggregator) => {
20✔
79
                FeatureData::NullableInt(aggregator.into_data())
20✔
80
            }
81
            TypedAggregator::MeanNumber(aggregator) => {
13✔
82
                FeatureData::NullableFloat(aggregator.into_data())
13✔
83
            }
84
        }
85
    }
33✔
86

87
    #[allow(dead_code)]
88
    pub fn nulls(&self) -> &[bool] {
1✔
89
        match self {
1✔
90
            TypedAggregator::FirstValueFloat(aggregator) => aggregator.nulls(),
×
91
            TypedAggregator::FirstValueInt(aggregator) => aggregator.nulls(),
1✔
92
            TypedAggregator::MeanNumber(aggregator) => aggregator.nulls(),
×
93
        }
94
    }
1✔
95

96
    /// Whether an aggregator needs no more values for producing the outcome
97
    pub fn is_satisfied(&self) -> bool {
71✔
98
        match self {
71✔
99
            TypedAggregator::FirstValueFloat(aggregator) => aggregator.is_satisfied(),
×
100
            TypedAggregator::FirstValueInt(aggregator) => aggregator.is_satisfied(),
45✔
101
            TypedAggregator::MeanNumber(aggregator) => aggregator.is_satisfied(),
26✔
102
        }
103
    }
71✔
104

105
    pub fn add_feature_data(&mut self, data: FeatureData, weight: u64) -> Result<()> {
14✔
106
        match self {
14✔
107
            TypedAggregator::FirstValueFloat(a) => a.add_feature_data(data, weight),
×
108
            TypedAggregator::FirstValueInt(a) => a.add_feature_data(data, weight),
4✔
109
            TypedAggregator::MeanNumber(a) => a.add_feature_data(data, weight),
10✔
110
        }
111
    }
14✔
112
}
113

114
pub type FirstValueFloatAggregator = FirstValueAggregator<f64>;
115
pub type FirstValueIntAggregator = FirstValueAggregator<i64>;
116

117
/// Aggregation function that uses only the first value occurrence
118
pub struct FirstValueAggregator<T> {
119
    values: Vec<T>,
120
    not_pristine: Vec<bool>,
121
    null: Vec<bool>,
122
    number_of_pristine_values: usize,
123
}
124

125
impl<T> Aggregator for FirstValueAggregator<T>
126
where
127
    T: Pixel + FirstValueOutputType,
128
{
129
    type Output = T;
130

131
    fn new(number_of_features: usize) -> Self {
24✔
132
        Self {
24✔
133
            values: vec![T::zero(); number_of_features],
24✔
134
            not_pristine: vec![false; number_of_features],
24✔
135
            null: vec![false; number_of_features],
24✔
136
            number_of_pristine_values: number_of_features,
24✔
137
        }
24✔
138
    }
24✔
139

140
    fn add_value<P>(&mut self, feature_idx: usize, pixel: P, _weight: u64)
78✔
141
    where
78✔
142
        P: Pixel + AsPrimitive<Self::Output>,
78✔
143
    {
78✔
144
        if self.not_pristine[feature_idx] {
78✔
145
            return;
6✔
146
        }
72✔
147

72✔
148
        self.values[feature_idx] = pixel.as_();
72✔
149

72✔
150
        self.not_pristine[feature_idx] = true;
72✔
151
        self.number_of_pristine_values -= 1;
72✔
152
    }
78✔
153

154
    fn add_null(&mut self, feature_idx: usize) {
7✔
155
        if self.not_pristine[feature_idx] {
7✔
156
            return;
1✔
157
        }
6✔
158

6✔
159
        self.null[feature_idx] = true;
6✔
160

6✔
161
        self.not_pristine[feature_idx] = true;
6✔
162
        self.number_of_pristine_values -= 1;
6✔
163
    }
7✔
164

165
    fn feature_data_type() -> FeatureDataType {
×
166
        T::feature_data_type()
×
167
    }
×
168

169
    fn data(&self) -> &[Self::Output] {
3✔
170
        &self.values
3✔
171
    }
3✔
172

173
    fn nulls(&self) -> &[bool] {
1✔
174
        &self.null
1✔
175
    }
1✔
176

177
    fn into_data(self) -> Vec<Option<Self::Output>> {
20✔
178
        self.values
20✔
179
            .into_iter()
20✔
180
            .zip(self.null)
20✔
181
            .map(|(value, is_null)| if is_null { None } else { Some(value) })
75✔
182
            .collect()
20✔
183
    }
20✔
184

185
    fn into_typed(self) -> TypedAggregator {
22✔
186
        T::typed_aggregator(self)
22✔
187
    }
22✔
188

189
    fn is_satisfied(&self) -> bool {
45✔
190
        self.number_of_pristine_values == 0
45✔
191
    }
45✔
192

193
    fn add_feature_data(&mut self, data: FeatureData, weight: u64) -> Result<()> {
4✔
194
        ensure!(
4✔
195
            data.len() == self.values.len(),
4✔
196
            error::FeatureDataLengthMismatch
×
197
        );
198

199
        match data {
4✔
200
            geoengine_datatypes::primitives::FeatureData::NullableInt(values) => {
4✔
201
                for (i, &value) in values.iter().enumerate() {
12✔
202
                    if let Some(value) = value {
12✔
203
                        self.add_value(i, T::from_(value), weight);
12✔
204
                    } else {
12✔
205
                        self.add_null(i);
×
206
                    }
×
207
                }
208
            }
209
            geoengine_datatypes::primitives::FeatureData::NullableFloat(values) => {
×
210
                for (i, &value) in values.iter().enumerate() {
×
211
                    if let Some(value) = value {
×
212
                        self.add_value(i, T::from_(value), weight);
×
213
                    } else {
×
214
                        self.add_null(i);
×
215
                    }
×
216
                }
217
            }
218
            _ => return Err(Error::FeatureDataNotAggregatable),
×
219
        }
220

221
        Ok(())
4✔
222
    }
4✔
223
}
224

225
pub trait FirstValueOutputType {
226
    fn feature_data_type() -> FeatureDataType;
227
    fn typed_aggregator(aggregator: FirstValueAggregator<Self>) -> TypedAggregator
228
    where
229
        Self: Sized;
230
}
231

232
impl FirstValueOutputType for i64 {
233
    fn feature_data_type() -> FeatureDataType {
×
234
        FeatureDataType::Int
×
235
    }
×
236

237
    fn typed_aggregator(aggregator: FirstValueAggregator<Self>) -> TypedAggregator {
22✔
238
        TypedAggregator::FirstValueInt(aggregator)
22✔
239
    }
22✔
240
}
241

242
impl FirstValueOutputType for f64 {
243
    fn feature_data_type() -> FeatureDataType {
×
244
        FeatureDataType::Float
×
245
    }
×
246

247
    fn typed_aggregator(aggregator: FirstValueAggregator<Self>) -> TypedAggregator {
×
248
        TypedAggregator::FirstValueFloat(aggregator)
×
249
    }
×
250
}
251

252
/// Aggregation function that calculates the weighted mean
253
pub struct MeanValueAggregator {
254
    means: Vec<f64>,
255
    sum_weights: Vec<f64>,
256
    null: Vec<bool>,
257
    number_of_non_null_values: usize,
258
}
259

260
impl Aggregator for MeanValueAggregator {
261
    type Output = f64;
262

263
    fn new(number_of_features: usize) -> Self {
14✔
264
        Self {
14✔
265
            means: vec![0.; number_of_features],
14✔
266
            sum_weights: vec![0.; number_of_features],
14✔
267
            null: vec![false; number_of_features],
14✔
268
            number_of_non_null_values: number_of_features,
14✔
269
        }
14✔
270
    }
14✔
271

272
    fn add_value<P>(&mut self, feature_idx: usize, pixel: P, weight: u64)
82✔
273
    where
82✔
274
        P: Pixel + AsPrimitive<Self::Output>,
82✔
275
    {
82✔
276
        debug_assert!(weight > 0, "weights must be positive and non-zero");
82✔
277

278
        if self.null[feature_idx] {
82✔
279
            return;
4✔
280
        }
78✔
281

78✔
282
        let value: f64 = pixel.as_();
78✔
283
        let weight: f64 = weight.as_();
78✔
284

78✔
285
        let old_mean = self.means[feature_idx];
78✔
286
        let old_normalized_weight = self.sum_weights[feature_idx] / weight;
78✔
287

78✔
288
        self.sum_weights[feature_idx] += weight;
78✔
289
        self.means[feature_idx] += (value - old_mean) / (old_normalized_weight + 1.);
78✔
290
    }
82✔
291

292
    fn add_null(&mut self, feature_idx: usize) {
4✔
293
        if self.null[feature_idx] {
4✔
294
            return;
×
295
        }
4✔
296

4✔
297
        self.null[feature_idx] = true;
4✔
298
        self.number_of_non_null_values -= 1;
4✔
299
    }
4✔
300

301
    fn feature_data_type() -> FeatureDataType {
×
302
        FeatureDataType::Float
×
303
    }
×
304

305
    fn data(&self) -> &[Self::Output] {
1✔
306
        &self.means
1✔
307
    }
1✔
308

309
    fn nulls(&self) -> &[bool] {
×
310
        &self.null
×
311
    }
×
312

313
    fn into_data(self) -> Vec<Option<Self::Output>> {
13✔
314
        self.means
13✔
315
            .into_iter()
13✔
316
            .zip(self.null)
13✔
317
            .map(|(value, is_null)| if is_null { None } else { Some(value) })
29✔
318
            .collect()
13✔
319
    }
13✔
320

321
    fn into_typed(self) -> TypedAggregator {
13✔
322
        TypedAggregator::MeanNumber(self)
13✔
323
    }
13✔
324

325
    fn is_satisfied(&self) -> bool {
26✔
326
        self.number_of_non_null_values == 0
26✔
327
    }
26✔
328

329
    fn add_feature_data(&mut self, data: FeatureData, weight: u64) -> Result<()> {
10✔
330
        ensure!(
10✔
331
            data.len() == self.means.len(),
10✔
332
            error::FeatureDataLengthMismatch
×
333
        );
334

335
        match data {
10✔
336
            geoengine_datatypes::primitives::FeatureData::NullableInt(values) => {
6✔
337
                for (i, &value) in values.iter().enumerate() {
28✔
338
                    if let Some(value) = value {
28✔
339
                        self.add_value(i, value, weight);
24✔
340
                    } else {
24✔
341
                        self.add_null(i);
4✔
342
                    }
4✔
343
                }
344
            }
345
            geoengine_datatypes::primitives::FeatureData::NullableFloat(values) => {
4✔
346
                for (i, &value) in values.iter().enumerate() {
6✔
347
                    if let Some(value) = value {
6✔
348
                        self.add_value(i, value, weight);
6✔
349
                    } else {
6✔
350
                        self.add_null(i);
×
351
                    }
×
352
                }
353
            }
354
            _ => return Err(Error::FeatureDataNotAggregatable),
×
355
        }
356

357
        Ok(())
10✔
358
    }
10✔
359
}
360

361
#[cfg(test)]
362
mod tests {
363
    use super::*;
364

365
    #[test]
1✔
366
    #[allow(clippy::float_cmp)]
367
    fn fist_value_f64() {
1✔
368
        let mut aggregator = FirstValueFloatAggregator::new(2);
1✔
369

1✔
370
        aggregator.add_value(0, 1, 1);
1✔
371
        aggregator.add_value(0, 2, 1);
1✔
372

1✔
373
        aggregator.add_value(1, 10, 1);
1✔
374

1✔
375
        assert_eq!(aggregator.data(), &[1., 10.]);
1✔
376
    }
1✔
377

378
    #[test]
1✔
379
    fn fist_value_i64() {
1✔
380
        let mut aggregator = FirstValueIntAggregator::new(2);
1✔
381

1✔
382
        aggregator.add_value(0, 2., 1);
1✔
383
        aggregator.add_value(0, 0., 1);
1✔
384

1✔
385
        aggregator.add_value(1, 4., 1);
1✔
386

1✔
387
        assert_eq!(aggregator.data(), &[2, 4]);
1✔
388
    }
1✔
389

390
    #[test]
1✔
391
    #[allow(clippy::float_cmp)]
392
    fn mean() {
1✔
393
        let mut aggregator = MeanValueAggregator::new(2);
1✔
394

395
        for i in 1..=10 {
11✔
396
            aggregator.add_value(0, i, 1);
10✔
397
            aggregator.add_value(1, i, i);
10✔
398
        }
10✔
399

400
        assert_eq!(aggregator.data(), &[5.5, 385. / 55.]);
1✔
401
    }
1✔
402

403
    #[test]
1✔
404
    fn typed() {
1✔
405
        let mut aggregator = FirstValueIntAggregator::new(2).into_typed();
1✔
406

1✔
407
        aggregator.add_value(0, 2., 1);
1✔
408
        aggregator.add_value(0, 0., 1);
1✔
409

1✔
410
        aggregator.add_value(1, 4., 1);
1✔
411

412
        if let TypedAggregator::FirstValueInt(ref aggregator) = aggregator {
1✔
413
            assert_eq!(aggregator.data(), &[2, 4]);
1✔
414
        } else {
415
            unreachable!();
×
416
        }
417

418
        assert_eq!(
1✔
419
            aggregator.into_data(),
1✔
420
            FeatureData::NullableInt(vec![Some(2), Some(4)])
1✔
421
        );
1✔
422
    }
1✔
423

424
    #[test]
1✔
425
    fn satisfaction() {
1✔
426
        let mut aggregator = FirstValueIntAggregator::new(2).into_typed();
1✔
427

1✔
428
        assert!(!aggregator.is_satisfied());
1✔
429

430
        aggregator.add_value(0, 2., 1);
1✔
431

1✔
432
        assert!(!aggregator.is_satisfied());
1✔
433

434
        aggregator.add_value(1, 0., 1);
1✔
435

1✔
436
        assert!(aggregator.is_satisfied());
1✔
437

438
        aggregator.add_value(1, 4., 1);
1✔
439

1✔
440
        assert!(aggregator.is_satisfied());
1✔
441
    }
1✔
442

443
    #[test]
1✔
444
    fn nulls() {
1✔
445
        let mut aggregator = FirstValueIntAggregator::new(2).into_typed();
1✔
446

1✔
447
        assert!(!aggregator.is_satisfied());
1✔
448

449
        aggregator.add_null(0);
1✔
450
        aggregator.add_null(1);
1✔
451

1✔
452
        assert!(aggregator.is_satisfied());
1✔
453

454
        assert_eq!(aggregator.nulls(), &[true, true]);
1✔
455
    }
1✔
456

457
    #[test]
1✔
458
    fn value_then_null() {
1✔
459
        let mut aggregator = FirstValueIntAggregator::new(1).into_typed();
1✔
460

1✔
461
        aggregator.add_value(0, 1337, 1);
1✔
462
        aggregator.add_null(0);
1✔
463

1✔
464
        assert_eq!(
1✔
465
            aggregator.into_data(),
1✔
466
            FeatureData::NullableInt(vec![Some(1337)])
1✔
467
        );
1✔
468
    }
1✔
469
}
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