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

geo-engine / geoengine / 10178074589

31 Jul 2024 09:34AM UTC coverage: 91.068% (+0.4%) from 90.682%
10178074589

push

github

web-flow
Merge pull request #973 from geo-engine/remove-XGB-update-toolchain

Remove-XGB-update-toolchain

81 of 88 new or added lines in 29 files covered. (92.05%)

456 existing lines in 119 files now uncovered.

131088 of 143945 relevant lines covered (91.07%)

53581.03 hits per line

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

93.66
/operators/src/processing/rasterization/mod.rs
1
use crate::engine::TypedVectorQueryProcessor::MultiPoint;
2
use crate::engine::{
3
    CanonicOperatorName, ExecutionContext, InitializedRasterOperator, InitializedSources,
4
    InitializedVectorOperator, Operator, OperatorName, QueryContext, QueryProcessor,
5
    RasterBandDescriptors, RasterOperator, RasterQueryProcessor, RasterResultDescriptor,
6
    SingleVectorSource, TypedRasterQueryProcessor, TypedVectorQueryProcessor, WorkflowOperatorPath,
7
};
8
use arrow::datatypes::ArrowNativeTypeOp;
9
use geoengine_datatypes::primitives::{CacheHint, ColumnSelection};
10

11
use crate::error;
12
use crate::processing::rasterization::GridOrDensity::Grid;
13
use crate::util;
14

15
use async_trait::async_trait;
16

17
use futures::stream::BoxStream;
18
use futures::{stream, StreamExt};
19
use geoengine_datatypes::collections::GeometryCollection;
20

21
use geoengine_datatypes::primitives::{
22
    AxisAlignedRectangle, BoundingBox2D, Coordinate2D, RasterQueryRectangle, SpatialPartition2D,
23
    SpatialPartitioned, SpatialResolution, VectorQueryRectangle,
24
};
25
use geoengine_datatypes::raster::{
26
    GeoTransform, Grid2D, GridOrEmpty, GridSize, GridSpaceToLinearSpace, RasterDataType,
27
    RasterTile2D, TilingSpecification,
28
};
29

30
use num_traits::FloatConst;
31
use rayon::prelude::*;
32

33
use serde::{Deserialize, Serialize};
34
use snafu::ensure;
35

36
use crate::util::{spawn_blocking, spawn_blocking_with_thread_pool};
37

38
use typetag::serde;
39

40
/// An operator that rasterizes vector data
41
pub type Rasterization = Operator<GridOrDensity, SingleVectorSource>;
42

43
impl OperatorName for Rasterization {
44
    const TYPE_NAME: &'static str = "Rasterization";
45
}
46

UNCOV
47
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
×
48
#[serde(rename_all = "camelCase")]
49
pub enum GridSizeMode {
50
    /// The spatial resolution is interpreted as a fixed size in coordinate units
51
    Fixed,
52
    /// The spatial resolution is interpreted as a multiplier for the query pixel size
53
    Relative,
54
}
55

UNCOV
56
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
×
57
#[serde(rename_all = "camelCase")]
58
#[serde(tag = "type")]
59
pub enum GridOrDensity {
60
    /// A grid which summarizes points in cells (2D histogram)
61
    Grid(GridParams),
62
    /// A heatmap calculated from a gaussian density function
63
    Density(DensityParams),
64
}
65

UNCOV
66
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
×
67
pub struct DensityParams {
68
    /// Defines the cutoff (as percentage of maximum density) down to which a point is taken
69
    /// into account for an output pixel density value
70
    cutoff: f64,
71
    /// The standard deviation parameter for the gaussian function
72
    stddev: f64,
73
}
74

UNCOV
75
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
×
76
#[serde(rename_all = "camelCase")]
77
pub struct GridParams {
78
    /// The size of grid cells, interpreted depending on the chosen grid size mode
79
    spatial_resolution: SpatialResolution,
80
    /// The origin coordinate which aligns the grid bounds
81
    origin_coordinate: Coordinate2D,
82
    /// Determines how to interpret the grid resolution
83
    grid_size_mode: GridSizeMode,
84
}
85

86
#[typetag::serde]
×
87
#[async_trait]
88
impl RasterOperator for Rasterization {
89
    async fn _initialize(
90
        self: Box<Self>,
91
        path: WorkflowOperatorPath,
92
        context: &dyn ExecutionContext,
93
    ) -> util::Result<Box<dyn InitializedRasterOperator>> {
8✔
94
        let name = CanonicOperatorName::from(&self);
8✔
95

8✔
96
        let initialized_source = self.sources.initialize_sources(path, context).await?;
8✔
97
        let vector_source = initialized_source.vector;
8✔
98
        let in_desc = vector_source.result_descriptor();
8✔
99

8✔
100
        let tiling_specification = context.tiling_specification();
8✔
101

8✔
102
        let out_desc = RasterResultDescriptor {
8✔
103
            spatial_reference: in_desc.spatial_reference,
8✔
104
            data_type: RasterDataType::F64,
8✔
105
            bbox: None,
8✔
106
            time: in_desc.time,
8✔
107
            resolution: None,
8✔
108
            bands: RasterBandDescriptors::new_single_band(),
8✔
109
        };
8✔
110

8✔
111
        match self.params {
8✔
112
            Grid(params) => Ok(InitializedGridRasterization {
8✔
113
                name,
6✔
114
                source: vector_source,
6✔
115
                result_descriptor: out_desc,
6✔
116
                spatial_resolution: params.spatial_resolution,
6✔
117
                grid_size_mode: params.grid_size_mode,
6✔
118
                tiling_specification,
6✔
119
                origin_coordinate: params.origin_coordinate,
6✔
120
            }
6✔
121
            .boxed()),
6✔
122
            GridOrDensity::Density(params) => InitializedDensityRasterization::new(
8✔
123
                name,
2✔
124
                vector_source,
2✔
125
                out_desc,
2✔
126
                tiling_specification,
2✔
127
                params.cutoff,
2✔
128
                params.stddev,
2✔
129
            )
2✔
130
            .map(InitializedRasterOperator::boxed),
2✔
131
        }
8✔
132
    }
8✔
133

134
    span_fn!(Rasterization);
135
}
136

137
pub struct InitializedGridRasterization {
138
    name: CanonicOperatorName,
139
    source: Box<dyn InitializedVectorOperator>,
140
    result_descriptor: RasterResultDescriptor,
141
    spatial_resolution: SpatialResolution,
142
    grid_size_mode: GridSizeMode,
143
    tiling_specification: TilingSpecification,
144
    origin_coordinate: Coordinate2D,
145
}
146

147
impl InitializedRasterOperator for InitializedGridRasterization {
148
    fn result_descriptor(&self) -> &RasterResultDescriptor {
×
149
        &self.result_descriptor
×
150
    }
×
151

152
    fn query_processor(&self) -> util::Result<TypedRasterQueryProcessor> {
6✔
153
        Ok(TypedRasterQueryProcessor::F64(
6✔
154
            GridRasterizationQueryProcessor {
6✔
155
                input: self.source.query_processor()?,
6✔
156
                result_descriptor: self.result_descriptor.clone(),
6✔
157
                spatial_resolution: self.spatial_resolution,
6✔
158
                grid_size_mode: self.grid_size_mode,
6✔
159
                tiling_specification: self.tiling_specification,
6✔
160
                origin_coordinate: self.origin_coordinate,
6✔
161
            }
6✔
162
            .boxed(),
6✔
163
        ))
164
    }
6✔
165

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

171
pub struct InitializedDensityRasterization {
172
    name: CanonicOperatorName,
173
    source: Box<dyn InitializedVectorOperator>,
174
    result_descriptor: RasterResultDescriptor,
175
    tiling_specification: TilingSpecification,
176
    radius: f64,
177
    stddev: f64,
178
}
179

180
impl InitializedDensityRasterization {
181
    fn new(
2✔
182
        name: CanonicOperatorName,
2✔
183
        source: Box<dyn InitializedVectorOperator>,
2✔
184
        result_descriptor: RasterResultDescriptor,
2✔
185
        tiling_specification: TilingSpecification,
2✔
186
        cutoff: f64,
2✔
187
        stddev: f64,
2✔
188
    ) -> Result<Self, error::Error> {
2✔
189
        ensure!(
2✔
190
            (0. ..1.).contains(&cutoff),
2✔
191
            error::InvalidOperatorSpec {
×
192
                reason: "The cutoff for density rasterization must be in [0, 1).".to_string()
×
193
            }
×
194
        );
195
        ensure!(
2✔
196
            stddev >= 0.,
2✔
197
            error::InvalidOperatorSpec {
×
198
                reason: "The standard deviation for density rasterization must be greater than or equal to zero."
×
199
                    .to_string()
×
200
            }
×
201
        );
202

203
        // Determine radius from cutoff percentage
204
        let radius = gaussian_inverse(cutoff * gaussian(0., stddev), stddev);
2✔
205

2✔
206
        Ok(InitializedDensityRasterization {
2✔
207
            name,
2✔
208
            source,
2✔
209
            result_descriptor,
2✔
210
            tiling_specification,
2✔
211
            radius,
2✔
212
            stddev,
2✔
213
        })
2✔
214
    }
2✔
215
}
216

217
impl InitializedRasterOperator for InitializedDensityRasterization {
218
    fn result_descriptor(&self) -> &RasterResultDescriptor {
×
219
        &self.result_descriptor
×
220
    }
×
221

222
    fn query_processor(&self) -> util::Result<TypedRasterQueryProcessor> {
2✔
223
        Ok(TypedRasterQueryProcessor::F64(
2✔
224
            DensityRasterizationQueryProcessor {
2✔
225
                result_descriptor: self.result_descriptor.clone(),
2✔
226
                input: self.source.query_processor()?,
2✔
227
                tiling_specification: self.tiling_specification,
2✔
228
                radius: self.radius,
2✔
229
                stddev: self.stddev,
2✔
230
            }
2✔
231
            .boxed(),
2✔
232
        ))
233
    }
2✔
234

235
    fn canonic_name(&self) -> CanonicOperatorName {
×
236
        self.name.clone()
×
237
    }
×
238
}
239

240
pub struct GridRasterizationQueryProcessor {
241
    input: TypedVectorQueryProcessor,
242
    result_descriptor: RasterResultDescriptor,
243
    spatial_resolution: SpatialResolution,
244
    grid_size_mode: GridSizeMode,
245
    tiling_specification: TilingSpecification,
246
    origin_coordinate: Coordinate2D,
247
}
248

249
#[async_trait]
250
impl RasterQueryProcessor for GridRasterizationQueryProcessor {
251
    type RasterType = f64;
252

253
    /// Performs a grid rasterization by first determining the grid resolution to use.
254
    /// The grid resolution is limited to the query resolution, because a finer granularity
255
    /// would not be visible in the resulting raster.
256
    /// Then, for each tile, a grid, aligned to the configured origin coordinate, is created.
257
    /// All points within the spatial bounds of the grid are queried and counted in the
258
    /// grid cells.
259
    /// Finally, the grid resolution is upsampled (if necessary) to the tile resolution.
260
    async fn raster_query<'a>(
261
        &'a self,
262
        query: RasterQueryRectangle,
263
        ctx: &'a dyn QueryContext,
264
    ) -> util::Result<BoxStream<'a, util::Result<RasterTile2D<Self::RasterType>>>> {
6✔
265
        if let MultiPoint(points_processor) = &self.input {
6✔
266
            let grid_resolution = match self.grid_size_mode {
6✔
267
                GridSizeMode::Fixed => SpatialResolution {
6✔
268
                    x: f64::max(self.spatial_resolution.x, query.spatial_resolution.x),
3✔
269
                    y: f64::max(self.spatial_resolution.y, query.spatial_resolution.y),
3✔
270
                },
3✔
271
                GridSizeMode::Relative => SpatialResolution {
6✔
272
                    x: f64::max(
3✔
273
                        self.spatial_resolution.x * query.spatial_resolution.x,
3✔
274
                        query.spatial_resolution.x,
3✔
275
                    ),
3✔
276
                    y: f64::max(
3✔
277
                        self.spatial_resolution.y * query.spatial_resolution.y,
3✔
278
                        query.spatial_resolution.y,
3✔
279
                    ),
3✔
280
                },
3✔
281
            };
6✔
282

6✔
283
            let tiling_strategy = self
6✔
284
                .tiling_specification
6✔
285
                .strategy(query.spatial_resolution.x, -query.spatial_resolution.y);
6✔
286
            let tile_shape = tiling_strategy.tile_size_in_pixels;
6✔
287

6✔
288
            let tiles = stream::iter(
6✔
289
                tiling_strategy.tile_information_iterator(query.spatial_bounds),
6✔
290
            )
6✔
291
            .then(move |tile_info| async move {
24✔
292
                let grid_spatial_bounds = tile_info
24✔
293
                    .spatial_partition()
24✔
294
                    .snap_to_grid(self.origin_coordinate, grid_resolution);
24✔
295

24✔
296
                let grid_size_x =
24✔
297
                    f64::ceil(grid_spatial_bounds.size_x() / grid_resolution.x) as usize;
24✔
298
                let grid_size_y =
24✔
299
                    f64::ceil(grid_spatial_bounds.size_y() / grid_resolution.y) as usize;
24✔
300

24✔
301
                let vector_query = VectorQueryRectangle {
24✔
302
                    spatial_bounds: grid_spatial_bounds.as_bbox(),
24✔
303
                    time_interval: query.time_interval,
24✔
304
                    spatial_resolution: grid_resolution,
24✔
305
                    attributes: ColumnSelection::all(),
24✔
306
                };
24✔
307

24✔
308
                let grid_geo_transform = GeoTransform::new(
24✔
309
                    grid_spatial_bounds.upper_left(),
24✔
310
                    grid_resolution.x,
24✔
311
                    -grid_resolution.y,
24✔
312
                );
24✔
313

24✔
314
                let mut chunks = points_processor.query(vector_query, ctx).await?;
24✔
315

24✔
316
                let mut cache_hint = CacheHint::max_duration();
24✔
317

24✔
318
                let mut grid_data = vec![0.; grid_size_x * grid_size_y];
24✔
319
                while let Some(chunk) = chunks.next().await {
48✔
320
                    let chunk = chunk?;
24✔
321

24✔
322
                    cache_hint.merge_with(&chunk.cache_hint);
24✔
323

24✔
324
                    grid_data = spawn_blocking(move || {
24✔
325
                        for &coord in chunk.coordinates() {
24✔
326
                            if !grid_spatial_bounds.contains_coordinate(&coord) {
24✔
327
                                continue;
24✔
328
                            }
24✔
329
                            let [y, x] = grid_geo_transform.coordinate_to_grid_idx_2d(coord).0;
21✔
330
                            grid_data[x as usize + y as usize * grid_size_x] += 1.;
21✔
331
                        }
24✔
332
                        grid_data
24✔
333
                    })
24✔
334
                    .await
24✔
335
                    .expect("Should only forward panics from spawned task");
24✔
336
                }
24✔
337

24✔
338
                let tile_data = spawn_blocking(move || {
24✔
339
                    let mut tile_data = Vec::with_capacity(tile_shape.number_of_elements());
24✔
340
                    for tile_y in 0..tile_shape.axis_size_y() as isize {
60✔
341
                        for tile_x in 0..tile_shape.axis_size_x() as isize {
156✔
342
                            let pixel_coordinate = tile_info
156✔
343
                                .tile_geo_transform()
156✔
344
                                .grid_idx_to_pixel_center_coordinate_2d([tile_y, tile_x].into());
156✔
345
                            if query.spatial_bounds.contains_coordinate(&pixel_coordinate) {
156✔
346
                                let [grid_y, grid_x] = grid_geo_transform
156✔
347
                                    .coordinate_to_grid_idx_2d(pixel_coordinate)
156✔
348
                                    .0;
156✔
349
                                tile_data.push(
156✔
350
                                    grid_data[grid_x as usize + grid_y as usize * grid_size_x],
156✔
351
                                );
156✔
352
                            } else {
156✔
353
                                tile_data.push(0.);
×
354
                            }
×
355
                        }
24✔
356
                    }
24✔
357
                    tile_data
24✔
358
                })
24✔
359
                .await
24✔
360
                .expect("Should only forward panics from spawned task");
24✔
361
                let tile_grid = Grid2D::new(tile_shape, tile_data)
24✔
362
                    .expect("Data vector length should match the number of pixels in the tile");
24✔
363

24✔
364
                Ok(RasterTile2D::new_with_tile_info(
24✔
365
                    query.time_interval,
24✔
366
                    tile_info,
24✔
367
                    0,
24✔
368
                    GridOrEmpty::Grid(tile_grid.into()),
24✔
369
                    cache_hint,
24✔
370
                ))
24✔
371
            });
24✔
372
            Ok(tiles.boxed())
6✔
373
        } else {
6✔
374
            Ok(generate_zeroed_tiles(self.tiling_specification, &query))
6✔
375
        }
6✔
376
    }
6✔
377

378
    fn raster_result_descriptor(&self) -> &RasterResultDescriptor {
6✔
379
        &self.result_descriptor
6✔
380
    }
6✔
381
}
382

383
pub struct DensityRasterizationQueryProcessor {
384
    input: TypedVectorQueryProcessor,
385
    result_descriptor: RasterResultDescriptor,
386
    tiling_specification: TilingSpecification,
387
    radius: f64,
388
    stddev: f64,
389
}
390

391
#[async_trait]
392
impl RasterQueryProcessor for DensityRasterizationQueryProcessor {
393
    type RasterType = f64;
394

395
    /// Performs a gaussian density rasterization.
396
    /// For each tile, the spatial bounds are extended by `radius` in x and y direction.
397
    /// All points within these extended bounds are then queried. For each point, the distance to
398
    /// its surrounding tile pixels (up to `radius` distance) is measured and input into the
399
    /// gaussian density function with the configured standard deviation. The density values
400
    /// for each pixel are then summed to result in the tile pixel grid.
401
    async fn raster_query<'a>(
402
        &'a self,
403
        query: RasterQueryRectangle,
404
        ctx: &'a dyn QueryContext,
405
    ) -> util::Result<BoxStream<'a, util::Result<RasterTile2D<Self::RasterType>>>> {
2✔
406
        if let MultiPoint(points_processor) = &self.input {
2✔
407
            let tiling_strategy = self
2✔
408
                .tiling_specification
2✔
409
                .strategy(query.spatial_resolution.x, -query.spatial_resolution.y);
2✔
410

2✔
411
            let tile_size_x = tiling_strategy.tile_size_in_pixels.axis_size_x();
2✔
412
            let tile_size_y = tiling_strategy.tile_size_in_pixels.axis_size_y();
2✔
413

2✔
414
            // Use rounding factor calculated from query resolution to extend in full pixel units
2✔
415
            let rounding_factor = f64::max(
2✔
416
                1. / query.spatial_resolution.x,
2✔
417
                1. / query.spatial_resolution.y,
2✔
418
            );
2✔
419
            let radius = (self.radius * rounding_factor).ceil() / rounding_factor;
2✔
420

2✔
421
            let tiles = stream::iter(
2✔
422
                tiling_strategy.tile_information_iterator(query.spatial_bounds),
2✔
423
            )
2✔
424
            .then(move |tile_info| async move {
4✔
425
                let tile_bounds = tile_info.spatial_partition();
4✔
426

4✔
427
                let vector_query = VectorQueryRectangle {
4✔
428
                    spatial_bounds: extended_bounding_box_from_spatial_partition(
4✔
429
                        tile_bounds,
4✔
430
                        radius,
4✔
431
                    ),
4✔
432
                    time_interval: query.time_interval,
4✔
433
                    spatial_resolution: query.spatial_resolution,
4✔
434
                    attributes: ColumnSelection::all(),
4✔
435
                };
4✔
436

4✔
437
                let tile_geo_transform = tile_info.tile_geo_transform();
4✔
438

4✔
439
                let mut chunks = points_processor.query(vector_query, ctx).await?;
4✔
440

4✔
441
                let mut tile_data = vec![0.; tile_size_x * tile_size_y];
4✔
442

4✔
443
                let mut cache_hint = CacheHint::max_duration();
4✔
444

4✔
445
                while let Some(chunk) = chunks.next().await {
8✔
446
                    let chunk = chunk?;
4✔
447

4✔
448
                    cache_hint.merge_with(&chunk.cache_hint);
4✔
449

4✔
450
                    let stddev = self.stddev;
4✔
451
                    tile_data =
4✔
452
                        spawn_blocking_with_thread_pool(ctx.thread_pool().clone(), move || {
4✔
453
                            tile_data.par_iter_mut().enumerate().for_each(
4✔
454
                                |(linear_index, pixel)| {
16✔
455
                                    let pixel_coordinate = tile_geo_transform
16✔
456
                                        .grid_idx_to_pixel_center_coordinate_2d(
16✔
457
                                            tile_geo_transform
16✔
458
                                                .spatial_to_grid_bounds(&tile_bounds)
16✔
459
                                                .grid_idx_unchecked(linear_index),
16✔
460
                                        );
16✔
461

4✔
462
                                    for coord in chunk.coordinates() {
32✔
463
                                        let distance = coord.euclidean_distance(&pixel_coordinate);
32✔
464

32✔
465
                                        if distance <= radius {
32✔
466
                                            *pixel += gaussian(distance, stddev);
20✔
467
                                        }
20✔
468
                                    }
4✔
469
                                },
16✔
470
                            );
4✔
471

4✔
472
                            tile_data
4✔
473
                        })
4✔
474
                        .await?;
4✔
475
                }
4✔
476

4✔
477
                Ok(RasterTile2D::new_with_tile_info(
4✔
478
                    query.time_interval,
4✔
479
                    tile_info,
4✔
480
                    0,
4✔
481
                    GridOrEmpty::Grid(
4✔
482
                        Grid2D::new(tiling_strategy.tile_size_in_pixels, tile_data)
4✔
483
                            .expect(
4✔
484
                                "Data vector length should match the number of pixels in the tile",
4✔
485
                            )
4✔
486
                            .into(),
4✔
487
                    ),
4✔
488
                    cache_hint,
4✔
489
                ))
4✔
490
            });
4✔
491

2✔
492
            Ok(tiles.boxed())
2✔
493
        } else {
2✔
494
            Ok(generate_zeroed_tiles(self.tiling_specification, &query))
2✔
495
        }
2✔
496
    }
2✔
497

498
    fn raster_result_descriptor(&self) -> &RasterResultDescriptor {
2✔
499
        &self.result_descriptor
2✔
500
    }
2✔
501
}
502

503
fn generate_zeroed_tiles<'a>(
×
504
    tiling_specification: TilingSpecification,
×
505
    query: &RasterQueryRectangle,
×
506
) -> BoxStream<'a, util::Result<RasterTile2D<f64>>> {
×
507
    let tiling_strategy =
×
508
        tiling_specification.strategy(query.spatial_resolution.x, -query.spatial_resolution.y);
×
509
    let tile_shape = tiling_strategy.tile_size_in_pixels;
×
510
    let time_interval = query.time_interval;
×
511

×
512
    stream::iter(
×
513
        tiling_strategy
×
514
            .tile_information_iterator(query.spatial_bounds)
×
515
            .map(move |tile_info| {
×
516
                let tile_data = vec![0.; tile_shape.number_of_elements()];
×
517
                let tile_grid = Grid2D::new(tile_shape, tile_data)
×
518
                    .expect("Data vector length should match the number of pixels in the tile");
×
519

×
520
                Ok(RasterTile2D::new_with_tile_info(
×
521
                    time_interval,
×
522
                    tile_info,
×
523
                    0,
×
524
                    GridOrEmpty::Grid(tile_grid.into()),
×
525
                    CacheHint::no_cache(),
×
526
                ))
×
527
            }),
×
528
    )
×
529
    .boxed()
×
530
}
×
531

532
fn extended_bounding_box_from_spatial_partition(
4✔
533
    spatial_partition: SpatialPartition2D,
4✔
534
    extent: f64,
4✔
535
) -> BoundingBox2D {
4✔
536
    BoundingBox2D::new_unchecked(
4✔
537
        Coordinate2D::new(
4✔
538
            spatial_partition
4✔
539
                .lower_left()
4✔
540
                .x
4✔
541
                .sub_checked(extent)
4✔
542
                .unwrap_or(f64::MIN),
4✔
543
            spatial_partition
4✔
544
                .lower_left()
4✔
545
                .y
4✔
546
                .sub_checked(extent)
4✔
547
                .unwrap_or(f64::MIN),
4✔
548
        ),
4✔
549
        Coordinate2D::new(
4✔
550
            spatial_partition
4✔
551
                .upper_right()
4✔
552
                .x
4✔
553
                .add_checked(extent)
4✔
554
                .unwrap_or(f64::MAX),
4✔
555
            spatial_partition
4✔
556
                .upper_right()
4✔
557
                .y
4✔
558
                .add_checked(extent)
4✔
559
                .unwrap_or(f64::MAX),
4✔
560
        ),
4✔
561
    )
4✔
562
}
4✔
563

564
/// Calculates the gaussian density value for
565
/// `x`, the distance from the mean and
566
/// `stddev`, the standard deviation
567
fn gaussian(x: f64, stddev: f64) -> f64 {
46✔
568
    (1. / (f64::sqrt(2. * f64::PI()) * stddev)) * f64::exp(-(x * x) / (2. * stddev * stddev))
46✔
569
}
46✔
570

571
/// The inverse function of [gaussian](gaussian)
572
fn gaussian_inverse(x: f64, stddev: f64) -> f64 {
2✔
573
    f64::sqrt(2.)
2✔
574
        * f64::sqrt(stddev * stddev * f64::ln(1. / (f64::sqrt(2. * f64::PI()) * stddev * x)))
2✔
575
}
2✔
576

577
#[cfg(test)]
578
mod tests {
579
    use crate::engine::{
580
        InitializedRasterOperator, MockExecutionContext, MockQueryContext, QueryProcessor,
581
        RasterOperator, SingleVectorSource, VectorOperator, WorkflowOperatorPath,
582
    };
583
    use crate::mock::{MockPointSource, MockPointSourceParams};
584
    use crate::processing::rasterization::GridSizeMode::{Fixed, Relative};
585
    use crate::processing::rasterization::{
586
        gaussian, DensityParams, GridOrDensity, GridParams, Rasterization,
587
    };
588
    use futures::StreamExt;
589
    use geoengine_datatypes::primitives::{
590
        BandSelection, Coordinate2D, RasterQueryRectangle, SpatialPartition2D, SpatialResolution,
591
    };
592
    use geoengine_datatypes::raster::TilingSpecification;
593
    use geoengine_datatypes::util::test::TestDefault;
594

595
    async fn get_results(
8✔
596
        rasterization: Box<dyn InitializedRasterOperator>,
8✔
597
        query: RasterQueryRectangle,
8✔
598
    ) -> Vec<Vec<f64>> {
8✔
599
        rasterization
8✔
600
            .query_processor()
8✔
601
            .unwrap()
8✔
602
            .get_f64()
8✔
603
            .unwrap()
8✔
604
            .query(query, &MockQueryContext::test_default())
8✔
605
            .await
×
606
            .unwrap()
8✔
607
            .map(|res| {
28✔
608
                res.unwrap()
28✔
609
                    .grid_array
28✔
610
                    .into_materialized_masked_grid()
28✔
611
                    .inner_grid
28✔
612
                    .data
28✔
613
            })
28✔
614
            .collect()
8✔
615
            .await
45✔
616
    }
8✔
617

618
    #[tokio::test]
619
    async fn fixed_grid_basic() {
1✔
620
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
621
            TilingSpecification::new([0., 0.].into(), [2, 2].into()),
1✔
622
        );
1✔
623
        let rasterization = Rasterization {
1✔
624
            params: GridOrDensity::Grid(GridParams {
1✔
625
                spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
626
                origin_coordinate: [0., 0.].into(),
1✔
627
                grid_size_mode: Fixed,
1✔
628
            }),
1✔
629
            sources: SingleVectorSource {
1✔
630
                vector: MockPointSource {
1✔
631
                    params: MockPointSourceParams {
1✔
632
                        points: vec![
1✔
633
                            (-1., 1.).into(),
1✔
634
                            (1., 1.).into(),
1✔
635
                            (-1., -1.).into(),
1✔
636
                            (1., -1.).into(),
1✔
637
                        ],
1✔
638
                    },
1✔
639
                }
1✔
640
                .boxed(),
1✔
641
            },
1✔
642
        }
1✔
643
        .boxed()
1✔
644
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
645
        .await
1✔
646
        .unwrap();
1✔
647

1✔
648
        let query = RasterQueryRectangle {
1✔
649
            spatial_bounds: SpatialPartition2D::new([-2., 2.].into(), [2., -2.].into()).unwrap(),
1✔
650
            time_interval: Default::default(),
1✔
651
            spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
652
            attributes: BandSelection::first(),
1✔
653
        };
1✔
654

1✔
655
        let res = get_results(rasterization, query).await;
8✔
656

1✔
657
        assert_eq!(
1✔
658
            res,
1✔
659
            vec![
1✔
660
                vec![0., 0., 0., 1.],
1✔
661
                vec![0., 0., 0., 1.],
1✔
662
                vec![0., 0., 0., 1.],
1✔
663
                vec![0., 0., 0., 1.],
1✔
664
            ]
1✔
665
        );
1✔
666
    }
1✔
667

668
    #[tokio::test]
669
    async fn fixed_grid_with_shift() {
1✔
670
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
671
            TilingSpecification::new([0., 0.].into(), [2, 2].into()),
1✔
672
        );
1✔
673
        let rasterization = Rasterization {
1✔
674
            params: GridOrDensity::Grid(GridParams {
1✔
675
                spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
676
                origin_coordinate: [0.5, -0.5].into(),
1✔
677
                grid_size_mode: Fixed,
1✔
678
            }),
1✔
679
            sources: SingleVectorSource {
1✔
680
                vector: MockPointSource {
1✔
681
                    params: MockPointSourceParams {
1✔
682
                        points: vec![
1✔
683
                            (-1., 1.).into(),
1✔
684
                            (1., 1.).into(),
1✔
685
                            (-1., -1.).into(),
1✔
686
                            (1., -1.).into(),
1✔
687
                        ],
1✔
688
                    },
1✔
689
                }
1✔
690
                .boxed(),
1✔
691
            },
1✔
692
        }
1✔
693
        .boxed()
1✔
694
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
695
        .await
1✔
696
        .unwrap();
1✔
697

1✔
698
        let query = RasterQueryRectangle {
1✔
699
            spatial_bounds: SpatialPartition2D::new([-2., 2.].into(), [2., -2.].into()).unwrap(),
1✔
700
            time_interval: Default::default(),
1✔
701
            spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
702
            attributes: BandSelection::first(),
1✔
703
        };
1✔
704

1✔
705
        let res = get_results(rasterization, query).await;
8✔
706

1✔
707
        assert_eq!(
1✔
708
            res,
1✔
709
            vec![
1✔
710
                vec![1., 0., 0., 0.],
1✔
711
                vec![1., 0., 0., 0.],
1✔
712
                vec![1., 0., 0., 0.],
1✔
713
                vec![1., 0., 0., 0.],
1✔
714
            ]
1✔
715
        );
1✔
716
    }
1✔
717

718
    #[tokio::test]
719
    async fn fixed_grid_with_upsampling() {
1✔
720
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
721
            TilingSpecification::new([0., 0.].into(), [3, 3].into()),
1✔
722
        );
1✔
723
        let rasterization = Rasterization {
1✔
724
            params: GridOrDensity::Grid(GridParams {
1✔
725
                spatial_resolution: SpatialResolution { x: 2.0, y: 2.0 },
1✔
726
                origin_coordinate: [0., 0.].into(),
1✔
727
                grid_size_mode: Fixed,
1✔
728
            }),
1✔
729
            sources: SingleVectorSource {
1✔
730
                vector: MockPointSource {
1✔
731
                    params: MockPointSourceParams {
1✔
732
                        points: vec![
1✔
733
                            (-1., 1.).into(),
1✔
734
                            (1., 1.).into(),
1✔
735
                            (-1., -1.).into(),
1✔
736
                            (1., -1.).into(),
1✔
737
                        ],
1✔
738
                    },
1✔
739
                }
1✔
740
                .boxed(),
1✔
741
            },
1✔
742
        }
1✔
743
        .boxed()
1✔
744
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
745
        .await
1✔
746
        .unwrap();
1✔
747

1✔
748
        let query = RasterQueryRectangle {
1✔
749
            spatial_bounds: SpatialPartition2D::new([-3., 3.].into(), [3., -3.].into()).unwrap(),
1✔
750
            time_interval: Default::default(),
1✔
751
            spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
752
            attributes: BandSelection::first(),
1✔
753
        };
1✔
754

1✔
755
        let res = get_results(rasterization, query).await;
8✔
756

1✔
757
        assert_eq!(
1✔
758
            res,
1✔
759
            vec![
1✔
760
                vec![0., 0., 0., 0., 1., 1., 0., 1., 1.],
1✔
761
                vec![0., 0., 0., 1., 1., 0., 1., 1., 0.],
1✔
762
                vec![0., 1., 1., 0., 1., 1., 0., 0., 0.],
1✔
763
                vec![1., 1., 0., 1., 1., 0., 0., 0., 0.],
1✔
764
            ]
1✔
765
        );
1✔
766
    }
1✔
767

768
    #[tokio::test]
769
    async fn relative_grid_basic() {
1✔
770
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
771
            TilingSpecification::new([0., 0.].into(), [3, 3].into()),
1✔
772
        );
1✔
773
        let rasterization = Rasterization {
1✔
774
            params: GridOrDensity::Grid(GridParams {
1✔
775
                spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
776
                origin_coordinate: [0., 0.].into(),
1✔
777
                grid_size_mode: Relative,
1✔
778
            }),
1✔
779
            sources: SingleVectorSource {
1✔
780
                vector: MockPointSource {
1✔
781
                    params: MockPointSourceParams {
1✔
782
                        points: vec![
1✔
783
                            (-1., 1.).into(),
1✔
784
                            (1., 1.).into(),
1✔
785
                            (-1., -1.).into(),
1✔
786
                            (1., -1.).into(),
1✔
787
                        ],
1✔
788
                    },
1✔
789
                }
1✔
790
                .boxed(),
1✔
791
            },
1✔
792
        }
1✔
793
        .boxed()
1✔
794
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
795
        .await
1✔
796
        .unwrap();
1✔
797

1✔
798
        let query = RasterQueryRectangle {
1✔
799
            spatial_bounds: SpatialPartition2D::new([-1.5, 1.5].into(), [1.5, -1.5].into())
1✔
800
                .unwrap(),
1✔
801
            time_interval: Default::default(),
1✔
802
            spatial_resolution: SpatialResolution { x: 0.5, y: 0.5 },
1✔
803
            attributes: BandSelection::first(),
1✔
804
        };
1✔
805

1✔
806
        let res = get_results(rasterization, query).await;
8✔
807

1✔
808
        assert_eq!(
1✔
809
            res,
1✔
810
            vec![
1✔
811
                vec![0., 0., 0., 0., 1., 0., 0., 0., 0.],
1✔
812
                vec![0., 0., 0., 0., 0., 1., 0., 0., 0.],
1✔
813
                vec![0., 0., 0., 0., 0., 0., 0., 1., 0.],
1✔
814
                vec![0., 0., 0., 0., 0., 0., 0., 0., 1.],
1✔
815
            ]
1✔
816
        );
1✔
817
    }
1✔
818

819
    #[tokio::test]
820
    async fn relative_grid_with_shift() {
1✔
821
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
822
            TilingSpecification::new([0., 0.].into(), [3, 3].into()),
1✔
823
        );
1✔
824
        let rasterization = Rasterization {
1✔
825
            params: GridOrDensity::Grid(GridParams {
1✔
826
                spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
827
                origin_coordinate: [0.25, -0.25].into(),
1✔
828
                grid_size_mode: Relative,
1✔
829
            }),
1✔
830
            sources: SingleVectorSource {
1✔
831
                vector: MockPointSource {
1✔
832
                    params: MockPointSourceParams {
1✔
833
                        points: vec![
1✔
834
                            (-1., 1.).into(),
1✔
835
                            (1., 1.).into(),
1✔
836
                            (-1., -1.).into(),
1✔
837
                            (1., -1.).into(),
1✔
838
                        ],
1✔
839
                    },
1✔
840
                }
1✔
841
                .boxed(),
1✔
842
            },
1✔
843
        }
1✔
844
        .boxed()
1✔
845
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
846
        .await
1✔
847
        .unwrap();
1✔
848

1✔
849
        let query = RasterQueryRectangle {
1✔
850
            spatial_bounds: SpatialPartition2D::new([-1.5, 1.5].into(), [1.5, -1.5].into())
1✔
851
                .unwrap(),
1✔
852
            time_interval: Default::default(),
1✔
853
            spatial_resolution: SpatialResolution { x: 0.5, y: 0.5 },
1✔
854
            attributes: BandSelection::first(),
1✔
855
        };
1✔
856

1✔
857
        let res = get_results(rasterization, query).await;
8✔
858

1✔
859
        assert_eq!(
1✔
860
            res,
1✔
861
            vec![
1✔
862
                vec![1., 0., 0., 0., 0., 0., 0., 0., 0.],
1✔
863
                vec![0., 1., 0., 0., 0., 0., 0., 0., 0.],
1✔
864
                vec![0., 0., 0., 1., 0., 0., 0., 0., 0.],
1✔
865
                vec![0., 0., 0., 0., 1., 0., 0., 0., 0.],
1✔
866
            ]
1✔
867
        );
1✔
868
    }
1✔
869

870
    #[tokio::test]
871
    async fn relative_grid_with_upsampling() {
1✔
872
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
873
            TilingSpecification::new([0., 0.].into(), [2, 2].into()),
1✔
874
        );
1✔
875
        let rasterization = Rasterization {
1✔
876
            params: GridOrDensity::Grid(GridParams {
1✔
877
                spatial_resolution: SpatialResolution { x: 2.0, y: 2.0 },
1✔
878
                origin_coordinate: [0., 0.].into(),
1✔
879
                grid_size_mode: Relative,
1✔
880
            }),
1✔
881
            sources: SingleVectorSource {
1✔
882
                vector: MockPointSource {
1✔
883
                    params: MockPointSourceParams {
1✔
884
                        points: vec![
1✔
885
                            (-1., 1.).into(),
1✔
886
                            (1., 1.).into(),
1✔
887
                            (-1., -1.).into(),
1✔
888
                            (1., -1.).into(),
1✔
889
                        ],
1✔
890
                    },
1✔
891
                }
1✔
892
                .boxed(),
1✔
893
            },
1✔
894
        }
1✔
895
        .boxed()
1✔
896
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
897
        .await
1✔
898
        .unwrap();
1✔
899

1✔
900
        let query = RasterQueryRectangle {
1✔
901
            spatial_bounds: SpatialPartition2D::new([-1., 1.].into(), [1., -1.].into()).unwrap(),
1✔
902
            time_interval: Default::default(),
1✔
903
            spatial_resolution: SpatialResolution { x: 0.5, y: 0.5 },
1✔
904
            attributes: BandSelection::first(),
1✔
905
        };
1✔
906

1✔
907
        let res = get_results(rasterization, query).await;
1✔
908

1✔
909
        assert_eq!(
1✔
910
            res,
1✔
911
            vec![
1✔
912
                vec![1., 1., 1., 1.],
1✔
913
                vec![0., 0., 0., 0.],
1✔
914
                vec![0., 0., 0., 0.],
1✔
915
                vec![0., 0., 0., 0.]
1✔
916
            ]
1✔
917
        );
1✔
918
    }
1✔
919

920
    #[tokio::test]
921
    async fn density_basic() {
1✔
922
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
923
            TilingSpecification::new([0., 0.].into(), [2, 2].into()),
1✔
924
        );
1✔
925
        let rasterization = Rasterization {
1✔
926
            params: GridOrDensity::Density(DensityParams {
1✔
927
                cutoff: gaussian(0.99, 1.0) / gaussian(0., 1.0),
1✔
928
                stddev: 1.0,
1✔
929
            }),
1✔
930
            sources: SingleVectorSource {
1✔
931
                vector: MockPointSource {
1✔
932
                    params: MockPointSourceParams {
1✔
933
                        points: vec![(-1., 1.).into(), (1., 1.).into()],
1✔
934
                    },
1✔
935
                }
1✔
936
                .boxed(),
1✔
937
            },
1✔
938
        }
1✔
939
        .boxed()
1✔
940
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
941
        .await
1✔
942
        .unwrap();
1✔
943

1✔
944
        let query = RasterQueryRectangle {
1✔
945
            spatial_bounds: SpatialPartition2D::new([-2., 2.].into(), [2., 0.].into()).unwrap(),
1✔
946
            time_interval: Default::default(),
1✔
947
            spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
948
            attributes: BandSelection::first(),
1✔
949
        };
1✔
950

1✔
951
        let res = get_results(rasterization, query).await;
2✔
952

1✔
953
        assert_eq!(
1✔
954
            res,
1✔
955
            vec![
1✔
956
                vec![
1✔
957
                    gaussian(
1✔
958
                        Coordinate2D::new(-1., 1.)
1✔
959
                            .euclidean_distance(&Coordinate2D::new(-1.5, 1.5)),
1✔
960
                        1.0
1✔
961
                    ),
1✔
962
                    gaussian(
1✔
963
                        Coordinate2D::new(-1., 1.)
1✔
964
                            .euclidean_distance(&Coordinate2D::new(-0.5, 1.5)),
1✔
965
                        1.0
1✔
966
                    ),
1✔
967
                    gaussian(
1✔
968
                        Coordinate2D::new(-1., 1.)
1✔
969
                            .euclidean_distance(&Coordinate2D::new(-1.5, 0.5)),
1✔
970
                        1.0
1✔
971
                    ),
1✔
972
                    gaussian(
1✔
973
                        Coordinate2D::new(-1., 1.)
1✔
974
                            .euclidean_distance(&Coordinate2D::new(-0.5, 0.5)),
1✔
975
                        1.0
1✔
976
                    )
1✔
977
                ],
1✔
978
                vec![
1✔
979
                    gaussian(
1✔
980
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 1.5)),
1✔
981
                        1.0
1✔
982
                    ),
1✔
983
                    gaussian(
1✔
984
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(1.5, 1.5)),
1✔
985
                        1.0
1✔
986
                    ),
1✔
987
                    gaussian(
1✔
988
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 0.5)),
1✔
989
                        1.0
1✔
990
                    ),
1✔
991
                    gaussian(
1✔
992
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(1.5, 0.5)),
1✔
993
                        1.0
1✔
994
                    )
1✔
995
                ],
1✔
996
            ]
1✔
997
        );
1✔
998
    }
1✔
999

1000
    #[tokio::test]
1001
    async fn density_radius_overlap() {
1✔
1002
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
1003
            TilingSpecification::new([0., 0.].into(), [2, 2].into()),
1✔
1004
        );
1✔
1005
        let rasterization = Rasterization {
1✔
1006
            params: GridOrDensity::Density(DensityParams {
1✔
1007
                cutoff: gaussian(1.99, 1.0) / gaussian(0., 1.0),
1✔
1008
                stddev: 1.0,
1✔
1009
            }),
1✔
1010
            sources: SingleVectorSource {
1✔
1011
                vector: MockPointSource {
1✔
1012
                    params: MockPointSourceParams {
1✔
1013
                        points: vec![(-1., 1.).into(), (1., 1.).into()],
1✔
1014
                    },
1✔
1015
                }
1✔
1016
                .boxed(),
1✔
1017
            },
1✔
1018
        }
1✔
1019
        .boxed()
1✔
1020
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1021
        .await
1✔
1022
        .unwrap();
1✔
1023

1✔
1024
        let query = RasterQueryRectangle {
1✔
1025
            spatial_bounds: SpatialPartition2D::new([-2., 2.].into(), [2., 0.].into()).unwrap(),
1✔
1026
            time_interval: Default::default(),
1✔
1027
            spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
1028
            attributes: BandSelection::first(),
1✔
1029
        };
1✔
1030

1✔
1031
        let res = get_results(rasterization, query).await;
2✔
1032

1✔
1033
        assert_eq!(
1✔
1034
            res,
1✔
1035
            vec![
1✔
1036
                vec![
1✔
1037
                    gaussian(
1✔
1038
                        Coordinate2D::new(-1., 1.)
1✔
1039
                            .euclidean_distance(&Coordinate2D::new(-1.5, 1.5)),
1✔
1040
                        1.0
1✔
1041
                    ),
1✔
1042
                    gaussian(
1✔
1043
                        Coordinate2D::new(-1., 1.)
1✔
1044
                            .euclidean_distance(&Coordinate2D::new(-0.5, 1.5)),
1✔
1045
                        1.0
1✔
1046
                    ) + gaussian(
1✔
1047
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(-0.5, 1.5)),
1✔
1048
                        1.0
1✔
1049
                    ),
1✔
1050
                    gaussian(
1✔
1051
                        Coordinate2D::new(-1., 1.)
1✔
1052
                            .euclidean_distance(&Coordinate2D::new(-1.5, 0.5)),
1✔
1053
                        1.0
1✔
1054
                    ),
1✔
1055
                    gaussian(
1✔
1056
                        Coordinate2D::new(-1., 1.)
1✔
1057
                            .euclidean_distance(&Coordinate2D::new(-0.5, 0.5)),
1✔
1058
                        1.0
1✔
1059
                    ) + gaussian(
1✔
1060
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(-0.5, 0.5)),
1✔
1061
                        1.0
1✔
1062
                    )
1✔
1063
                ],
1✔
1064
                vec![
1✔
1065
                    gaussian(
1✔
1066
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 1.5)),
1✔
1067
                        1.0
1✔
1068
                    ) + gaussian(
1✔
1069
                        Coordinate2D::new(-1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 1.5)),
1✔
1070
                        1.0
1✔
1071
                    ),
1✔
1072
                    gaussian(
1✔
1073
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(1.5, 1.5)),
1✔
1074
                        1.0
1✔
1075
                    ),
1✔
1076
                    gaussian(
1✔
1077
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 0.5)),
1✔
1078
                        1.0
1✔
1079
                    ) + gaussian(
1✔
1080
                        Coordinate2D::new(-1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 0.5)),
1✔
1081
                        1.0
1✔
1082
                    ),
1✔
1083
                    gaussian(
1✔
1084
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(1.5, 0.5)),
1✔
1085
                        1.0
1✔
1086
                    )
1✔
1087
                ],
1✔
1088
            ]
1✔
1089
        );
1✔
1090
    }
1✔
1091
}
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