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

geo-engine / geoengine / 12469296660

23 Dec 2024 03:15PM UTC coverage: 90.56% (-0.1%) from 90.695%
12469296660

push

github

web-flow
Merge pull request #998 from geo-engine/quota_log_wip

Quota and Data usage Logging

859 of 1214 new or added lines in 66 files covered. (70.76%)

3 existing lines in 2 files now uncovered.

133923 of 147883 relevant lines covered (90.56%)

54439.32 hits per line

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

88.81
/operators/src/processing/line_simplification.rs
1
use crate::{
2
    engine::{
3
        CanonicOperatorName, ExecutionContext, InitializedSources, InitializedVectorOperator,
4
        Operator, OperatorName, QueryContext, QueryProcessor, SingleVectorSource,
5
        TypedVectorQueryProcessor, VectorOperator, VectorQueryProcessor, VectorResultDescriptor,
6
        WorkflowOperatorPath,
7
    },
8
    util::Result,
9
};
10
use async_trait::async_trait;
11
use futures::{stream::BoxStream, StreamExt, TryStreamExt};
12
use geoengine_datatypes::{
13
    collections::{
14
        FeatureCollection, GeoFeatureCollectionModifications, IntoGeometryIterator, VectorDataType,
15
    },
16
    error::{BoxedResultExt, ErrorSource},
17
    primitives::{
18
        BoundingBox2D, ColumnSelection, Geometry, MultiLineString, MultiLineStringRef,
19
        MultiPolygon, MultiPolygonRef, SpatialResolution, VectorQueryRectangle,
20
    },
21
    util::arrow::ArrowTyped,
22
};
23
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
24
use serde::{Deserialize, Serialize};
25
use snafu::Snafu;
26

27
/// A `LineSimplification` operator simplifies the geometry of a `Vector` by removing vertices.
28
///
29
/// The simplification is performed on the geometry of the `Vector` and not on the data.
30
/// The `LineSimplification` operator is only available for (multi-)lines or (multi-)polygons.
31
///
32
pub type LineSimplification = Operator<LineSimplificationParams, SingleVectorSource>;
33

34
impl OperatorName for LineSimplification {
35
    const TYPE_NAME: &'static str = "LineSimplification";
36
}
37

38
#[typetag::serde]
1✔
39
#[async_trait]
40
impl VectorOperator for LineSimplification {
41
    async fn _initialize(
42
        self: Box<Self>,
43
        path: WorkflowOperatorPath,
44
        context: &dyn ExecutionContext,
45
    ) -> Result<Box<dyn InitializedVectorOperator>> {
5✔
46
        if self
5✔
47
            .params
5✔
48
            .epsilon
5✔
49
            .map_or(false, |e| !e.is_finite() || e <= 0.0)
5✔
50
        {
51
            return Err(LineSimplificationError::InvalidEpsilon.into());
2✔
52
        }
3✔
53

3✔
54
        let name = CanonicOperatorName::from(&self);
3✔
55

56
        let sources = self
3✔
57
            .sources
3✔
58
            .initialize_sources(path.clone(), context)
3✔
59
            .await?;
3✔
60
        let source = sources.vector;
3✔
61

3✔
62
        if source.result_descriptor().data_type != VectorDataType::MultiLineString
3✔
63
            && source.result_descriptor().data_type != VectorDataType::MultiPolygon
2✔
64
        {
65
            return Err(LineSimplificationError::InvalidGeometryType.into());
1✔
66
        }
2✔
67

2✔
68
        let initialized_operator = InitializedLineSimplification {
2✔
69
            name,
2✔
70
            path,
2✔
71
            result_descriptor: source.result_descriptor().clone(),
2✔
72
            source,
2✔
73
            algorithm: self.params.algorithm,
2✔
74
            epsilon: self.params.epsilon,
2✔
75
        };
2✔
76

2✔
77
        Ok(initialized_operator.boxed())
2✔
78
    }
10✔
79

80
    span_fn!(LineSimplification);
81
}
82

83
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
84
#[serde(rename_all = "camelCase")]
85
pub struct LineSimplificationParams {
86
    pub algorithm: LineSimplificationAlgorithm,
87
    /// The epsilon parameter is used to determine the maximum distance between the original and the simplified geometry.
88
    /// If `None` is provided, the epsilon is derived by the query's [`SpatialResolution`].
89
    pub epsilon: Option<f64>,
90
}
91

92
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
93
#[serde(rename_all = "camelCase")]
94
pub enum LineSimplificationAlgorithm {
95
    DouglasPeucker,
96
    Visvalingam,
97
}
98

99
pub struct InitializedLineSimplification {
100
    name: CanonicOperatorName,
101
    path: WorkflowOperatorPath,
102
    result_descriptor: VectorResultDescriptor,
103
    source: Box<dyn InitializedVectorOperator>,
104
    algorithm: LineSimplificationAlgorithm,
105
    epsilon: Option<f64>,
106
}
107

108
impl InitializedVectorOperator for InitializedLineSimplification {
109
    fn result_descriptor(&self) -> &VectorResultDescriptor {
1✔
110
        &self.result_descriptor
1✔
111
    }
1✔
112

113
    fn query_processor(&self) -> Result<TypedVectorQueryProcessor> {
2✔
114
        match (self.source.query_processor()?, self.algorithm) {
2✔
115
            (
116
                TypedVectorQueryProcessor::Data(..) | TypedVectorQueryProcessor::MultiPoint(..),
117
                _,
118
            ) => Err(LineSimplificationError::InvalidGeometryType.into()),
×
119
            (
120
                TypedVectorQueryProcessor::MultiLineString(source),
1✔
121
                LineSimplificationAlgorithm::DouglasPeucker,
1✔
122
            ) => Ok(TypedVectorQueryProcessor::MultiLineString(
1✔
123
                LineSimplificationProcessor {
1✔
124
                    source,
1✔
125
                    _algorithm: DouglasPeucker,
1✔
126
                    epsilon: self.epsilon,
1✔
127
                }
1✔
128
                .boxed(),
1✔
129
            )),
1✔
130
            (
131
                TypedVectorQueryProcessor::MultiLineString(source),
×
132
                LineSimplificationAlgorithm::Visvalingam,
×
133
            ) => Ok(TypedVectorQueryProcessor::MultiLineString(
×
134
                LineSimplificationProcessor {
×
135
                    source,
×
136
                    _algorithm: Visvalingam,
×
137
                    epsilon: self.epsilon,
×
138
                }
×
139
                .boxed(),
×
140
            )),
×
141
            (
142
                TypedVectorQueryProcessor::MultiPolygon(source),
×
143
                LineSimplificationAlgorithm::DouglasPeucker,
×
144
            ) => Ok(TypedVectorQueryProcessor::MultiPolygon(
×
145
                LineSimplificationProcessor {
×
146
                    source,
×
147
                    _algorithm: DouglasPeucker,
×
148
                    epsilon: self.epsilon,
×
149
                }
×
150
                .boxed(),
×
151
            )),
×
152
            (
153
                TypedVectorQueryProcessor::MultiPolygon(source),
1✔
154
                LineSimplificationAlgorithm::Visvalingam,
1✔
155
            ) => Ok(TypedVectorQueryProcessor::MultiPolygon(
1✔
156
                LineSimplificationProcessor {
1✔
157
                    source,
1✔
158
                    _algorithm: Visvalingam,
1✔
159
                    epsilon: self.epsilon,
1✔
160
                }
1✔
161
                .boxed(),
1✔
162
            )),
1✔
163
        }
164
    }
2✔
165

166
    fn canonic_name(&self) -> CanonicOperatorName {
×
167
        self.name.clone()
×
168
    }
×
169

NEW
170
    fn name(&self) -> &'static str {
×
NEW
171
        LineSimplification::TYPE_NAME
×
NEW
172
    }
×
173

NEW
174
    fn path(&self) -> WorkflowOperatorPath {
×
NEW
175
        self.path.clone()
×
NEW
176
    }
×
177
}
178

179
struct LineSimplificationProcessor<P, G, A>
180
where
181
    P: VectorQueryProcessor<VectorType = FeatureCollection<G>>,
182
    G: Geometry,
183
    for<'c> FeatureCollection<G>: IntoGeometryIterator<'c>,
184
    for<'c> A: LineSimplificationAlgorithmImpl<
185
        <FeatureCollection<G> as IntoGeometryIterator<'c>>::GeometryType,
186
        G,
187
    >,
188
{
189
    source: P,
190
    _algorithm: A,
191
    epsilon: Option<f64>,
192
}
193

194
pub trait LineSimplificationAlgorithmImpl<In, Out: Geometry>: Send + Sync {
195
    fn simplify(geometry_ref: In, epsilon: f64) -> Out;
196

197
    fn derive_epsilon(spatial_resolution: SpatialResolution) -> f64 {
×
198
        f64::sqrt(spatial_resolution.x.powi(2) + spatial_resolution.y.powi(2)) / f64::sqrt(2.)
×
199
    }
×
200
}
201

202
struct DouglasPeucker;
203
struct Visvalingam;
204

205
impl<'c> LineSimplificationAlgorithmImpl<MultiLineStringRef<'c>, MultiLineString>
206
    for DouglasPeucker
207
{
208
    fn simplify(geometry: MultiLineStringRef<'c>, epsilon: f64) -> MultiLineString {
2✔
209
        use geo::Simplify;
210

211
        let geo_geometry = geo::MultiLineString::<f64>::from(&geometry);
2✔
212
        let geo_geometry = geo_geometry.simplify(&epsilon);
2✔
213
        geo_geometry.into()
2✔
214
    }
2✔
215
}
216

217
impl<'c> LineSimplificationAlgorithmImpl<MultiPolygonRef<'c>, MultiPolygon> for DouglasPeucker {
218
    fn simplify(geometry: MultiPolygonRef<'c>, epsilon: f64) -> MultiPolygon {
×
219
        use geo::Simplify;
220

221
        let geo_geometry = geo::MultiPolygon::<f64>::from(&geometry);
×
222
        let geo_geometry = geo_geometry.simplify(&epsilon);
×
223
        geo_geometry.into()
×
224
    }
×
225
}
226

227
impl<'c> LineSimplificationAlgorithmImpl<MultiLineStringRef<'c>, MultiLineString> for Visvalingam {
228
    fn simplify(geometry: MultiLineStringRef<'c>, epsilon: f64) -> MultiLineString {
×
229
        use geo::SimplifyVwPreserve;
230

231
        let geo_geometry = geo::MultiLineString::<f64>::from(&geometry);
×
232
        let geo_geometry = geo_geometry.simplify_vw_preserve(&epsilon);
×
233
        geo_geometry.into()
×
234
    }
×
235

236
    fn derive_epsilon(spatial_resolution: SpatialResolution) -> f64 {
×
237
        // for visvalingam, the epsilon is squared since it reflects some triangle area
×
238
        // this is a heuristic, though
×
239
        spatial_resolution.x * spatial_resolution.y
×
240
    }
×
241
}
242

243
impl<'c> LineSimplificationAlgorithmImpl<MultiPolygonRef<'c>, MultiPolygon> for Visvalingam {
244
    fn simplify(geometry: MultiPolygonRef<'c>, epsilon: f64) -> MultiPolygon {
1✔
245
        use geo::SimplifyVwPreserve;
246

247
        let geo_geometry = geo::MultiPolygon::<f64>::from(&geometry);
1✔
248
        let geo_geometry = geo_geometry.simplify_vw_preserve(&epsilon);
1✔
249
        geo_geometry.into()
1✔
250
    }
1✔
251

252
    fn derive_epsilon(spatial_resolution: SpatialResolution) -> f64 {
1✔
253
        // for visvalingam, the epsilon is squared since it reflects some triangle area
1✔
254
        // this is a heuristic, though
1✔
255
        spatial_resolution.x * spatial_resolution.y
1✔
256
    }
1✔
257
}
258

259
impl<P, G, A> LineSimplificationProcessor<P, G, A>
260
where
261
    P: VectorQueryProcessor<VectorType = FeatureCollection<G>>,
262
    G: Geometry,
263
    for<'c> FeatureCollection<G>: IntoGeometryIterator<'c> + GeoFeatureCollectionModifications<G>,
264
    for<'c> A: LineSimplificationAlgorithmImpl<
265
        <FeatureCollection<G> as IntoGeometryIterator<'c>>::GeometryType,
266
        G,
267
    >,
268
{
269
    fn simplify(collection: &FeatureCollection<G>, epsilon: f64) -> Result<FeatureCollection<G>> {
2✔
270
        // TODO: chunk within parallelization to reduce overhead if necessary
2✔
271

2✔
272
        let simplified_geometries = collection
2✔
273
            .geometries()
2✔
274
            .into_par_iter()
2✔
275
            .map(|geometry| A::simplify(geometry, epsilon))
3✔
276
            .collect();
2✔
277

2✔
278
        Ok(collection
2✔
279
            .replace_geometries(simplified_geometries)
2✔
280
            .boxed_context(error::ErrorDuringSimplification)?)
2✔
281
    }
2✔
282
}
283

284
#[async_trait]
285
impl<P, G, A> QueryProcessor for LineSimplificationProcessor<P, G, A>
286
where
287
    P: QueryProcessor<
288
        Output = FeatureCollection<G>,
289
        SpatialBounds = BoundingBox2D,
290
        Selection = ColumnSelection,
291
        ResultDescription = VectorResultDescriptor,
292
    >,
293
    G: Geometry + ArrowTyped + 'static,
294
    for<'c> FeatureCollection<G>: IntoGeometryIterator<'c> + GeoFeatureCollectionModifications<G>,
295
    for<'c> A: LineSimplificationAlgorithmImpl<
296
        <FeatureCollection<G> as IntoGeometryIterator<'c>>::GeometryType,
297
        G,
298
    >,
299
{
300
    type Output = FeatureCollection<G>;
301
    type SpatialBounds = BoundingBox2D;
302
    type Selection = ColumnSelection;
303
    type ResultDescription = VectorResultDescriptor;
304

305
    async fn _query<'a>(
306
        &'a self,
307
        query: VectorQueryRectangle,
308
        ctx: &'a dyn QueryContext,
309
    ) -> Result<BoxStream<'a, Result<Self::Output>>> {
2✔
310
        let chunks = self.source.query(query.clone(), ctx).await?;
2✔
311

312
        let epsilon = self
2✔
313
            .epsilon
2✔
314
            .unwrap_or_else(|| A::derive_epsilon(query.spatial_resolution));
2✔
315

2✔
316
        let simplified_chunks = chunks.and_then(move |chunk| async move {
2✔
317
            crate::util::spawn_blocking_with_thread_pool(ctx.thread_pool().clone(), move || {
2✔
318
                Self::simplify(&chunk, epsilon)
2✔
319
            })
2✔
320
            .await
2✔
321
            .boxed_context(error::UnexpectedInternal)?
2✔
322
        });
4✔
323

2✔
324
        Ok(simplified_chunks.boxed())
2✔
325
    }
4✔
326

327
    fn result_descriptor(&self) -> &VectorResultDescriptor {
4✔
328
        self.source.result_descriptor()
4✔
329
    }
4✔
330
}
331

332
#[derive(Debug, Snafu)]
×
333
#[snafu(visibility(pub(crate)), context(suffix(false)), module(error))]
334
pub enum LineSimplificationError {
335
    #[snafu(display("`epsilon` parameter must be greater than 0"))]
336
    InvalidEpsilon,
337
    #[snafu(display("Geometry must be of type `MultiLineString` or `MultiPolygon`"))]
338
    InvalidGeometryType,
339
    #[snafu(display("Error during simplification of geometry: {}", source))]
340
    ErrorDuringSimplification { source: Box<dyn ErrorSource> },
341
    #[snafu(display("Unexpected internal error: {}", source))]
342
    UnexpectedInternal { source: Box<dyn ErrorSource> },
343
}
344

345
#[cfg(test)]
346
mod tests {
347
    use super::*;
348
    use crate::{
349
        engine::{MockExecutionContext, MockQueryContext, StaticMetaData},
350
        mock::MockFeatureCollectionSource,
351
        source::{
352
            OgrSource, OgrSourceColumnSpec, OgrSourceDataset, OgrSourceDatasetTimeType,
353
            OgrSourceErrorSpec, OgrSourceParameters,
354
        },
355
    };
356
    use geoengine_datatypes::{
357
        collections::{
358
            ChunksEqualIgnoringCacheHint, FeatureCollectionInfos, GeometryCollection,
359
            MultiLineStringCollection, MultiPointCollection, MultiPolygonCollection,
360
        },
361
        dataset::{DataId, DatasetId, NamedData},
362
        primitives::{
363
            FeatureData, MultiLineString, MultiPoint, TimeInterval, {CacheHint, CacheTtlSeconds},
364
        },
365
        spatial_reference::SpatialReference,
366
        test_data,
367
        util::{test::TestDefault, Identifier},
368
    };
369

370
    #[tokio::test]
371
    async fn test_ser_de() {
1✔
372
        let operator = LineSimplification {
1✔
373
            params: LineSimplificationParams {
1✔
374
                epsilon: Some(1.0),
1✔
375
                algorithm: LineSimplificationAlgorithm::DouglasPeucker,
1✔
376
            },
1✔
377
            sources: MockFeatureCollectionSource::<MultiPolygon>::multiple(vec![])
1✔
378
                .boxed()
1✔
379
                .into(),
1✔
380
        }
1✔
381
        .boxed();
1✔
382

1✔
383
        let serialized = serde_json::to_value(&operator).unwrap();
1✔
384

1✔
385
        assert_eq!(
1✔
386
            serialized,
1✔
387
            serde_json::json!({
1✔
388
                "type": "LineSimplification",
1✔
389
                "params": {
1✔
390
                    "epsilon": 1.0,
1✔
391
                    "algorithm": "douglasPeucker",
1✔
392
                },
1✔
393
                "sources": {
1✔
394
                    "vector": {
1✔
395
                        "type": "MockFeatureCollectionSourceMultiPolygon",
1✔
396
                        "params": {
1✔
397
                            "collections": [],
1✔
398
                            "spatialReference": "EPSG:4326",
1✔
399
                            "measurements": null,
1✔
400
                        }
1✔
401
                    }
1✔
402
                },
1✔
403
            })
1✔
404
        );
1✔
405

1✔
406
        let _operator: Box<dyn VectorOperator> = serde_json::from_value(serialized).unwrap();
1✔
407
    }
1✔
408

409
    #[tokio::test]
410
    async fn test_errors() {
1✔
411
        // zero epsilon
1✔
412
        assert!(LineSimplification {
1✔
413
            params: LineSimplificationParams {
1✔
414
                epsilon: Some(0.0),
1✔
415
                algorithm: LineSimplificationAlgorithm::DouglasPeucker,
1✔
416
            },
1✔
417
            sources: MockFeatureCollectionSource::<MultiPolygon>::single(
1✔
418
                MultiPolygonCollection::empty()
1✔
419
            )
1✔
420
            .boxed()
1✔
421
            .into(),
1✔
422
        }
1✔
423
        .boxed()
1✔
424
        .initialize(
1✔
425
            WorkflowOperatorPath::initialize_root(),
1✔
426
            &MockExecutionContext::test_default()
1✔
427
        )
1✔
428
        .await
1✔
429
        .is_err());
1✔
430

1✔
431
        // invalid epsilon
1✔
432
        assert!(LineSimplification {
1✔
433
            params: LineSimplificationParams {
1✔
434
                epsilon: Some(f64::NAN),
1✔
435
                algorithm: LineSimplificationAlgorithm::Visvalingam,
1✔
436
            },
1✔
437
            sources: MockFeatureCollectionSource::<MultiPolygon>::single(
1✔
438
                MultiPolygonCollection::empty()
1✔
439
            )
1✔
440
            .boxed()
1✔
441
            .into(),
1✔
442
        }
1✔
443
        .boxed()
1✔
444
        .initialize(
1✔
445
            WorkflowOperatorPath::initialize_root(),
1✔
446
            &MockExecutionContext::test_default()
1✔
447
        )
1✔
448
        .await
1✔
449
        .is_err());
1✔
450

1✔
451
        // not lines or polygons
1✔
452
        assert!(LineSimplification {
1✔
453
            params: LineSimplificationParams {
1✔
454
                epsilon: None,
1✔
455
                algorithm: LineSimplificationAlgorithm::DouglasPeucker,
1✔
456
            },
1✔
457
            sources: MockFeatureCollectionSource::<MultiPoint>::single(
1✔
458
                MultiPointCollection::empty()
1✔
459
            )
1✔
460
            .boxed()
1✔
461
            .into(),
1✔
462
        }
1✔
463
        .boxed()
1✔
464
        .initialize(
1✔
465
            WorkflowOperatorPath::initialize_root(),
1✔
466
            &MockExecutionContext::test_default()
1✔
467
        )
1✔
468
        .await
1✔
469
        .is_err());
1✔
470
    }
1✔
471

472
    #[tokio::test]
473
    async fn test_line_simplification() {
1✔
474
        let collection = MultiLineStringCollection::from_data(
1✔
475
            vec![
1✔
476
                MultiLineString::new(vec![vec![
1✔
477
                    (0.0, 0.0).into(),
1✔
478
                    (5.0, 4.0).into(),
1✔
479
                    (11.0, 5.5).into(),
1✔
480
                    (17.3, 3.2).into(),
1✔
481
                    (27.8, 0.1).into(),
1✔
482
                ]])
1✔
483
                .unwrap(),
1✔
484
                MultiLineString::new(vec![vec![(0.0, 0.0).into(), (5.0, 4.0).into()]]).unwrap(),
1✔
485
            ],
1✔
486
            vec![TimeInterval::new(0, 1).unwrap(); 2],
1✔
487
            [("foo".to_string(), FeatureData::Float(vec![0., 1.]))]
1✔
488
                .iter()
1✔
489
                .cloned()
1✔
490
                .collect(),
1✔
491
            CacheHint::default(),
1✔
492
        )
1✔
493
        .unwrap();
1✔
494

1✔
495
        let source = MockFeatureCollectionSource::single(collection.clone()).boxed();
1✔
496

1✔
497
        let simplification = LineSimplification {
1✔
498
            params: LineSimplificationParams {
1✔
499
                epsilon: Some(1.0),
1✔
500
                algorithm: LineSimplificationAlgorithm::DouglasPeucker,
1✔
501
            },
1✔
502
            sources: source.into(),
1✔
503
        }
1✔
504
        .boxed();
1✔
505

1✔
506
        let initialized = simplification
1✔
507
            .initialize(
1✔
508
                WorkflowOperatorPath::initialize_root(),
1✔
509
                &MockExecutionContext::test_default(),
1✔
510
            )
1✔
511
            .await
1✔
512
            .unwrap();
1✔
513

1✔
514
        let processor = initialized
1✔
515
            .query_processor()
1✔
516
            .unwrap()
1✔
517
            .multi_line_string()
1✔
518
            .unwrap();
1✔
519

1✔
520
        let query_rectangle = VectorQueryRectangle {
1✔
521
            spatial_bounds: BoundingBox2D::new((0., 0.).into(), (4., 4.).into()).unwrap(),
1✔
522
            time_interval: TimeInterval::default(),
1✔
523
            spatial_resolution: SpatialResolution::one(),
1✔
524
            attributes: ColumnSelection::all(),
1✔
525
        };
1✔
526

1✔
527
        let query_ctx = MockQueryContext::test_default();
1✔
528

1✔
529
        let stream = processor.query(query_rectangle, &query_ctx).await.unwrap();
1✔
530

1✔
531
        let collections: Vec<MultiLineStringCollection> =
1✔
532
            stream.map(Result::unwrap).collect().await;
1✔
533

1✔
534
        assert_eq!(collections.len(), 1);
1✔
535

1✔
536
        let expected = MultiLineStringCollection::from_data(
1✔
537
            vec![
1✔
538
                MultiLineString::new(vec![vec![
1✔
539
                    (0.0, 0.0).into(),
1✔
540
                    (5.0, 4.0).into(),
1✔
541
                    (11.0, 5.5).into(),
1✔
542
                    (27.8, 0.1).into(),
1✔
543
                ]])
1✔
544
                .unwrap(),
1✔
545
                MultiLineString::new(vec![vec![(0.0, 0.0).into(), (5.0, 4.0).into()]]).unwrap(),
1✔
546
            ],
1✔
547
            vec![TimeInterval::new(0, 1).unwrap(); 2],
1✔
548
            [("foo".to_string(), FeatureData::Float(vec![0., 1.]))]
1✔
549
                .iter()
1✔
550
                .cloned()
1✔
551
                .collect(),
1✔
552
            CacheHint::default(),
1✔
553
        )
1✔
554
        .unwrap();
1✔
555

1✔
556
        assert!(collections[0].chunks_equal_ignoring_cache_hint(&expected));
1✔
557
    }
1✔
558

559
    #[tokio::test]
560
    async fn test_polygon_simplification() {
1✔
561
        let id: DataId = DatasetId::new().into();
1✔
562
        let name = NamedData::with_system_name("polygons");
1✔
563
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
564
        exe_ctx.add_meta_data::<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>(
1✔
565
            id.clone(),
1✔
566
            name.clone(),
1✔
567
            Box::new(StaticMetaData {
1✔
568
                loading_info: OgrSourceDataset {
1✔
569
                    file_name: test_data!("vector/data/germany_polygon.gpkg").into(),
1✔
570
                    layer_name: "test_germany".to_owned(),
1✔
571
                    data_type: Some(VectorDataType::MultiPolygon),
1✔
572
                    time: OgrSourceDatasetTimeType::None,
1✔
573
                    default_geometry: None,
1✔
574
                    columns: Some(OgrSourceColumnSpec {
1✔
575
                        format_specifics: None,
1✔
576
                        x: String::new(),
1✔
577
                        y: None,
1✔
578
                        int: vec![],
1✔
579
                        float: vec![],
1✔
580
                        text: vec![],
1✔
581
                        bool: vec![],
1✔
582
                        datetime: vec![],
1✔
583
                        rename: None,
1✔
584
                    }),
1✔
585
                    force_ogr_time_filter: false,
1✔
586
                    force_ogr_spatial_filter: false,
1✔
587
                    on_error: OgrSourceErrorSpec::Abort,
1✔
588
                    sql_query: None,
1✔
589
                    attribute_query: None,
1✔
590
                    cache_ttl: CacheTtlSeconds::default(),
1✔
591
                },
1✔
592
                result_descriptor: VectorResultDescriptor {
1✔
593
                    data_type: VectorDataType::MultiPolygon,
1✔
594
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
595
                    columns: Default::default(),
1✔
596
                    time: None,
1✔
597
                    bbox: None,
1✔
598
                },
1✔
599
                phantom: Default::default(),
1✔
600
            }),
1✔
601
        );
1✔
602

1✔
603
        let simplification = LineSimplification {
1✔
604
            params: LineSimplificationParams {
1✔
605
                epsilon: None,
1✔
606
                algorithm: LineSimplificationAlgorithm::Visvalingam,
1✔
607
            },
1✔
608
            sources: OgrSource {
1✔
609
                params: OgrSourceParameters {
1✔
610
                    data: name,
1✔
611
                    attribute_projection: None,
1✔
612
                    attribute_filters: None,
1✔
613
                },
1✔
614
            }
1✔
615
            .boxed()
1✔
616
            .into(),
1✔
617
        }
1✔
618
        .boxed()
1✔
619
        .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
620
        .await
1✔
621
        .unwrap();
1✔
622

1✔
623
        assert_eq!(
1✔
624
            simplification.result_descriptor().data_type,
1✔
625
            VectorDataType::MultiPolygon
1✔
626
        );
1✔
627

1✔
628
        let query_processor = simplification
1✔
629
            .query_processor()
1✔
630
            .unwrap()
1✔
631
            .multi_polygon()
1✔
632
            .unwrap();
1✔
633

1✔
634
        let query_bbox = BoundingBox2D::new((-180.0, -90.0).into(), (180.00, 90.0).into()).unwrap();
1✔
635

1✔
636
        let query_context = MockQueryContext::test_default();
1✔
637
        let query = query_processor
1✔
638
            .query(
1✔
639
                VectorQueryRectangle {
1✔
640
                    spatial_bounds: query_bbox,
1✔
641
                    time_interval: Default::default(),
1✔
642
                    spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
643
                    attributes: ColumnSelection::all(),
1✔
644
                },
1✔
645
                &query_context,
1✔
646
            )
1✔
647
            .await
1✔
648
            .unwrap();
1✔
649

1✔
650
        let result: Vec<MultiPolygonCollection> = query.try_collect().await.unwrap();
1✔
651

1✔
652
        assert_eq!(result.len(), 1);
1✔
653
        let result = result.into_iter().next().unwrap();
1✔
654

1✔
655
        assert_eq!(result.len(), 1);
1✔
656
        assert_eq!(result.feature_offsets().len(), 2);
1✔
657
        assert_eq!(result.polygon_offsets().len(), 23);
1✔
658
        assert_eq!(result.ring_offsets().len(), 23);
1✔
659
        assert_eq!(result.coordinates().len(), 96 /* was 3027 */);
1✔
660
    }
1✔
661
}
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