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

geo-engine / geoengine / 19438548963

17 Nov 2025 05:27PM UTC coverage: 88.512%. First build
19438548963

Pull #1083

github

web-flow
Merge c3ef76798 into f0e6b6470
Pull Request #1083: feat: new gdal source workflow optimization

6329 of 7650 new or added lines in 78 files covered. (82.73%)

116305 of 131400 relevant lines covered (88.51%)

494085.33 hits per line

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

85.51
/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
    optimization::OptimizationError,
9
    util::Result,
10
};
11
use async_trait::async_trait;
12
use futures::{StreamExt, TryStreamExt, stream::BoxStream};
13
use geoengine_datatypes::{
14
    collections::{
15
        FeatureCollection, GeoFeatureCollectionModifications, IntoGeometryIterator, VectorDataType,
16
    },
17
    error::{BoxedResultExt, ErrorSource},
18
    primitives::{
19
        BoundingBox2D, ColumnSelection, Geometry, MultiLineString, MultiLineStringRef,
20
        MultiPolygon, MultiPolygonRef, SpatialResolution, VectorQueryRectangle,
21
    },
22
    util::arrow::ArrowTyped,
23
};
24
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
25
use serde::{Deserialize, Serialize};
26
use snafu::Snafu;
27

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

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

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

51
        let name = CanonicOperatorName::from(&self);
52

53
        let sources = self
54
            .sources
55
            .initialize_sources(path.clone(), context)
56
            .await?;
57
        let source = sources.vector;
58

59
        if source.result_descriptor().data_type != VectorDataType::MultiLineString
60
            && source.result_descriptor().data_type != VectorDataType::MultiPolygon
61
        {
62
            return Err(LineSimplificationError::InvalidGeometryType.into());
63
        }
64

65
        let initialized_operator = InitializedLineSimplification {
66
            name,
67
            path,
68
            result_descriptor: source.result_descriptor().clone(),
69
            source,
70
            algorithm: self.params.algorithm,
71
            epsilon: self.params.epsilon,
72
        };
73

74
        Ok(initialized_operator.boxed())
75
    }
5✔
76

77
    span_fn!(LineSimplification);
78
}
79

80
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
81
#[serde(rename_all = "camelCase")]
82
pub struct LineSimplificationParams {
83
    pub algorithm: LineSimplificationAlgorithm,
84
    /// The epsilon parameter is used to determine the maximum distance between the original and the simplified geometry.
85
    pub epsilon: f64,
86
}
87

88
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
89
#[serde(rename_all = "camelCase")]
90
pub enum LineSimplificationAlgorithm {
91
    DouglasPeucker,
92
    Visvalingam,
93
}
94

95
pub struct InitializedLineSimplification {
96
    name: CanonicOperatorName,
97
    path: WorkflowOperatorPath,
98
    result_descriptor: VectorResultDescriptor,
99
    source: Box<dyn InitializedVectorOperator>,
100
    algorithm: LineSimplificationAlgorithm,
101
    epsilon: f64,
102
}
103

104
impl InitializedVectorOperator for InitializedLineSimplification {
105
    fn result_descriptor(&self) -> &VectorResultDescriptor {
1✔
106
        &self.result_descriptor
1✔
107
    }
1✔
108

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

162
    fn canonic_name(&self) -> CanonicOperatorName {
×
163
        self.name.clone()
×
164
    }
×
165

166
    fn name(&self) -> &'static str {
×
167
        LineSimplification::TYPE_NAME
×
168
    }
×
169

170
    fn path(&self) -> WorkflowOperatorPath {
×
171
        self.path.clone()
×
172
    }
×
173

NEW
174
    fn optimize(
×
NEW
175
        &self,
×
NEW
176
        target_resolution: SpatialResolution,
×
NEW
177
    ) -> Result<Box<dyn VectorOperator>, OptimizationError> {
×
178
        Ok(LineSimplification {
NEW
179
            params: LineSimplificationParams {
×
NEW
180
                algorithm: self.algorithm,
×
NEW
181
                epsilon: self.epsilon,
×
NEW
182
            },
×
183
            sources: SingleVectorSource {
NEW
184
                vector: self.source.optimize(target_resolution)?,
×
185
            },
186
        }
NEW
187
        .boxed())
×
NEW
188
    }
×
189
}
190

191
struct LineSimplificationProcessor<P, G, A>
192
where
193
    P: VectorQueryProcessor<VectorType = FeatureCollection<G>>,
194
    G: Geometry,
195
    for<'c> FeatureCollection<G>: IntoGeometryIterator<'c>,
196
    for<'c> A: LineSimplificationAlgorithmImpl<
197
            <FeatureCollection<G> as IntoGeometryIterator<'c>>::GeometryType,
198
            G,
199
        >,
200
{
201
    source: P,
202
    _algorithm: A,
203
    epsilon: f64,
204
}
205

206
pub trait LineSimplificationAlgorithmImpl<In, Out: Geometry>: Send + Sync {
207
    fn simplify(geometry_ref: In, epsilon: f64) -> Out;
208
}
209

210
struct DouglasPeucker;
211
struct Visvalingam;
212

213
impl<'c> LineSimplificationAlgorithmImpl<MultiLineStringRef<'c>, MultiLineString>
214
    for DouglasPeucker
215
{
216
    fn simplify(geometry: MultiLineStringRef<'c>, epsilon: f64) -> MultiLineString {
2✔
217
        use geo::Simplify;
218

219
        let geo_geometry = geo::MultiLineString::<f64>::from(&geometry);
2✔
220
        let geo_geometry = geo_geometry.simplify(epsilon);
2✔
221
        geo_geometry.into()
2✔
222
    }
2✔
223
}
224

225
impl<'c> LineSimplificationAlgorithmImpl<MultiPolygonRef<'c>, MultiPolygon> for DouglasPeucker {
226
    fn simplify(geometry: MultiPolygonRef<'c>, epsilon: f64) -> MultiPolygon {
×
227
        use geo::Simplify;
228

229
        let geo_geometry = geo::MultiPolygon::<f64>::from(&geometry);
×
230
        let geo_geometry = geo_geometry.simplify(epsilon);
×
231
        geo_geometry.into()
×
232
    }
×
233
}
234

235
impl<'c> LineSimplificationAlgorithmImpl<MultiLineStringRef<'c>, MultiLineString> for Visvalingam {
236
    fn simplify(geometry: MultiLineStringRef<'c>, epsilon: f64) -> MultiLineString {
×
237
        use geo::SimplifyVwPreserve;
238

239
        let geo_geometry = geo::MultiLineString::<f64>::from(&geometry);
×
240
        let geo_geometry = geo_geometry.simplify_vw_preserve(epsilon);
×
241
        geo_geometry.into()
×
242
    }
×
243
}
244

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

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

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

268
        let simplified_geometries = collection
2✔
269
            .geometries()
2✔
270
            .into_par_iter()
2✔
271
            .map(|geometry| A::simplify(geometry, epsilon))
3✔
272
            .collect();
2✔
273

274
        Ok(collection
2✔
275
            .replace_geometries(simplified_geometries)
2✔
276
            .boxed_context(error::ErrorDuringSimplification)?)
2✔
277
    }
2✔
278
}
279

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

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

308
        let epsilon = self.epsilon;
309
        if epsilon <= 0.0 || !epsilon.is_finite() {
310
            return Err(LineSimplificationError::InvalidEpsilon.into());
311
        }
312

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

321
        Ok(simplified_chunks.boxed())
322
    }
2✔
323

324
    fn result_descriptor(&self) -> &VectorResultDescriptor {
4✔
325
        self.source.result_descriptor()
4✔
326
    }
4✔
327
}
328

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

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

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

381
        let serialized = serde_json::to_value(&operator).unwrap();
1✔
382

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

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

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

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

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

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

499
        let source = MockFeatureCollectionSource::single(collection.clone()).boxed();
1✔
500

501
        let simplification = LineSimplification {
1✔
502
            params: LineSimplificationParams {
1✔
503
                epsilon: 1.0,
1✔
504
                algorithm: LineSimplificationAlgorithm::DouglasPeucker,
1✔
505
            },
1✔
506
            sources: source.into(),
1✔
507
        }
1✔
508
        .boxed();
1✔
509

510
        let initialized = simplification
1✔
511
            .initialize(
1✔
512
                WorkflowOperatorPath::initialize_root(),
1✔
513
                &MockExecutionContext::test_default(),
1✔
514
            )
1✔
515
            .await
1✔
516
            .unwrap();
1✔
517

518
        let processor = initialized
1✔
519
            .query_processor()
1✔
520
            .unwrap()
1✔
521
            .multi_line_string()
1✔
522
            .unwrap();
1✔
523

524
        let query_rectangle = VectorQueryRectangle::new(
1✔
525
            BoundingBox2D::new((0., 0.).into(), (4., 4.).into()).unwrap(),
1✔
526
            TimeInterval::default(),
1✔
527
            ColumnSelection::all(),
1✔
528
        );
529

530
        let exe_ctx = MockExecutionContext::test_default();
1✔
531
        let query_ctx = exe_ctx.mock_query_context_test_default();
1✔
532

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

535
        let collections: Vec<MultiLineStringCollection> =
1✔
536
            stream.map(Result::unwrap).collect().await;
1✔
537

538
        assert_eq!(collections.len(), 1);
1✔
539

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

560
        assert!(collections[0].chunks_equal_ignoring_cache_hint(&expected));
1✔
561
    }
1✔
562

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

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

627
        assert_eq!(
1✔
628
            simplification.result_descriptor().data_type,
1✔
629
            VectorDataType::MultiPolygon
630
        );
631

632
        let query_processor = simplification
1✔
633
            .query_processor()
1✔
634
            .unwrap()
1✔
635
            .multi_polygon()
1✔
636
            .unwrap();
1✔
637

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

640
        let query_context = exe_ctx.mock_query_context_test_default();
1✔
641
        let query = query_processor
1✔
642
            .query(
1✔
643
                VectorQueryRectangle::new(query_bbox, Default::default(), ColumnSelection::all()),
1✔
644
                &query_context,
1✔
645
            )
1✔
646
            .await
1✔
647
            .unwrap();
1✔
648

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

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

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

© 2026 Coveralls, Inc