• 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

92.53
/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

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

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

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

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

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

8✔
103
        let tiling_specification = context.tiling_specification();
8✔
104

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

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

139
    span_fn!(Rasterization);
140
}
141

142
pub struct InitializedGridRasterization {
143
    name: CanonicOperatorName,
144
    path: WorkflowOperatorPath,
145
    source: Box<dyn InitializedVectorOperator>,
146
    result_descriptor: RasterResultDescriptor,
147
    spatial_resolution: SpatialResolution,
148
    grid_size_mode: GridSizeMode,
149
    tiling_specification: TilingSpecification,
150
    origin_coordinate: Coordinate2D,
151
}
152

153
impl InitializedRasterOperator for InitializedGridRasterization {
154
    fn result_descriptor(&self) -> &RasterResultDescriptor {
×
155
        &self.result_descriptor
×
156
    }
×
157

158
    fn query_processor(&self) -> util::Result<TypedRasterQueryProcessor> {
6✔
159
        Ok(TypedRasterQueryProcessor::F64(
6✔
160
            GridRasterizationQueryProcessor {
6✔
161
                input: self.source.query_processor()?,
6✔
162
                result_descriptor: self.result_descriptor.clone(),
6✔
163
                spatial_resolution: self.spatial_resolution,
6✔
164
                grid_size_mode: self.grid_size_mode,
6✔
165
                tiling_specification: self.tiling_specification,
6✔
166
                origin_coordinate: self.origin_coordinate,
6✔
167
            }
6✔
168
            .boxed(),
6✔
169
        ))
170
    }
6✔
171

172
    fn canonic_name(&self) -> CanonicOperatorName {
×
173
        self.name.clone()
×
174
    }
×
175

NEW
176
    fn name(&self) -> &'static str {
×
NEW
177
        Rasterization::TYPE_NAME
×
NEW
178
    }
×
179

NEW
180
    fn path(&self) -> WorkflowOperatorPath {
×
NEW
181
        self.path.clone()
×
NEW
182
    }
×
183
}
184

185
pub struct InitializedDensityRasterization {
186
    name: CanonicOperatorName,
187
    path: WorkflowOperatorPath,
188
    source: Box<dyn InitializedVectorOperator>,
189
    result_descriptor: RasterResultDescriptor,
190
    tiling_specification: TilingSpecification,
191
    radius: f64,
192
    stddev: f64,
193
}
194

195
impl InitializedDensityRasterization {
196
    fn new(
2✔
197
        name: CanonicOperatorName,
2✔
198
        path: WorkflowOperatorPath,
2✔
199
        source: Box<dyn InitializedVectorOperator>,
2✔
200
        result_descriptor: RasterResultDescriptor,
2✔
201
        tiling_specification: TilingSpecification,
2✔
202
        cutoff: f64,
2✔
203
        stddev: f64,
2✔
204
    ) -> Result<Self, error::Error> {
2✔
205
        ensure!(
2✔
206
            (0. ..1.).contains(&cutoff),
2✔
207
            error::InvalidOperatorSpec {
×
208
                reason: "The cutoff for density rasterization must be in [0, 1).".to_string()
×
209
            }
×
210
        );
211
        ensure!(
2✔
212
            stddev >= 0.,
2✔
213
            error::InvalidOperatorSpec {
×
214
                reason: "The standard deviation for density rasterization must be greater than or equal to zero."
×
215
                    .to_string()
×
216
            }
×
217
        );
218

219
        // Determine radius from cutoff percentage
220
        let radius = gaussian_inverse(cutoff * gaussian(0., stddev), stddev);
2✔
221

2✔
222
        Ok(InitializedDensityRasterization {
2✔
223
            name,
2✔
224
            path,
2✔
225
            source,
2✔
226
            result_descriptor,
2✔
227
            tiling_specification,
2✔
228
            radius,
2✔
229
            stddev,
2✔
230
        })
2✔
231
    }
2✔
232
}
233

234
impl InitializedRasterOperator for InitializedDensityRasterization {
235
    fn result_descriptor(&self) -> &RasterResultDescriptor {
×
236
        &self.result_descriptor
×
237
    }
×
238

239
    fn query_processor(&self) -> util::Result<TypedRasterQueryProcessor> {
2✔
240
        Ok(TypedRasterQueryProcessor::F64(
2✔
241
            DensityRasterizationQueryProcessor {
2✔
242
                result_descriptor: self.result_descriptor.clone(),
2✔
243
                input: self.source.query_processor()?,
2✔
244
                tiling_specification: self.tiling_specification,
2✔
245
                radius: self.radius,
2✔
246
                stddev: self.stddev,
2✔
247
            }
2✔
248
            .boxed(),
2✔
249
        ))
250
    }
2✔
251

252
    fn canonic_name(&self) -> CanonicOperatorName {
×
253
        self.name.clone()
×
254
    }
×
255

NEW
256
    fn name(&self) -> &'static str {
×
NEW
257
        Rasterization::TYPE_NAME
×
NEW
258
    }
×
259

NEW
260
    fn path(&self) -> WorkflowOperatorPath {
×
NEW
261
        self.path.clone()
×
NEW
262
    }
×
263
}
264

265
pub struct GridRasterizationQueryProcessor {
266
    input: TypedVectorQueryProcessor,
267
    result_descriptor: RasterResultDescriptor,
268
    spatial_resolution: SpatialResolution,
269
    grid_size_mode: GridSizeMode,
270
    tiling_specification: TilingSpecification,
271
    origin_coordinate: Coordinate2D,
272
}
273

274
#[async_trait]
275
impl RasterQueryProcessor for GridRasterizationQueryProcessor {
276
    type RasterType = f64;
277

278
    /// Performs a grid rasterization by first determining the grid resolution to use.
279
    /// The grid resolution is limited to the query resolution, because a finer granularity
280
    /// would not be visible in the resulting raster.
281
    /// Then, for each tile, a grid, aligned to the configured origin coordinate, is created.
282
    /// All points within the spatial bounds of the grid are queried and counted in the
283
    /// grid cells.
284
    /// Finally, the grid resolution is upsampled (if necessary) to the tile resolution.
285
    async fn raster_query<'a>(
286
        &'a self,
287
        query: RasterQueryRectangle,
288
        ctx: &'a dyn QueryContext,
289
    ) -> util::Result<BoxStream<'a, util::Result<RasterTile2D<Self::RasterType>>>> {
6✔
290
        if let MultiPoint(points_processor) = &self.input {
6✔
291
            let grid_resolution = match self.grid_size_mode {
6✔
292
                GridSizeMode::Fixed => SpatialResolution {
3✔
293
                    x: f64::max(self.spatial_resolution.x, query.spatial_resolution.x),
3✔
294
                    y: f64::max(self.spatial_resolution.y, query.spatial_resolution.y),
3✔
295
                },
3✔
296
                GridSizeMode::Relative => SpatialResolution {
3✔
297
                    x: f64::max(
3✔
298
                        self.spatial_resolution.x * query.spatial_resolution.x,
3✔
299
                        query.spatial_resolution.x,
3✔
300
                    ),
3✔
301
                    y: f64::max(
3✔
302
                        self.spatial_resolution.y * query.spatial_resolution.y,
3✔
303
                        query.spatial_resolution.y,
3✔
304
                    ),
3✔
305
                },
3✔
306
            };
307

308
            let tiling_strategy = self
6✔
309
                .tiling_specification
6✔
310
                .strategy(query.spatial_resolution.x, -query.spatial_resolution.y);
6✔
311
            let tile_shape = tiling_strategy.tile_size_in_pixels;
6✔
312

6✔
313
            let tiles = stream::iter(
6✔
314
                tiling_strategy.tile_information_iterator(query.spatial_bounds),
6✔
315
            )
6✔
316
            .then(move |tile_info| async move {
24✔
317
                let grid_spatial_bounds = tile_info
24✔
318
                    .spatial_partition()
24✔
319
                    .snap_to_grid(self.origin_coordinate, grid_resolution);
24✔
320

24✔
321
                let grid_size_x =
24✔
322
                    f64::ceil(grid_spatial_bounds.size_x() / grid_resolution.x) as usize;
24✔
323
                let grid_size_y =
24✔
324
                    f64::ceil(grid_spatial_bounds.size_y() / grid_resolution.y) as usize;
24✔
325

24✔
326
                let vector_query = VectorQueryRectangle {
24✔
327
                    spatial_bounds: grid_spatial_bounds.as_bbox(),
24✔
328
                    time_interval: query.time_interval,
24✔
329
                    spatial_resolution: grid_resolution,
24✔
330
                    attributes: ColumnSelection::all(),
24✔
331
                };
24✔
332

24✔
333
                let grid_geo_transform = GeoTransform::new(
24✔
334
                    grid_spatial_bounds.upper_left(),
24✔
335
                    grid_resolution.x,
24✔
336
                    -grid_resolution.y,
24✔
337
                );
24✔
338

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

341
                let mut cache_hint = CacheHint::max_duration();
24✔
342

24✔
343
                let mut grid_data = vec![0.; grid_size_x * grid_size_y];
24✔
344
                while let Some(chunk) = chunks.next().await {
48✔
345
                    let chunk = chunk?;
24✔
346

347
                    cache_hint.merge_with(&chunk.cache_hint);
24✔
348

349
                    grid_data = spawn_blocking(move || {
24✔
350
                        for &coord in chunk.coordinates() {
24✔
351
                            if !grid_spatial_bounds.contains_coordinate(&coord) {
24✔
352
                                continue;
3✔
353
                            }
21✔
354
                            let [y, x] = grid_geo_transform.coordinate_to_grid_idx_2d(coord).0;
21✔
355
                            grid_data[x as usize + y as usize * grid_size_x] += 1.;
21✔
356
                        }
357
                        grid_data
24✔
358
                    })
24✔
359
                    .await
24✔
360
                    .expect("Should only forward panics from spawned task");
24✔
361
                }
362

363
                let tile_data = spawn_blocking(move || {
24✔
364
                    let mut tile_data = Vec::with_capacity(tile_shape.number_of_elements());
24✔
365
                    for tile_y in 0..tile_shape.axis_size_y() as isize {
60✔
366
                        for tile_x in 0..tile_shape.axis_size_x() as isize {
156✔
367
                            let pixel_coordinate = tile_info
156✔
368
                                .tile_geo_transform()
156✔
369
                                .grid_idx_to_pixel_center_coordinate_2d([tile_y, tile_x].into());
156✔
370
                            if query.spatial_bounds.contains_coordinate(&pixel_coordinate) {
156✔
371
                                let [grid_y, grid_x] = grid_geo_transform
156✔
372
                                    .coordinate_to_grid_idx_2d(pixel_coordinate)
156✔
373
                                    .0;
156✔
374
                                tile_data.push(
156✔
375
                                    grid_data[grid_x as usize + grid_y as usize * grid_size_x],
156✔
376
                                );
156✔
377
                            } else {
156✔
378
                                tile_data.push(0.);
×
379
                            }
×
380
                        }
381
                    }
382
                    tile_data
24✔
383
                })
24✔
384
                .await
24✔
385
                .expect("Should only forward panics from spawned task");
24✔
386
                let tile_grid = Grid2D::new(tile_shape, tile_data)
24✔
387
                    .expect("Data vector length should match the number of pixels in the tile");
24✔
388

24✔
389
                Ok(RasterTile2D::new_with_tile_info(
24✔
390
                    query.time_interval,
24✔
391
                    tile_info,
24✔
392
                    0,
24✔
393
                    GridOrEmpty::Grid(tile_grid.into()),
24✔
394
                    cache_hint,
24✔
395
                ))
24✔
396
            });
48✔
397
            Ok(tiles.boxed())
6✔
398
        } else {
399
            Ok(generate_zeroed_tiles(self.tiling_specification, &query))
×
400
        }
401
    }
12✔
402

403
    fn raster_result_descriptor(&self) -> &RasterResultDescriptor {
6✔
404
        &self.result_descriptor
6✔
405
    }
6✔
406
}
407

408
pub struct DensityRasterizationQueryProcessor {
409
    input: TypedVectorQueryProcessor,
410
    result_descriptor: RasterResultDescriptor,
411
    tiling_specification: TilingSpecification,
412
    radius: f64,
413
    stddev: f64,
414
}
415

416
#[async_trait]
417
impl RasterQueryProcessor for DensityRasterizationQueryProcessor {
418
    type RasterType = f64;
419

420
    /// Performs a gaussian density rasterization.
421
    /// For each tile, the spatial bounds are extended by `radius` in x and y direction.
422
    /// All points within these extended bounds are then queried. For each point, the distance to
423
    /// its surrounding tile pixels (up to `radius` distance) is measured and input into the
424
    /// gaussian density function with the configured standard deviation. The density values
425
    /// for each pixel are then summed to result in the tile pixel grid.
426
    async fn raster_query<'a>(
427
        &'a self,
428
        query: RasterQueryRectangle,
429
        ctx: &'a dyn QueryContext,
430
    ) -> util::Result<BoxStream<'a, util::Result<RasterTile2D<Self::RasterType>>>> {
2✔
431
        if let MultiPoint(points_processor) = &self.input {
2✔
432
            let tiling_strategy = self
2✔
433
                .tiling_specification
2✔
434
                .strategy(query.spatial_resolution.x, -query.spatial_resolution.y);
2✔
435

2✔
436
            let tile_size_x = tiling_strategy.tile_size_in_pixels.axis_size_x();
2✔
437
            let tile_size_y = tiling_strategy.tile_size_in_pixels.axis_size_y();
2✔
438

2✔
439
            // Use rounding factor calculated from query resolution to extend in full pixel units
2✔
440
            let rounding_factor = f64::max(
2✔
441
                1. / query.spatial_resolution.x,
2✔
442
                1. / query.spatial_resolution.y,
2✔
443
            );
2✔
444
            let radius = (self.radius * rounding_factor).ceil() / rounding_factor;
2✔
445

2✔
446
            let tiles = stream::iter(
2✔
447
                tiling_strategy.tile_information_iterator(query.spatial_bounds),
2✔
448
            )
2✔
449
            .then(move |tile_info| async move {
4✔
450
                let tile_bounds = tile_info.spatial_partition();
4✔
451

4✔
452
                let vector_query = VectorQueryRectangle {
4✔
453
                    spatial_bounds: extended_bounding_box_from_spatial_partition(
4✔
454
                        tile_bounds,
4✔
455
                        radius,
4✔
456
                    ),
4✔
457
                    time_interval: query.time_interval,
4✔
458
                    spatial_resolution: query.spatial_resolution,
4✔
459
                    attributes: ColumnSelection::all(),
4✔
460
                };
4✔
461

4✔
462
                let tile_geo_transform = tile_info.tile_geo_transform();
4✔
463

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

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

4✔
468
                let mut cache_hint = CacheHint::max_duration();
4✔
469

470
                while let Some(chunk) = chunks.next().await {
8✔
471
                    let chunk = chunk?;
4✔
472

473
                    cache_hint.merge_with(&chunk.cache_hint);
4✔
474

4✔
475
                    let stddev = self.stddev;
4✔
476
                    tile_data =
4✔
477
                        spawn_blocking_with_thread_pool(ctx.thread_pool().clone(), move || {
4✔
478
                            tile_data.par_iter_mut().enumerate().for_each(
4✔
479
                                |(linear_index, pixel)| {
16✔
480
                                    let pixel_coordinate = tile_geo_transform
16✔
481
                                        .grid_idx_to_pixel_center_coordinate_2d(
16✔
482
                                            tile_geo_transform
16✔
483
                                                .spatial_to_grid_bounds(&tile_bounds)
16✔
484
                                                .grid_idx_unchecked(linear_index),
16✔
485
                                        );
16✔
486

487
                                    for coord in chunk.coordinates() {
32✔
488
                                        let distance = coord.euclidean_distance(&pixel_coordinate);
32✔
489

32✔
490
                                        if distance <= radius {
32✔
491
                                            *pixel += gaussian(distance, stddev);
20✔
492
                                        }
20✔
493
                                    }
494
                                },
16✔
495
                            );
4✔
496

4✔
497
                            tile_data
4✔
498
                        })
4✔
499
                        .await?;
4✔
500
                }
501

502
                Ok(RasterTile2D::new_with_tile_info(
4✔
503
                    query.time_interval,
4✔
504
                    tile_info,
4✔
505
                    0,
4✔
506
                    GridOrEmpty::Grid(
4✔
507
                        Grid2D::new(tiling_strategy.tile_size_in_pixels, tile_data)
4✔
508
                            .expect(
4✔
509
                                "Data vector length should match the number of pixels in the tile",
4✔
510
                            )
4✔
511
                            .into(),
4✔
512
                    ),
4✔
513
                    cache_hint,
4✔
514
                ))
4✔
515
            });
8✔
516

2✔
517
            Ok(tiles.boxed())
2✔
518
        } else {
519
            Ok(generate_zeroed_tiles(self.tiling_specification, &query))
×
520
        }
521
    }
4✔
522

523
    fn raster_result_descriptor(&self) -> &RasterResultDescriptor {
2✔
524
        &self.result_descriptor
2✔
525
    }
2✔
526
}
527

528
fn generate_zeroed_tiles<'a>(
×
529
    tiling_specification: TilingSpecification,
×
530
    query: &RasterQueryRectangle,
×
531
) -> BoxStream<'a, util::Result<RasterTile2D<f64>>> {
×
532
    let tiling_strategy =
×
533
        tiling_specification.strategy(query.spatial_resolution.x, -query.spatial_resolution.y);
×
534
    let tile_shape = tiling_strategy.tile_size_in_pixels;
×
535
    let time_interval = query.time_interval;
×
536

×
537
    stream::iter(
×
538
        tiling_strategy
×
539
            .tile_information_iterator(query.spatial_bounds)
×
540
            .map(move |tile_info| {
×
541
                let tile_data = vec![0.; tile_shape.number_of_elements()];
×
542
                let tile_grid = Grid2D::new(tile_shape, tile_data)
×
543
                    .expect("Data vector length should match the number of pixels in the tile");
×
544

×
545
                Ok(RasterTile2D::new_with_tile_info(
×
546
                    time_interval,
×
547
                    tile_info,
×
548
                    0,
×
549
                    GridOrEmpty::Grid(tile_grid.into()),
×
550
                    CacheHint::no_cache(),
×
551
                ))
×
552
            }),
×
553
    )
×
554
    .boxed()
×
555
}
×
556

557
fn extended_bounding_box_from_spatial_partition(
4✔
558
    spatial_partition: SpatialPartition2D,
4✔
559
    extent: f64,
4✔
560
) -> BoundingBox2D {
4✔
561
    BoundingBox2D::new_unchecked(
4✔
562
        Coordinate2D::new(
4✔
563
            spatial_partition
4✔
564
                .lower_left()
4✔
565
                .x
4✔
566
                .sub_checked(extent)
4✔
567
                .unwrap_or(f64::MIN),
4✔
568
            spatial_partition
4✔
569
                .lower_left()
4✔
570
                .y
4✔
571
                .sub_checked(extent)
4✔
572
                .unwrap_or(f64::MIN),
4✔
573
        ),
4✔
574
        Coordinate2D::new(
4✔
575
            spatial_partition
4✔
576
                .upper_right()
4✔
577
                .x
4✔
578
                .add_checked(extent)
4✔
579
                .unwrap_or(f64::MAX),
4✔
580
            spatial_partition
4✔
581
                .upper_right()
4✔
582
                .y
4✔
583
                .add_checked(extent)
4✔
584
                .unwrap_or(f64::MAX),
4✔
585
        ),
4✔
586
    )
4✔
587
}
4✔
588

589
/// Calculates the gaussian density value for
590
/// `x`, the distance from the mean and
591
/// `stddev`, the standard deviation
592
fn gaussian(x: f64, stddev: f64) -> f64 {
46✔
593
    (1. / (f64::sqrt(2. * f64::PI()) * stddev)) * f64::exp(-(x * x) / (2. * stddev * stddev))
46✔
594
}
46✔
595

596
/// The inverse function of [gaussian](gaussian)
597
fn gaussian_inverse(x: f64, stddev: f64) -> f64 {
2✔
598
    f64::sqrt(2.)
2✔
599
        * f64::sqrt(stddev * stddev * f64::ln(1. / (f64::sqrt(2. * f64::PI()) * stddev * x)))
2✔
600
}
2✔
601

602
#[cfg(test)]
603
mod tests {
604
    use crate::engine::{
605
        InitializedRasterOperator, MockExecutionContext, MockQueryContext, QueryProcessor,
606
        RasterOperator, SingleVectorSource, VectorOperator, WorkflowOperatorPath,
607
    };
608
    use crate::mock::{MockPointSource, MockPointSourceParams};
609
    use crate::processing::rasterization::GridSizeMode::{Fixed, Relative};
610
    use crate::processing::rasterization::{
611
        gaussian, DensityParams, GridOrDensity, GridParams, Rasterization,
612
    };
613
    use futures::StreamExt;
614
    use geoengine_datatypes::primitives::{
615
        BandSelection, Coordinate2D, RasterQueryRectangle, SpatialPartition2D, SpatialResolution,
616
    };
617
    use geoengine_datatypes::raster::TilingSpecification;
618
    use geoengine_datatypes::util::test::TestDefault;
619

620
    async fn get_results(
8✔
621
        rasterization: Box<dyn InitializedRasterOperator>,
8✔
622
        query: RasterQueryRectangle,
8✔
623
    ) -> Vec<Vec<f64>> {
8✔
624
        rasterization
8✔
625
            .query_processor()
8✔
626
            .unwrap()
8✔
627
            .get_f64()
8✔
628
            .unwrap()
8✔
629
            .query(query, &MockQueryContext::test_default())
8✔
630
            .await
8✔
631
            .unwrap()
8✔
632
            .map(|res| {
28✔
633
                res.unwrap()
28✔
634
                    .grid_array
28✔
635
                    .into_materialized_masked_grid()
28✔
636
                    .inner_grid
28✔
637
                    .data
28✔
638
            })
28✔
639
            .collect()
8✔
640
            .await
8✔
641
    }
8✔
642

643
    #[tokio::test]
644
    async fn fixed_grid_basic() {
1✔
645
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
646
            TilingSpecification::new([0., 0.].into(), [2, 2].into()),
1✔
647
        );
1✔
648
        let rasterization = Rasterization {
1✔
649
            params: GridOrDensity::Grid(GridParams {
1✔
650
                spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
651
                origin_coordinate: [0., 0.].into(),
1✔
652
                grid_size_mode: Fixed,
1✔
653
            }),
1✔
654
            sources: SingleVectorSource {
1✔
655
                vector: MockPointSource {
1✔
656
                    params: MockPointSourceParams {
1✔
657
                        points: vec![
1✔
658
                            (-1., 1.).into(),
1✔
659
                            (1., 1.).into(),
1✔
660
                            (-1., -1.).into(),
1✔
661
                            (1., -1.).into(),
1✔
662
                        ],
1✔
663
                    },
1✔
664
                }
1✔
665
                .boxed(),
1✔
666
            },
1✔
667
        }
1✔
668
        .boxed()
1✔
669
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
670
        .await
1✔
671
        .unwrap();
1✔
672

1✔
673
        let query = RasterQueryRectangle {
1✔
674
            spatial_bounds: SpatialPartition2D::new([-2., 2.].into(), [2., -2.].into()).unwrap(),
1✔
675
            time_interval: Default::default(),
1✔
676
            spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
677
            attributes: BandSelection::first(),
1✔
678
        };
1✔
679

1✔
680
        let res = get_results(rasterization, query).await;
1✔
681

1✔
682
        assert_eq!(
1✔
683
            res,
1✔
684
            vec![
1✔
685
                vec![0., 0., 0., 1.],
1✔
686
                vec![0., 0., 0., 1.],
1✔
687
                vec![0., 0., 0., 1.],
1✔
688
                vec![0., 0., 0., 1.],
1✔
689
            ]
1✔
690
        );
1✔
691
    }
1✔
692

693
    #[tokio::test]
694
    async fn fixed_grid_with_shift() {
1✔
695
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
696
            TilingSpecification::new([0., 0.].into(), [2, 2].into()),
1✔
697
        );
1✔
698
        let rasterization = Rasterization {
1✔
699
            params: GridOrDensity::Grid(GridParams {
1✔
700
                spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
701
                origin_coordinate: [0.5, -0.5].into(),
1✔
702
                grid_size_mode: Fixed,
1✔
703
            }),
1✔
704
            sources: SingleVectorSource {
1✔
705
                vector: MockPointSource {
1✔
706
                    params: MockPointSourceParams {
1✔
707
                        points: vec![
1✔
708
                            (-1., 1.).into(),
1✔
709
                            (1., 1.).into(),
1✔
710
                            (-1., -1.).into(),
1✔
711
                            (1., -1.).into(),
1✔
712
                        ],
1✔
713
                    },
1✔
714
                }
1✔
715
                .boxed(),
1✔
716
            },
1✔
717
        }
1✔
718
        .boxed()
1✔
719
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
720
        .await
1✔
721
        .unwrap();
1✔
722

1✔
723
        let query = RasterQueryRectangle {
1✔
724
            spatial_bounds: SpatialPartition2D::new([-2., 2.].into(), [2., -2.].into()).unwrap(),
1✔
725
            time_interval: Default::default(),
1✔
726
            spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
727
            attributes: BandSelection::first(),
1✔
728
        };
1✔
729

1✔
730
        let res = get_results(rasterization, query).await;
1✔
731

1✔
732
        assert_eq!(
1✔
733
            res,
1✔
734
            vec![
1✔
735
                vec![1., 0., 0., 0.],
1✔
736
                vec![1., 0., 0., 0.],
1✔
737
                vec![1., 0., 0., 0.],
1✔
738
                vec![1., 0., 0., 0.],
1✔
739
            ]
1✔
740
        );
1✔
741
    }
1✔
742

743
    #[tokio::test]
744
    async fn fixed_grid_with_upsampling() {
1✔
745
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
746
            TilingSpecification::new([0., 0.].into(), [3, 3].into()),
1✔
747
        );
1✔
748
        let rasterization = Rasterization {
1✔
749
            params: GridOrDensity::Grid(GridParams {
1✔
750
                spatial_resolution: SpatialResolution { x: 2.0, y: 2.0 },
1✔
751
                origin_coordinate: [0., 0.].into(),
1✔
752
                grid_size_mode: Fixed,
1✔
753
            }),
1✔
754
            sources: SingleVectorSource {
1✔
755
                vector: MockPointSource {
1✔
756
                    params: MockPointSourceParams {
1✔
757
                        points: vec![
1✔
758
                            (-1., 1.).into(),
1✔
759
                            (1., 1.).into(),
1✔
760
                            (-1., -1.).into(),
1✔
761
                            (1., -1.).into(),
1✔
762
                        ],
1✔
763
                    },
1✔
764
                }
1✔
765
                .boxed(),
1✔
766
            },
1✔
767
        }
1✔
768
        .boxed()
1✔
769
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
770
        .await
1✔
771
        .unwrap();
1✔
772

1✔
773
        let query = RasterQueryRectangle {
1✔
774
            spatial_bounds: SpatialPartition2D::new([-3., 3.].into(), [3., -3.].into()).unwrap(),
1✔
775
            time_interval: Default::default(),
1✔
776
            spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
777
            attributes: BandSelection::first(),
1✔
778
        };
1✔
779

1✔
780
        let res = get_results(rasterization, query).await;
1✔
781

1✔
782
        assert_eq!(
1✔
783
            res,
1✔
784
            vec![
1✔
785
                vec![0., 0., 0., 0., 1., 1., 0., 1., 1.],
1✔
786
                vec![0., 0., 0., 1., 1., 0., 1., 1., 0.],
1✔
787
                vec![0., 1., 1., 0., 1., 1., 0., 0., 0.],
1✔
788
                vec![1., 1., 0., 1., 1., 0., 0., 0., 0.],
1✔
789
            ]
1✔
790
        );
1✔
791
    }
1✔
792

793
    #[tokio::test]
794
    async fn relative_grid_basic() {
1✔
795
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
796
            TilingSpecification::new([0., 0.].into(), [3, 3].into()),
1✔
797
        );
1✔
798
        let rasterization = Rasterization {
1✔
799
            params: GridOrDensity::Grid(GridParams {
1✔
800
                spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
801
                origin_coordinate: [0., 0.].into(),
1✔
802
                grid_size_mode: Relative,
1✔
803
            }),
1✔
804
            sources: SingleVectorSource {
1✔
805
                vector: MockPointSource {
1✔
806
                    params: MockPointSourceParams {
1✔
807
                        points: vec![
1✔
808
                            (-1., 1.).into(),
1✔
809
                            (1., 1.).into(),
1✔
810
                            (-1., -1.).into(),
1✔
811
                            (1., -1.).into(),
1✔
812
                        ],
1✔
813
                    },
1✔
814
                }
1✔
815
                .boxed(),
1✔
816
            },
1✔
817
        }
1✔
818
        .boxed()
1✔
819
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
820
        .await
1✔
821
        .unwrap();
1✔
822

1✔
823
        let query = RasterQueryRectangle {
1✔
824
            spatial_bounds: SpatialPartition2D::new([-1.5, 1.5].into(), [1.5, -1.5].into())
1✔
825
                .unwrap(),
1✔
826
            time_interval: Default::default(),
1✔
827
            spatial_resolution: SpatialResolution { x: 0.5, y: 0.5 },
1✔
828
            attributes: BandSelection::first(),
1✔
829
        };
1✔
830

1✔
831
        let res = get_results(rasterization, query).await;
1✔
832

1✔
833
        assert_eq!(
1✔
834
            res,
1✔
835
            vec![
1✔
836
                vec![0., 0., 0., 0., 1., 0., 0., 0., 0.],
1✔
837
                vec![0., 0., 0., 0., 0., 1., 0., 0., 0.],
1✔
838
                vec![0., 0., 0., 0., 0., 0., 0., 1., 0.],
1✔
839
                vec![0., 0., 0., 0., 0., 0., 0., 0., 1.],
1✔
840
            ]
1✔
841
        );
1✔
842
    }
1✔
843

844
    #[tokio::test]
845
    async fn relative_grid_with_shift() {
1✔
846
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
847
            TilingSpecification::new([0., 0.].into(), [3, 3].into()),
1✔
848
        );
1✔
849
        let rasterization = Rasterization {
1✔
850
            params: GridOrDensity::Grid(GridParams {
1✔
851
                spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
852
                origin_coordinate: [0.25, -0.25].into(),
1✔
853
                grid_size_mode: Relative,
1✔
854
            }),
1✔
855
            sources: SingleVectorSource {
1✔
856
                vector: MockPointSource {
1✔
857
                    params: MockPointSourceParams {
1✔
858
                        points: vec![
1✔
859
                            (-1., 1.).into(),
1✔
860
                            (1., 1.).into(),
1✔
861
                            (-1., -1.).into(),
1✔
862
                            (1., -1.).into(),
1✔
863
                        ],
1✔
864
                    },
1✔
865
                }
1✔
866
                .boxed(),
1✔
867
            },
1✔
868
        }
1✔
869
        .boxed()
1✔
870
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
871
        .await
1✔
872
        .unwrap();
1✔
873

1✔
874
        let query = RasterQueryRectangle {
1✔
875
            spatial_bounds: SpatialPartition2D::new([-1.5, 1.5].into(), [1.5, -1.5].into())
1✔
876
                .unwrap(),
1✔
877
            time_interval: Default::default(),
1✔
878
            spatial_resolution: SpatialResolution { x: 0.5, y: 0.5 },
1✔
879
            attributes: BandSelection::first(),
1✔
880
        };
1✔
881

1✔
882
        let res = get_results(rasterization, query).await;
1✔
883

1✔
884
        assert_eq!(
1✔
885
            res,
1✔
886
            vec![
1✔
887
                vec![1., 0., 0., 0., 0., 0., 0., 0., 0.],
1✔
888
                vec![0., 1., 0., 0., 0., 0., 0., 0., 0.],
1✔
889
                vec![0., 0., 0., 1., 0., 0., 0., 0., 0.],
1✔
890
                vec![0., 0., 0., 0., 1., 0., 0., 0., 0.],
1✔
891
            ]
1✔
892
        );
1✔
893
    }
1✔
894

895
    #[tokio::test]
896
    async fn relative_grid_with_upsampling() {
1✔
897
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
898
            TilingSpecification::new([0., 0.].into(), [2, 2].into()),
1✔
899
        );
1✔
900
        let rasterization = Rasterization {
1✔
901
            params: GridOrDensity::Grid(GridParams {
1✔
902
                spatial_resolution: SpatialResolution { x: 2.0, y: 2.0 },
1✔
903
                origin_coordinate: [0., 0.].into(),
1✔
904
                grid_size_mode: Relative,
1✔
905
            }),
1✔
906
            sources: SingleVectorSource {
1✔
907
                vector: MockPointSource {
1✔
908
                    params: MockPointSourceParams {
1✔
909
                        points: vec![
1✔
910
                            (-1., 1.).into(),
1✔
911
                            (1., 1.).into(),
1✔
912
                            (-1., -1.).into(),
1✔
913
                            (1., -1.).into(),
1✔
914
                        ],
1✔
915
                    },
1✔
916
                }
1✔
917
                .boxed(),
1✔
918
            },
1✔
919
        }
1✔
920
        .boxed()
1✔
921
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
922
        .await
1✔
923
        .unwrap();
1✔
924

1✔
925
        let query = RasterQueryRectangle {
1✔
926
            spatial_bounds: SpatialPartition2D::new([-1., 1.].into(), [1., -1.].into()).unwrap(),
1✔
927
            time_interval: Default::default(),
1✔
928
            spatial_resolution: SpatialResolution { x: 0.5, y: 0.5 },
1✔
929
            attributes: BandSelection::first(),
1✔
930
        };
1✔
931

1✔
932
        let res = get_results(rasterization, query).await;
1✔
933

1✔
934
        assert_eq!(
1✔
935
            res,
1✔
936
            vec![
1✔
937
                vec![1., 1., 1., 1.],
1✔
938
                vec![0., 0., 0., 0.],
1✔
939
                vec![0., 0., 0., 0.],
1✔
940
                vec![0., 0., 0., 0.]
1✔
941
            ]
1✔
942
        );
1✔
943
    }
1✔
944

945
    #[tokio::test]
946
    async fn density_basic() {
1✔
947
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
948
            TilingSpecification::new([0., 0.].into(), [2, 2].into()),
1✔
949
        );
1✔
950
        let rasterization = Rasterization {
1✔
951
            params: GridOrDensity::Density(DensityParams {
1✔
952
                cutoff: gaussian(0.99, 1.0) / gaussian(0., 1.0),
1✔
953
                stddev: 1.0,
1✔
954
            }),
1✔
955
            sources: SingleVectorSource {
1✔
956
                vector: MockPointSource {
1✔
957
                    params: MockPointSourceParams {
1✔
958
                        points: vec![(-1., 1.).into(), (1., 1.).into()],
1✔
959
                    },
1✔
960
                }
1✔
961
                .boxed(),
1✔
962
            },
1✔
963
        }
1✔
964
        .boxed()
1✔
965
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
966
        .await
1✔
967
        .unwrap();
1✔
968

1✔
969
        let query = RasterQueryRectangle {
1✔
970
            spatial_bounds: SpatialPartition2D::new([-2., 2.].into(), [2., 0.].into()).unwrap(),
1✔
971
            time_interval: Default::default(),
1✔
972
            spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
973
            attributes: BandSelection::first(),
1✔
974
        };
1✔
975

1✔
976
        let res = get_results(rasterization, query).await;
1✔
977

1✔
978
        assert_eq!(
1✔
979
            res,
1✔
980
            vec![
1✔
981
                vec![
1✔
982
                    gaussian(
1✔
983
                        Coordinate2D::new(-1., 1.)
1✔
984
                            .euclidean_distance(&Coordinate2D::new(-1.5, 1.5)),
1✔
985
                        1.0
1✔
986
                    ),
1✔
987
                    gaussian(
1✔
988
                        Coordinate2D::new(-1., 1.)
1✔
989
                            .euclidean_distance(&Coordinate2D::new(-0.5, 1.5)),
1✔
990
                        1.0
1✔
991
                    ),
1✔
992
                    gaussian(
1✔
993
                        Coordinate2D::new(-1., 1.)
1✔
994
                            .euclidean_distance(&Coordinate2D::new(-1.5, 0.5)),
1✔
995
                        1.0
1✔
996
                    ),
1✔
997
                    gaussian(
1✔
998
                        Coordinate2D::new(-1., 1.)
1✔
999
                            .euclidean_distance(&Coordinate2D::new(-0.5, 0.5)),
1✔
1000
                        1.0
1✔
1001
                    )
1✔
1002
                ],
1✔
1003
                vec![
1✔
1004
                    gaussian(
1✔
1005
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 1.5)),
1✔
1006
                        1.0
1✔
1007
                    ),
1✔
1008
                    gaussian(
1✔
1009
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(1.5, 1.5)),
1✔
1010
                        1.0
1✔
1011
                    ),
1✔
1012
                    gaussian(
1✔
1013
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 0.5)),
1✔
1014
                        1.0
1✔
1015
                    ),
1✔
1016
                    gaussian(
1✔
1017
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(1.5, 0.5)),
1✔
1018
                        1.0
1✔
1019
                    )
1✔
1020
                ],
1✔
1021
            ]
1✔
1022
        );
1✔
1023
    }
1✔
1024

1025
    #[tokio::test]
1026
    async fn density_radius_overlap() {
1✔
1027
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
1028
            TilingSpecification::new([0., 0.].into(), [2, 2].into()),
1✔
1029
        );
1✔
1030
        let rasterization = Rasterization {
1✔
1031
            params: GridOrDensity::Density(DensityParams {
1✔
1032
                cutoff: gaussian(1.99, 1.0) / gaussian(0., 1.0),
1✔
1033
                stddev: 1.0,
1✔
1034
            }),
1✔
1035
            sources: SingleVectorSource {
1✔
1036
                vector: MockPointSource {
1✔
1037
                    params: MockPointSourceParams {
1✔
1038
                        points: vec![(-1., 1.).into(), (1., 1.).into()],
1✔
1039
                    },
1✔
1040
                }
1✔
1041
                .boxed(),
1✔
1042
            },
1✔
1043
        }
1✔
1044
        .boxed()
1✔
1045
        .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1046
        .await
1✔
1047
        .unwrap();
1✔
1048

1✔
1049
        let query = RasterQueryRectangle {
1✔
1050
            spatial_bounds: SpatialPartition2D::new([-2., 2.].into(), [2., 0.].into()).unwrap(),
1✔
1051
            time_interval: Default::default(),
1✔
1052
            spatial_resolution: SpatialResolution { x: 1.0, y: 1.0 },
1✔
1053
            attributes: BandSelection::first(),
1✔
1054
        };
1✔
1055

1✔
1056
        let res = get_results(rasterization, query).await;
1✔
1057

1✔
1058
        assert_eq!(
1✔
1059
            res,
1✔
1060
            vec![
1✔
1061
                vec![
1✔
1062
                    gaussian(
1✔
1063
                        Coordinate2D::new(-1., 1.)
1✔
1064
                            .euclidean_distance(&Coordinate2D::new(-1.5, 1.5)),
1✔
1065
                        1.0
1✔
1066
                    ),
1✔
1067
                    gaussian(
1✔
1068
                        Coordinate2D::new(-1., 1.)
1✔
1069
                            .euclidean_distance(&Coordinate2D::new(-0.5, 1.5)),
1✔
1070
                        1.0
1✔
1071
                    ) + gaussian(
1✔
1072
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(-0.5, 1.5)),
1✔
1073
                        1.0
1✔
1074
                    ),
1✔
1075
                    gaussian(
1✔
1076
                        Coordinate2D::new(-1., 1.)
1✔
1077
                            .euclidean_distance(&Coordinate2D::new(-1.5, 0.5)),
1✔
1078
                        1.0
1✔
1079
                    ),
1✔
1080
                    gaussian(
1✔
1081
                        Coordinate2D::new(-1., 1.)
1✔
1082
                            .euclidean_distance(&Coordinate2D::new(-0.5, 0.5)),
1✔
1083
                        1.0
1✔
1084
                    ) + gaussian(
1✔
1085
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(-0.5, 0.5)),
1✔
1086
                        1.0
1✔
1087
                    )
1✔
1088
                ],
1✔
1089
                vec![
1✔
1090
                    gaussian(
1✔
1091
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 1.5)),
1✔
1092
                        1.0
1✔
1093
                    ) + gaussian(
1✔
1094
                        Coordinate2D::new(-1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 1.5)),
1✔
1095
                        1.0
1✔
1096
                    ),
1✔
1097
                    gaussian(
1✔
1098
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(1.5, 1.5)),
1✔
1099
                        1.0
1✔
1100
                    ),
1✔
1101
                    gaussian(
1✔
1102
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 0.5)),
1✔
1103
                        1.0
1✔
1104
                    ) + gaussian(
1✔
1105
                        Coordinate2D::new(-1., 1.).euclidean_distance(&Coordinate2D::new(0.5, 0.5)),
1✔
1106
                        1.0
1✔
1107
                    ),
1✔
1108
                    gaussian(
1✔
1109
                        Coordinate2D::new(1., 1.).euclidean_distance(&Coordinate2D::new(1.5, 0.5)),
1✔
1110
                        1.0
1✔
1111
                    )
1✔
1112
                ],
1✔
1113
            ]
1✔
1114
        );
1✔
1115
    }
1✔
1116
}
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