• 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

97.78
/operators/src/processing/raster_vector_join/util.rs
1
use std::iter::Enumerate;
2

3
use geoengine_datatypes::collections::{FeatureCollection, GeometryRandomAccess};
4
use geoengine_datatypes::primitives::{Geometry, MultiPoint, MultiPointAccess, MultiPolygon};
5
use geoengine_datatypes::raster::{GridContains, GridShapeAccess};
6
use geoengine_datatypes::{
7
    primitives::TimeInterval,
8
    raster::{GridIdx2D, Pixel, RasterTile2D},
9
};
10

11
use crate::processing::point_in_polygon::PointInPolygonTesterWithCollection;
12

13
/// A `FeatureTimeSpan` combines a `TimeInterval` with a set of features it spans over.
14
/// Thus, it is used in combination with a `FeatureCollection`.
15
///
16
/// Both, `feature_index_start` and `feature_index_end` are inclusive.
17
///
18
#[derive(Debug, Clone, PartialEq, Eq)]
3✔
19
pub struct FeatureTimeSpan {
20
    pub feature_index_start: usize,
21
    pub feature_index_end: usize,
22
    pub time_interval: TimeInterval,
23
}
24

25
/// An iterator over `FeatureTimeSpan`s of `TimeInterval`s of a sorted `FeatureCollection`.
26
///
27
/// The `TimeInterval`s must be sorted ascending in order for the iterator to work.
28
///
29
pub struct FeatureTimeSpanIter<'c> {
30
    time_intervals: Enumerate<std::slice::Iter<'c, TimeInterval>>,
31
    current_time_span: Option<FeatureTimeSpan>,
32
}
33

34
impl<'c> FeatureTimeSpanIter<'c> {
35
    pub fn new(time_intervals: &'c [TimeInterval]) -> Self {
9✔
36
        Self {
9✔
37
            time_intervals: time_intervals.iter().enumerate(),
9✔
38
            current_time_span: None,
9✔
39
        }
9✔
40
    }
9✔
41
}
42

43
impl<'c> Iterator for FeatureTimeSpanIter<'c> {
44
    type Item = FeatureTimeSpan;
45

46
    fn next(&mut self) -> Option<Self::Item> {
16✔
47
        for (idx, time_interval) in &mut self.time_intervals {
47✔
48
            match self.current_time_span.take() {
33✔
49
                None => {
9✔
50
                    // nothing there yet? store it as the beginning
9✔
51

9✔
52
                    self.current_time_span = Some(FeatureTimeSpan {
9✔
53
                        feature_index_start: idx,
9✔
54
                        feature_index_end: idx,
9✔
55
                        time_interval: *time_interval,
9✔
56
                    });
9✔
57
                }
9✔
58
                Some(mut time_span) => {
24✔
59
                    if let Ok(combined_time_interval) = time_span.time_interval.union(time_interval)
24✔
60
                    {
22✔
61
                        // merge time intervals if possible
22✔
62

22✔
63
                        time_span.time_interval = combined_time_interval;
22✔
64
                        time_span.feature_index_end = idx;
22✔
65

22✔
66
                        self.current_time_span = Some(time_span);
22✔
67
                    } else {
22✔
68
                        // store current time interval for next span
69

70
                        self.current_time_span = Some(FeatureTimeSpan {
2✔
71
                            feature_index_start: idx,
2✔
72
                            feature_index_end: idx,
2✔
73
                            time_interval: *time_interval,
2✔
74
                        });
2✔
75

2✔
76
                        return Some(time_span);
2✔
77
                    }
78
                }
79
            }
80
        }
81

82
        // output last time span or `None`
83
        self.current_time_span.take()
14✔
84
    }
16✔
85
}
86

87
/// To calculate the pixels covered by a feature's geometries, a calculator is first initialized with a
88
/// collection of the given type `G` and can then be queried for each feature by the feature's index
89
pub trait CoveredPixels<G: Geometry>: Send + Sync {
90
    /// initialize the calculator with the given `collection`, potentially doing some  (expensive) precalculations
91
    fn initialize(collection: FeatureCollection<G>) -> Self;
92

93
    /// return the pixels of the given `raster` that are covered by the geometries of the feature at the
94
    /// `feature_index`
95
    fn covered_pixels<P: Pixel>(
96
        &self,
97
        feature_index: usize,
98
        raster: &RasterTile2D<P>,
99
    ) -> Vec<GridIdx2D>;
100

101
    fn collection_ref(&self) -> &FeatureCollection<G>;
102

103
    fn collection(self) -> FeatureCollection<G>;
104
}
105

106
pub struct MultiPointCoveredPixels {
107
    collection: FeatureCollection<MultiPoint>,
108
}
109

110
impl CoveredPixels<MultiPoint> for MultiPointCoveredPixels {
111
    fn initialize(collection: FeatureCollection<MultiPoint>) -> Self {
14✔
112
        Self { collection }
14✔
113
    }
14✔
114

115
    fn covered_pixels<P: Pixel>(
591✔
116
        &self,
591✔
117
        feature_index: usize,
591✔
118
        raster: &RasterTile2D<P>,
591✔
119
    ) -> Vec<GridIdx2D> {
591✔
120
        let geo_transform = raster.tile_information().tile_geo_transform();
591✔
121
        if let Some(geometry) = self.collection.geometry_at(feature_index) {
591✔
122
            return geometry
591✔
123
                .points()
591✔
124
                .iter()
591✔
125
                .map(|c| geo_transform.coordinate_to_grid_idx_2d(*c))
608✔
126
                .filter(|idx| raster.grid_shape().contains(idx))
608✔
127
                .collect();
591✔
128
        }
×
129

×
130
        vec![]
×
131
    }
591✔
132

133
    fn collection_ref(&self) -> &FeatureCollection<MultiPoint> {
16✔
134
        &self.collection
16✔
135
    }
16✔
136

137
    fn collection(self) -> FeatureCollection<MultiPoint> {
7✔
138
        self.collection
7✔
139
    }
7✔
140
}
141

142
pub struct MultiPolygonCoveredPixels {
143
    tester_with_collection: PointInPolygonTesterWithCollection,
144
}
145

146
impl CoveredPixels<MultiPolygon> for MultiPolygonCoveredPixels {
147
    fn initialize(collection: FeatureCollection<MultiPolygon>) -> Self {
3✔
148
        Self {
3✔
149
            tester_with_collection: PointInPolygonTesterWithCollection::new(collection), // TODO: parallelize
3✔
150
        }
3✔
151
    }
3✔
152

153
    fn covered_pixels<P: Pixel>(
8✔
154
        &self,
8✔
155
        feature_index: usize,
8✔
156
        raster: &RasterTile2D<P>,
8✔
157
    ) -> Vec<GridIdx2D> {
8✔
158
        let geo_transform = raster.tile_information().tile_geo_transform();
8✔
159

8✔
160
        let [height, width] = raster.grid_shape_array();
8✔
161

8✔
162
        let tester = self.tester_with_collection.tester();
8✔
163

8✔
164
        let mut pixels = vec![];
8✔
165
        for row in 0..height {
24✔
166
            for col in 0..width {
48✔
167
                let idx = [row as isize, col as isize].into();
48✔
168
                let coordinate = geo_transform.grid_idx_to_pixel_upper_left_coordinate_2d(idx);
48✔
169

48✔
170
                if tester.multi_polygon_contains_coordinate(coordinate, feature_index) {
48✔
171
                    pixels.push(idx);
16✔
172
                }
32✔
173
            }
174
        }
175

176
        pixels
8✔
177
    }
8✔
178

179
    fn collection_ref(&self) -> &FeatureCollection<MultiPolygon> {
5✔
180
        self.tester_with_collection.collection()
5✔
181
    }
5✔
182

183
    fn collection(self) -> FeatureCollection<MultiPolygon> {
2✔
184
        self.tester_with_collection.into()
2✔
185
    }
2✔
186
}
187

188
/// Creates a new calculator for for pixels covered by a given `feature_collection`'s geometries.
189
pub trait PixelCoverCreator<G: Geometry> {
190
    type C: CoveredPixels<G>;
191

192
    fn create_covered_pixels(self) -> Self::C;
193
}
194

195
impl PixelCoverCreator<MultiPoint> for FeatureCollection<MultiPoint> {
196
    type C = MultiPointCoveredPixels;
197

198
    fn create_covered_pixels(self) -> Self::C {
14✔
199
        MultiPointCoveredPixels::initialize(self)
14✔
200
    }
14✔
201
}
202

203
impl PixelCoverCreator<MultiPolygon> for FeatureCollection<MultiPolygon> {
204
    type C = MultiPolygonCoveredPixels;
205

206
    fn create_covered_pixels(self) -> Self::C {
3✔
207
        MultiPolygonCoveredPixels::initialize(self)
3✔
208
    }
3✔
209
}
210

211
#[cfg(test)]
212
mod tests {
213
    use super::*;
214

215
    #[test]
1✔
216
    fn time_spans() {
1✔
217
        let time_spans = FeatureTimeSpanIter::new(&[
1✔
218
            TimeInterval::new_unchecked(0, 4),
1✔
219
            TimeInterval::new_unchecked(2, 6),
1✔
220
            TimeInterval::new_unchecked(7, 9),
1✔
221
            TimeInterval::new_unchecked(9, 11),
1✔
222
            TimeInterval::new_unchecked(13, 14),
1✔
223
        ])
1✔
224
        .collect::<Vec<_>>();
1✔
225

1✔
226
        assert_eq!(
1✔
227
            time_spans,
1✔
228
            vec![
1✔
229
                FeatureTimeSpan {
1✔
230
                    feature_index_start: 0,
1✔
231
                    feature_index_end: 1,
1✔
232
                    time_interval: TimeInterval::new_unchecked(0, 6)
1✔
233
                },
1✔
234
                FeatureTimeSpan {
1✔
235
                    feature_index_start: 2,
1✔
236
                    feature_index_end: 3,
1✔
237
                    time_interval: TimeInterval::new_unchecked(7, 11)
1✔
238
                },
1✔
239
                FeatureTimeSpan {
1✔
240
                    feature_index_start: 4,
1✔
241
                    feature_index_end: 4,
1✔
242
                    time_interval: TimeInterval::new_unchecked(13, 14)
1✔
243
                }
1✔
244
            ]
1✔
245
        );
1✔
246
    }
1✔
247
}
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