From 991c60e802d5ccd0bfbef2741581361ab0ed219b Mon Sep 17 00:00:00 2001 From: Michael Mattig Date: Thu, 2 Sep 2021 16:14:25 +0200 Subject: [PATCH 01/10] produce empty tiles when querying outside of area of use of projection (wip) --- operators/src/processing/reprojection.rs | 146 ++++++++++++++++++++++- 1 file changed, 140 insertions(+), 6 deletions(-) diff --git a/operators/src/processing/reprojection.rs b/operators/src/processing/reprojection.rs index d7b3bc2df..e79669df6 100644 --- a/operators/src/processing/reprojection.rs +++ b/operators/src/processing/reprojection.rs @@ -12,9 +12,8 @@ use crate::{ util::{input::RasterOrVectorOperator, Result}, }; use async_trait::async_trait; -use futures::stream::BoxStream; +use futures::stream::{self, BoxStream}; use futures::StreamExt; -use geoengine_datatypes::primitives::Coordinate2D; use geoengine_datatypes::{ operations::reproject::{ suggest_pixel_size_from_diag_cross_projected, CoordinateProjection, CoordinateProjector, @@ -22,9 +21,10 @@ use geoengine_datatypes::{ }, primitives::{AxisAlignedRectangle, SpatialResolution}, primitives::{BoundingBox2D, SpatialPartition2D}, - raster::{Pixel, RasterTile2D, TilingSpecification}, + raster::{GridOrEmpty, Pixel, RasterTile2D, TilingSpecification}, spatial_reference::SpatialReference, }; +use geoengine_datatypes::{primitives::Coordinate2D, raster::EmptyGrid2D}; use num_traits::AsPrimitive; use serde::{Deserialize, Serialize}; use snafu::ResultExt; @@ -443,6 +443,7 @@ where impl RasterReprojectionProcessor where Q: RasterQueryProcessor, + P: Pixel, { pub fn new( source: Q, @@ -477,6 +478,25 @@ where spatial_resolution: query.spatial_resolution, }) } + + /// create a stream of `EmptyTile`s for the given query rectangle + fn no_data_stream<'a>( + query: QueryRectangle, + tiling_spec: TilingSpecification, + no_data_value: P, + ) -> BoxStream<'a, Result>> { + let iter = tiling_spec + .strategy(query.spatial_resolution.x, query.spatial_resolution.y) + .tile_information_iterator(query.spatial_bounds) + .map(move |tile| { + Ok(RasterTile2D::new_with_tile_info( + query.time_interval, + tile, + GridOrEmpty::Empty(EmptyGrid2D::new(tile.tile_size_in_pixels, no_data_value)), + )) + }); + stream::iter(iter).boxed() + } } #[async_trait] @@ -496,6 +516,22 @@ where let projector_source_target = CoordinateProjector::from_known_srs(self.from, self.to)?; let projector_target_source = CoordinateProjector::from_known_srs(self.to, self.from)?; + // if query does not intesect with source area of use, return stream with `EmptyTile`s + let query_rect = + match Self::clip_query(query, &projector_source_target, &projector_target_source) { + Ok(query_rect) => query_rect, + Err(Error::DataType { + source: geoengine_datatypes::error::Error::SpatialBoundsDoNotIntersect { .. }, + }) => { + return Ok(Self::no_data_stream( + query, + self.tiling_spec, + self.no_data_and_fill_value, + )) + } + Err(e) => return Err(e), + }; + let sub_query_spec = TileReprojectionSubQuery { in_srs: self.from, out_srs: self.to, @@ -511,7 +547,7 @@ where }; let s = RasterSubQueryAdapter::<'a, P, _, _>::new( &self.source, - Self::clip_query(query, &projector_source_target, &projector_target_source)?, + query_rect, self.tiling_spec, ctx, sub_query_spec, @@ -523,11 +559,13 @@ where #[cfg(test)] mod tests { + use std::path::PathBuf; + use crate::{ engine::{QueryRectangle, VectorOperator}, source::{ - FileNotFoundHandling, GdalDatasetParameters, GdalMetaDataRegular, GdalSource, - GdalSourceParameters, + FileNotFoundHandling, GdalDatasetParameters, GdalMetaDataRegular, GdalMetaDataStatic, + GdalSource, GdalSourceParameters, }, util::gdal::{add_ndvi_dataset, raster_dir}, }; @@ -1110,4 +1148,100 @@ mod tests { assert!(1. - (result_res.x / res_4326.x).abs() < 0.02); assert!(1. - (result_res.y / res_4326.y).abs() < 0.02); } + + #[tokio::test] + async fn query_outside_projection_area_of_use_produces_empty_tiles() { + let mut exe_ctx = MockExecutionContext::default(); + let query_ctx = MockQueryContext::default(); + + let m = GdalMetaDataStatic { + time: Some(TimeInterval::default()), + params: GdalDatasetParameters { + file_path: PathBuf::new(), + rasterband_channel: 1, + geo_transform: GeoTransform::new( + (166_021.44, 9_329_005.188).into(), + (534_994.66 - 166_021.444) / 100., + -9_329_005.18 / 100., + ), + width: 100, + height: 100, + file_not_found_handling: FileNotFoundHandling::NoData, + no_data_value: Some(0.), + properties_mapping: None, + gdal_open_options: None, + }, + result_descriptor: RasterResultDescriptor { + data_type: RasterDataType::U8, + spatial_reference: SpatialReference::new(SpatialReferenceAuthority::Epsg, 32636) + .into(), + measurement: Measurement::Unitless, + no_data_value: Some(0.), + }, + }; + + let id: DatasetId = InternalDatasetId::new().into(); + exe_ctx.add_meta_data(id.clone(), Box::new(m)); + + exe_ctx.tiling_specification = + TilingSpecification::new((0.0, 0.0).into(), [600, 600].into()); + + let output_shape: GridShape2D = [1000, 1000].into(); + let output_bounds = + SpatialPartition2D::new_unchecked((-180., 0.).into(), (180., -90.).into()); + let time_interval = TimeInterval::new_instant(1_388_534_400_000).unwrap(); // 2014-01-01 + + let gdal_op = GdalSource { + params: GdalSourceParameters { + dataset: id.clone(), + }, + } + .boxed(); + + let initialized_operator = RasterOperator::boxed(Reprojection { + params: ReprojectionParams { + target_spatial_reference: SpatialReference::epsg_4326(), + }, + sources: SingleRasterOrVectorSource { + source: gdal_op.into(), + }, + }) + .initialize(&exe_ctx) + .await + .unwrap(); + + let x_query_resolution = output_bounds.size_x() / output_shape.axis_size_x() as f64; + let y_query_resolution = output_bounds.size_y() / (output_shape.axis_size_y()) as f64; + let spatial_resolution = + SpatialResolution::new_unchecked(x_query_resolution, y_query_resolution); + + let qp = initialized_operator + .query_processor() + .unwrap() + .get_u8() + .unwrap(); + + let result = qp + .raster_query( + QueryRectangle { + spatial_bounds: output_bounds, + time_interval, + spatial_resolution, + }, + &query_ctx, + ) + .await + .unwrap() + .map(Result::unwrap) + .collect::>() + .await; + + // TODO: fix this test + assert_eq!(result.len(), 4); + + for r in result { + dbg!(r.tile_information()); + // assert!(r.is_empty()); + } + } } From f4d5987b1288d8b6b1d967e2b7f7e8438e54a0a1 Mon Sep 17 00:00:00 2001 From: Michael Mattig Date: Thu, 9 Sep 2021 11:51:23 +0200 Subject: [PATCH 02/10] fix test --- datatypes/src/raster/tiling.rs | 3 +++ operators/src/processing/reprojection.rs | 8 +++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/datatypes/src/raster/tiling.rs b/datatypes/src/raster/tiling.rs index 2f5bef05e..25904285e 100644 --- a/datatypes/src/raster/tiling.rs +++ b/datatypes/src/raster/tiling.rs @@ -23,6 +23,9 @@ impl TilingSpecification { /// create a `TilingStrategy` from self and pixel sizes pub fn strategy(self, x_pixel_size: f64, y_pixel_size: f64) -> TilingStrategy { + debug_assert!(x_pixel_size > 0.0); + debug_assert!(y_pixel_size < 0.0); + TilingStrategy::new_with_tiling_spec(self, x_pixel_size, y_pixel_size) } } diff --git a/operators/src/processing/reprojection.rs b/operators/src/processing/reprojection.rs index aa686c86e..b0707b111 100644 --- a/operators/src/processing/reprojection.rs +++ b/operators/src/processing/reprojection.rs @@ -486,7 +486,7 @@ where no_data_value: P, ) -> BoxStream<'a, Result>> { let iter = tiling_spec - .strategy(query.spatial_resolution.x, query.spatial_resolution.y) + .strategy(query.spatial_resolution.x, -query.spatial_resolution.y) .tile_information_iterator(query.spatial_bounds) .map(move |tile| { Ok(RasterTile2D::new_with_tile_info( @@ -516,7 +516,7 @@ where let projector_source_target = CoordinateProjector::from_known_srs(self.from, self.to)?; let projector_target_source = CoordinateProjector::from_known_srs(self.to, self.from)?; - // if query does not intesect with source area of use, return stream with `EmptyTile`s + // if query does not intersect with source area of use, return stream with `EmptyTile`s let query_rect = match Self::clip_query(query, &projector_source_target, &projector_target_source) { Ok(query_rect) => query_rect, @@ -1242,12 +1242,10 @@ mod tests { .collect::>() .await; - // TODO: fix this test assert_eq!(result.len(), 4); for r in result { - dbg!(r.tile_information()); - // assert!(r.is_empty()); + assert!(r.is_empty()); } } From 0c76d9ef4b8adfdcdc39c80dce51b441b23824c1 Mon Sep 17 00:00:00 2001 From: Michael Mattig Date: Thu, 9 Sep 2021 17:07:25 +0200 Subject: [PATCH 03/10] fix coordinate to pixel calculation and allow the raster subquery to produce empty tiles when the query rectangle cannot be translated --- datatypes/src/raster/geo_transform.rs | 48 +++++- datatypes/src/raster/tiling.rs | 4 +- .../src/adapters/raster_subquery_adapter.rs | 158 ++++++++++-------- .../mean_aggregation_subquery.rs | 6 +- .../min_max_first_last_subquery.rs | 6 +- .../MOD13A2_M_NDVI_2014-04-01_tile-20.rst | Bin 202500 -> 202500 bytes services/test-data/wms/get_map_ndvi.png | Bin 26066 -> 26120 bytes 7 files changed, 147 insertions(+), 75 deletions(-) diff --git a/datatypes/src/raster/geo_transform.rs b/datatypes/src/raster/geo_transform.rs index ef8ee78bf..9717e1598 100644 --- a/datatypes/src/raster/geo_transform.rs +++ b/datatypes/src/raster/geo_transform.rs @@ -108,8 +108,10 @@ impl GeoTransform { /// #[inline] pub fn coordinate_to_grid_idx_2d(&self, coord: Coordinate2D) -> GridIdx2D { - let grid_x_index = ((coord.x - self.origin_coordinate.x) / self.x_pixel_size) as isize; - let grid_y_index = ((coord.y - self.origin_coordinate.y) / self.y_pixel_size) as isize; + let grid_x_index = + ((coord.x - self.origin_coordinate.x) / self.x_pixel_size).floor() as isize; + let grid_y_index = + ((coord.y - self.origin_coordinate.y) / self.y_pixel_size).floor() as isize; [grid_y_index, grid_x_index].into() } @@ -273,6 +275,48 @@ mod tests { ); } + #[test] + fn geo_transform_coordinate_2d_to_global_grid_2d() { + let geo_transform = GeoTransform::new_with_coordinate_x_y(0.0, 1.0, 0.0, -1.0); + assert_eq!( + geo_transform.coordinate_to_grid_idx_2d((0.0, 0.0).into()), + GridIdx2D::new([0, 0]) + ); + assert_eq!( + geo_transform.coordinate_to_grid_idx_2d((0.5, 0.0).into()), + GridIdx2D::new([0, 0]) + ); + assert_eq!( + geo_transform.coordinate_to_grid_idx_2d((0.5, 0.5).into()), + GridIdx2D::new([-1, 0]) + ); + assert_eq!( + geo_transform.coordinate_to_grid_idx_2d((0.0, 0.5).into()), + GridIdx2D::new([-1, 0]) + ); + assert_eq!( + geo_transform.coordinate_to_grid_idx_2d((0.5, -0.5).into()), + GridIdx2D::new([0, 0]) + ); + assert_eq!( + geo_transform.coordinate_to_grid_idx_2d((-0.5, 0.5).into()), + GridIdx2D::new([-1, -1]) + ); + assert_eq!( + geo_transform.coordinate_to_grid_idx_2d((-0.5, -0.5).into()), + GridIdx2D::new([0, -1]) + ); + + assert_eq!( + geo_transform.coordinate_to_grid_idx_2d((1.5, -0.5).into()), + GridIdx2D::new([0, 1]) + ); + assert_eq!( + geo_transform.coordinate_to_grid_idx_2d((-1.5, 1.5).into()), + GridIdx2D::new([-2, -2]) + ); + } + #[test] fn pixel_center() { let geo_transform = GeoTransform::new_with_coordinate_x_y(5.0, 1.0, 5.0, -1.0); diff --git a/datatypes/src/raster/tiling.rs b/datatypes/src/raster/tiling.rs index 25904285e..390181779 100644 --- a/datatypes/src/raster/tiling.rs +++ b/datatypes/src/raster/tiling.rs @@ -69,7 +69,9 @@ impl TilingStrategy { let lr_idx = self .geo_transform .coordinate_to_grid_idx_2d(partition.lower_right()); - + // TODO: only subtract if lower right coordinate is exactly on pixel edge because + // it is not included in the partition. We don't want to lose the pixel if + // it is actually contained in the partition lr_idx - 1 } diff --git a/operators/src/adapters/raster_subquery_adapter.rs b/operators/src/adapters/raster_subquery_adapter.rs index aebcfce18..0838a59ac 100644 --- a/operators/src/adapters/raster_subquery_adapter.rs +++ b/operators/src/adapters/raster_subquery_adapter.rs @@ -10,6 +10,7 @@ use futures::{ }; use futures::{stream::FusedStream, Future}; use geoengine_datatypes::primitives::{SpatialPartition2D, SpatialPartitioned}; +use geoengine_datatypes::raster::{EmptyGrid2D, GridOrEmpty}; use geoengine_datatypes::{ error::Error::{GridIndexOutOfBounds, InvalidGridIndex}, operations::reproject::{ @@ -166,6 +167,7 @@ where { type Item = Result>; + #[allow(clippy::too_many_lines)] fn poll_next( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, @@ -198,64 +200,79 @@ where *this.time_start, )?; - if this.running_query.as_ref().is_none() && this.running_fold.as_ref().is_none() { - // there is no query and no stream pending - debug!("New running_query for: {:?}", &tile_query_rectangle); + let tile_accu_result = if let Some(tile_query_rectangle) = tile_query_rectangle { + if this.running_query.as_ref().is_none() && this.running_fold.as_ref().is_none() { + // there is no query and no stream pending + debug!("New running_query for: {:?}", &tile_query_rectangle); - let tile_query_stream = this - .source - .query(tile_query_rectangle, *this.query_ctx) - .boxed(); + let tile_query_stream = this + .source + .query(tile_query_rectangle, *this.query_ctx) + .boxed(); - this.running_query.set(Some(tile_query_stream)); - } - - let query_future_result = - if let Some(query_future) = this.running_query.as_mut().as_pin_mut() { - // TODO: match block? - let query_result: Result>>> = - ready!(query_future.poll(cx)); - Some(query_result) - } else { - None - }; - - this.running_query.set(None); - - match query_future_result { - Some(Ok(tile_query_stream)) => { - let tile_folding_accu = this - .sub_query - .new_fold_accu(fold_tile_spec, tile_query_rectangle)?; - - debug!("New running_fold: {:?}", &tile_query_rectangle); - let tile_folding_stream = - tile_query_stream.try_fold(tile_folding_accu, this.sub_query.fold_method()); - - this.running_fold.set(Some(tile_folding_stream)); - } - Some(Err(err)) => { - debug!("Tile fold stream returned error: {:?}", &err); - *this.ended = true; - return Poll::Ready(Some(Err(err))); + this.running_query.set(Some(tile_query_stream)); } - None => {} // there is no result but there meight be a running fold... - } - let future_result = match this.running_fold.as_mut().as_pin_mut() { - Some(fut) => { - ready!(fut.poll(cx)) - } - None => { - debug!("running_fold is None"); - return Poll::Ready(None); // should initialize next tile query? + let query_future_result = + if let Some(query_future) = this.running_query.as_mut().as_pin_mut() { + // TODO: match block? + let query_result: Result>>> = + ready!(query_future.poll(cx)); + Some(query_result) + } else { + None + }; + + this.running_query.set(None); + + match query_future_result { + Some(Ok(tile_query_stream)) => { + let tile_folding_accu = this + .sub_query + .new_fold_accu(fold_tile_spec, tile_query_rectangle)?; + + debug!("New running_fold: {:?}", &tile_query_rectangle); + let tile_folding_stream = + tile_query_stream.try_fold(tile_folding_accu, this.sub_query.fold_method()); + + this.running_fold.set(Some(tile_folding_stream)); + } + Some(Err(err)) => { + debug!("Tile fold stream returned error: {:?}", &err); + *this.ended = true; + return Poll::Ready(Some(Err(err))); + } + None => {} // there is no result but there might be a running fold... } - }; - // set the running future to None --> will create a new one in the next call - this.running_fold.set(None); + let future_result = match this.running_fold.as_mut().as_pin_mut() { + Some(fut) => { + ready!(fut.poll(cx)) + } + None => { + debug!("running_fold is None"); + return Poll::Ready(None); // should initialize next tile query? + } + }; - let tile_accu_result = future_result.map(FoldTileAccu::into_tile); + // set the running future to None --> will create a new one in the next call + this.running_fold.set(None); + + let tile_accu_result: Result> = + future_result.map(FoldTileAccu::into_tile); + + tile_accu_result + } else { + // no sub query query rectangle was produced for the current tile, thus output an empty tile + Ok(RasterTile2D::new_with_tile_info( + this.query_rect.time_interval, + fold_tile_spec, + GridOrEmpty::Empty(EmptyGrid2D::::new( + fold_tile_spec.tile_size_in_pixels, + PixelType::zero(), // TODO: get no data value + )), + )) + }; // if we produced a tile: get the end of the current time slot (must be the same for all tiles in the slot) if let Ok(ref r) = tile_accu_result { @@ -436,13 +453,14 @@ where query_rect: RasterQueryRectangle, ) -> Result; - /// This method generates a `QueryRectangle` for a tile-specific sub-query + /// This method generates `Some(QueryRectangle)` for a tile-specific sub-query or `None` if the `query_rect` cannot be translated. + /// In the latter case an `EmptyTile` will be produced for the sub query instead of querying the source. fn tile_query_rectangle( &self, tile_info: TileInformation, query_rect: RasterQueryRectangle, start_time: TimeInstance, - ) -> Result; + ) -> Result>; /// This method generates the method which combines the accumulator and each tile of the sub-query stream in the `TryFold` stream adapter. fn fold_method(&self) -> Self::FoldMethod; @@ -509,12 +527,12 @@ where tile_info: TileInformation, query_rect: RasterQueryRectangle, start_time: TimeInstance, - ) -> Result { - Ok(RasterQueryRectangle { + ) -> Result> { + Ok(Some(RasterQueryRectangle { spatial_bounds: tile_info.spatial_partition(), time_interval: TimeInterval::new_instant(start_time)?, spatial_resolution: query_rect.spatial_resolution, - }) + })) } fn fold_method(&self) -> Self::FoldMethod { @@ -609,23 +627,31 @@ where }) } + // TODO: return Result> fn tile_query_rectangle( &self, tile_info: TileInformation, query_rect: RasterQueryRectangle, start_time: TimeInstance, - ) -> Result { + ) -> Result> { let proj = CoordinateProjector::from_known_srs(self.out_srs, self.in_srs)?; - Ok(RasterQueryRectangle { - spatial_bounds: tile_info - .spatial_partition() - .intersection(&query_rect.spatial_partition()) - .expect("should not be empty") - .reproject(&proj)?, - time_interval: TimeInterval::new_instant(start_time)?, - spatial_resolution: self.in_spatial_res, - }) + let spatial_bounds = tile_info + .spatial_partition() + .intersection(&query_rect.spatial_partition()) + .expect("should not be empty") + .reproject(&proj); + + if let Ok(spatial_bounds) = spatial_bounds { + Ok(Some(RasterQueryRectangle { + spatial_bounds, + time_interval: TimeInterval::new_instant(start_time)?, + spatial_resolution: self.in_spatial_res, + })) + } else { + // output query rectangle is not valid in source projection => produce empty tile + Ok(None) + } } fn fold_method(&self) -> Self::FoldMethod { diff --git a/operators/src/processing/temporal_raster_aggregation/mean_aggregation_subquery.rs b/operators/src/processing/temporal_raster_aggregation/mean_aggregation_subquery.rs index 73cd9afb6..09a247709 100644 --- a/operators/src/processing/temporal_raster_aggregation/mean_aggregation_subquery.rs +++ b/operators/src/processing/temporal_raster_aggregation/mean_aggregation_subquery.rs @@ -222,12 +222,12 @@ where tile_info: TileInformation, query_rect: RasterQueryRectangle, start_time: TimeInstance, - ) -> Result { - Ok(RasterQueryRectangle { + ) -> Result> { + Ok(Some(RasterQueryRectangle { spatial_bounds: tile_info.spatial_partition(), spatial_resolution: query_rect.spatial_resolution, time_interval: TimeInterval::new(start_time, (start_time + self.step)?)?, - }) + })) } fn fold_method(&self) -> Self::FoldMethod { diff --git a/operators/src/processing/temporal_raster_aggregation/min_max_first_last_subquery.rs b/operators/src/processing/temporal_raster_aggregation/min_max_first_last_subquery.rs index 9b5107374..0d21744b6 100644 --- a/operators/src/processing/temporal_raster_aggregation/min_max_first_last_subquery.rs +++ b/operators/src/processing/temporal_raster_aggregation/min_max_first_last_subquery.rs @@ -357,12 +357,12 @@ where tile_info: TileInformation, query_rect: RasterQueryRectangle, start_time: TimeInstance, - ) -> Result { - Ok(QueryRectangle { + ) -> Result> { + Ok(Some(QueryRectangle { spatial_bounds: tile_info.spatial_partition(), spatial_resolution: query_rect.spatial_resolution, time_interval: TimeInterval::new(start_time, (start_time + self.step)?)?, - }) + })) } fn fold_method(&self) -> Self::FoldMethod { diff --git a/operators/test-data/raster/modis_ndvi/projected_3857/MOD13A2_M_NDVI_2014-04-01_tile-20.rst b/operators/test-data/raster/modis_ndvi/projected_3857/MOD13A2_M_NDVI_2014-04-01_tile-20.rst index 8f67c88e092501b44509bd1c86e43377d8db0c40..60066cb49318ca11df3ce67fac53d50e8429ca0a 100644 GIT binary patch literal 202500 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 VPni7jgulP>X`0}o;f#jCMG7% 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 IA86e2JAN`8_W%F@ diff --git a/services/test-data/wms/get_map_ndvi.png b/services/test-data/wms/get_map_ndvi.png index ad5f4ef7050d7311e72919c9236d7d8758dd7c0a..98118e57ef5a11db45558dc0cabe2ec987d3b9ca 100644 GIT binary patch literal 26120 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%0AgnQDd)7oC% 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 Date: Thu, 9 Sep 2021 17:24:30 +0200 Subject: [PATCH 04/10] debug assert that that x pixel size is always positive and y always negative --- datatypes/src/raster/geo_transform.rs | 18 ++++++++++++++++-- datatypes/src/raster/macros_raster_tile.rs | 2 +- datatypes/src/raster/operations/blit.rs | 4 ++-- datatypes/src/raster/raster_tile.rs | 4 ++-- datatypes/src/raster/tiling.rs | 14 +++++++------- operators/src/processing/meteosat/mod.rs | 10 +++++----- operators/src/processing/reprojection.rs | 10 +++++----- operators/src/source/gdal_source.rs | 14 +++++--------- operators/src/util/gdal.rs | 6 +----- .../datasets/external/sentinel_s2_l2a_cogs.rs | 10 +++++----- 10 files changed, 49 insertions(+), 43 deletions(-) diff --git a/datatypes/src/raster/geo_transform.rs b/datatypes/src/raster/geo_transform.rs index 9717e1598..16ec2382b 100644 --- a/datatypes/src/raster/geo_transform.rs +++ b/datatypes/src/raster/geo_transform.rs @@ -15,8 +15,8 @@ pub type GdalGeoTransform = [f64; 6]; #[serde(rename_all = "camelCase")] pub struct GeoTransform { pub origin_coordinate: Coordinate2D, - pub x_pixel_size: f64, - pub y_pixel_size: f64, + x_pixel_size: f64, + y_pixel_size: f64, } impl GeoTransform { @@ -32,6 +32,9 @@ impl GeoTransform { /// #[inline] pub fn new(origin_coordinate: Coordinate2D, x_pixel_size: f64, y_pixel_size: f64) -> Self { + debug_assert!(x_pixel_size > 0.0); + debug_assert!(y_pixel_size < 0.0); + Self { origin_coordinate, x_pixel_size, @@ -55,6 +58,9 @@ impl GeoTransform { origin_coordinate_y: f64, y_pixel_size: f64, ) -> Self { + debug_assert!(x_pixel_size > 0.0); + debug_assert!(y_pixel_size < 0.0); + Self { origin_coordinate: (origin_coordinate_x, origin_coordinate_y).into(), x_pixel_size, @@ -62,6 +68,14 @@ impl GeoTransform { } } + pub fn x_pixel_size(&self) -> f64 { + self.x_pixel_size + } + + pub fn y_pixel_size(&self) -> f64 { + self.y_pixel_size + } + /// Transforms a grid coordinate (row, column) ~ (y, x) into a SRS coordinate (x,y) /// The resulting coordinate is the upper left coordinate of the pixel /// See GDAL documentation for more details (including the two ignored parameters): diff --git a/datatypes/src/raster/macros_raster_tile.rs b/datatypes/src/raster/macros_raster_tile.rs index 875e68d8a..2ab42246a 100644 --- a/datatypes/src/raster/macros_raster_tile.rs +++ b/datatypes/src/raster/macros_raster_tile.rs @@ -452,7 +452,7 @@ mod tests { let typed_raster_a = TypedRasterTile2D::U32(RasterTile2D::new( TimeInterval::default(), [0, 0].into(), - [1.0, 1.0, 0.0, 1.0, 0.0, 1.0].into(), + [1.0, 1.0, 0.0, 1.0, 0.0, -1.0].into(), Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], None) .unwrap() .into(), diff --git a/datatypes/src/raster/operations/blit.rs b/datatypes/src/raster/operations/blit.rs index aa62fd66a..4b6a6fdad 100644 --- a/datatypes/src/raster/operations/blit.rs +++ b/datatypes/src/raster/operations/blit.rs @@ -19,8 +19,8 @@ impl Blit> for MaterializedRasterTile2D { // TODO: ensure pixels are aligned ensure!( - (self.geo_transform().x_pixel_size == source.geo_transform().x_pixel_size) - && (self.geo_transform().y_pixel_size == source.geo_transform().y_pixel_size), + (self.geo_transform().x_pixel_size() == source.geo_transform().x_pixel_size()) + && (self.geo_transform().y_pixel_size() == source.geo_transform().y_pixel_size()), error::Blit { details: "Incompatible pixel size" } diff --git a/datatypes/src/raster/raster_tile.rs b/datatypes/src/raster/raster_tile.rs index 2dfdcf6ce..43a5244ae 100644 --- a/datatypes/src/raster/raster_tile.rs +++ b/datatypes/src/raster/raster_tile.rs @@ -78,8 +78,8 @@ where GeoTransform::new( tile_upper_left_coord, - self.global_geo_transform.x_pixel_size, - self.global_geo_transform.y_pixel_size, + self.global_geo_transform.x_pixel_size(), + self.global_geo_transform.y_pixel_size(), ) } } diff --git a/datatypes/src/raster/tiling.rs b/datatypes/src/raster/tiling.rs index 390181779..98ecc3047 100644 --- a/datatypes/src/raster/tiling.rs +++ b/datatypes/src/raster/tiling.rs @@ -145,11 +145,11 @@ impl TileInformation { Self { tile_size_in_pixels: shape, global_tile_position: [0, 0].into(), - global_geo_transform: GeoTransform { - origin_coordinate: partition.upper_left(), - x_pixel_size: partition.size_x() / shape.axis_size_x() as f64, - y_pixel_size: -partition.size_y() / shape.axis_size_y() as f64, - }, + global_geo_transform: GeoTransform::new( + partition.upper_left(), + partition.size_x() / shape.axis_size_x() as f64, + -partition.size_y() / shape.axis_size_y() as f64, + ), } } @@ -208,8 +208,8 @@ impl TileInformation { GeoTransform::new( tile_upper_left_coord, - self.global_geo_transform.x_pixel_size, - self.global_geo_transform.y_pixel_size, + self.global_geo_transform.x_pixel_size(), + self.global_geo_transform.y_pixel_size(), ) } } diff --git a/operators/src/processing/meteosat/mod.rs b/operators/src/processing/meteosat/mod.rs index 2bf1d0ee0..df991773d 100644 --- a/operators/src/processing/meteosat/mod.rs +++ b/operators/src/processing/meteosat/mod.rs @@ -215,11 +215,11 @@ mod test_util { params: GdalDatasetParameters { file_path: raster_dir().join("msg/%%%_START_TIME_%%%.tif"), rasterband_channel: 1, - geo_transform: GeoTransform { - origin_coordinate: (-5_570_248.477_339_745, 5_570_248.477_339_745).into(), - x_pixel_size: 3_000.403_165_817_261, - y_pixel_size: -3_000.403_165_817_261, - }, + geo_transform: GeoTransform::new( + (-5_570_248.477_339_745, 5_570_248.477_339_745).into(), + 3_000.403_165_817_261, + -3_000.403_165_817_261, + ), width: 3712, height: 3712, file_not_found_handling: FileNotFoundHandling::Error, diff --git a/operators/src/processing/reprojection.rs b/operators/src/processing/reprojection.rs index b0707b111..86d5a4404 100644 --- a/operators/src/processing/reprojection.rs +++ b/operators/src/processing/reprojection.rs @@ -1035,11 +1035,11 @@ mod tests { file_path: raster_dir() .join("modis_ndvi/projected_3857/MOD13A2_M_NDVI_%%%_START_TIME_%%%.TIFF"), rasterband_channel: 1, - geo_transform: GeoTransform { - origin_coordinate: (20_037_508.342_789_244, 19_971_868.880_408_563).into(), - x_pixel_size: 14_052.950_258_048_739, - y_pixel_size: -14_057.881_117_788_405, - }, + geo_transform: GeoTransform::new( + (20_037_508.342_789_244, 19_971_868.880_408_563).into(), + 14_052.950_258_048_739, + -14_057.881_117_788_405, + ), width: 1000, height: 1000, file_not_found_handling: FileNotFoundHandling::Error, diff --git a/operators/src/source/gdal_source.rs b/operators/src/source/gdal_source.rs index 7b06e7373..cd5ff064d 100755 --- a/operators/src/source/gdal_source.rs +++ b/operators/src/source/gdal_source.rs @@ -209,8 +209,8 @@ impl SpatialPartitioned for GdalDatasetParameters { fn spatial_partition(&self) -> SpatialPartition2D { let lower_right_coordinate = self.geo_transform.origin_coordinate + Coordinate2D::from(( - self.geo_transform.x_pixel_size * self.width as f64, - self.geo_transform.y_pixel_size * self.height as f64, + self.geo_transform.x_pixel_size() * self.width as f64, + self.geo_transform.y_pixel_size() * self.height as f64, )); SpatialPartition2D::new_unchecked( self.geo_transform.origin_coordinate, @@ -574,7 +574,7 @@ where let geo_transform = info.params.geo_transform; // adjust the spatial resolution to the sign of the geotransform - let x_signed = if geo_transform.x_pixel_size.is_sign_positive() + let x_signed = if geo_transform.x_pixel_size().is_sign_positive() && spatial_resolution.x.is_sign_positive() { spatial_resolution.x @@ -582,7 +582,7 @@ where spatial_resolution.x * -1.0 }; - let y_signed = if geo_transform.y_pixel_size.is_sign_positive() + let y_signed = if geo_transform.y_pixel_size().is_sign_positive() && spatial_resolution.y.is_sign_positive() { spatial_resolution.y @@ -894,11 +894,7 @@ mod tests { &GdalDatasetParameters { file_path: raster_dir().join("modis_ndvi/MOD13A2_M_NDVI_2014-01-01.TIFF"), rasterband_channel: 1, - geo_transform: GeoTransform { - origin_coordinate: (-180., 90.).into(), - x_pixel_size: 0.1, - y_pixel_size: -0.1, - }, + geo_transform: GeoTransform::new((-180., 90.).into(), 0.1, -0.1), width: 3600, height: 1800, file_not_found_handling: FileNotFoundHandling::NoData, diff --git a/operators/src/util/gdal.rs b/operators/src/util/gdal.rs index 8fac9f3f2..d65e2f812 100644 --- a/operators/src/util/gdal.rs +++ b/operators/src/util/gdal.rs @@ -53,11 +53,7 @@ pub fn create_ndvi_meta_data() -> GdalMetaDataRegular { params: GdalDatasetParameters { file_path: raster_dir().join("modis_ndvi/MOD13A2_M_NDVI_%%%_START_TIME_%%%.TIFF"), rasterband_channel: 1, - geo_transform: GeoTransform { - origin_coordinate: (-180., 90.).into(), - x_pixel_size: 0.1, - y_pixel_size: -0.1, - }, + geo_transform: GeoTransform::new((-180., 90.).into(), 0.1, -0.1), width: 3600, height: 1800, file_not_found_handling: FileNotFoundHandling::NoData, diff --git a/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs b/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs index 4fdc7dabc..3ed568528 100644 --- a/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs +++ b/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs @@ -561,11 +561,11 @@ mod tests { params: GdalDatasetParameters { file_path: "/vsicurl/https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/32/R/PU/2021/1/S2B_32RPU_20210102_0_L2A/B01.tif".into(), rasterband_channel: 1, - geo_transform: GeoTransform { - origin_coordinate: (600_000.0, 3_400_020.0).into(), - x_pixel_size: 60., - y_pixel_size: -60., - }, + geo_transform: GeoTransform::new( + (600_000.0, 3_400_020.0).into(), + 60., + -60., + ), width: 1830, height: 1830, file_not_found_handling: FileNotFoundHandling::NoData, From 48fd875f2bbf226abc91bbe4babe316ccfd3976a Mon Sep 17 00:00:00 2001 From: Michael Mattig Date: Thu, 9 Sep 2021 17:59:46 +0200 Subject: [PATCH 05/10] put no data into raster subquery adapter to be able to produce empty tiles --- .../src/adapters/raster_subquery_adapter.rs | 26 +++++++++++-- operators/src/processing/reprojection.rs | 1 + .../temporal_aggregation_operator.rs | 37 +++++++++++++++++-- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/operators/src/adapters/raster_subquery_adapter.rs b/operators/src/adapters/raster_subquery_adapter.rs index 0838a59ac..5b4801239 100644 --- a/operators/src/adapters/raster_subquery_adapter.rs +++ b/operators/src/adapters/raster_subquery_adapter.rs @@ -102,6 +102,8 @@ where ended: bool, /// The `SubQuery` defines what this adapter does. sub_query: SubQuery, + + no_data_value: Option, } impl<'a, PixelType, RasterProcessorType, SubQuery> @@ -118,6 +120,7 @@ where tiling_spec: TilingSpecification, query_ctx: &'a dyn QueryContext, sub_query: SubQuery, + no_data_value: Option, ) -> Self { let tiling_strat = tiling_spec.strategy( query_rect.spatial_resolution.x, @@ -140,6 +143,7 @@ where query_ctx, ended: false, sub_query, + no_data_value, } } } @@ -269,7 +273,7 @@ where fold_tile_spec, GridOrEmpty::Empty(EmptyGrid2D::::new( fold_tile_spec.tile_size_in_pixels, - PixelType::zero(), // TODO: get no data value + this.no_data_value.unwrap_or(PixelType::zero()), )), )) }; @@ -471,12 +475,20 @@ where query: RasterQueryRectangle, ctx: &'a dyn QueryContext, tiling_specification: TilingSpecification, + no_data_value: Option, ) -> RasterSubQueryAdapter<'a, T, S, Self> where S: RasterQueryProcessor, Self: Sized, { - RasterSubQueryAdapter::<'a, T, S, Self>::new(source, query, tiling_specification, ctx, self) + RasterSubQueryAdapter::<'a, T, S, Self>::new( + source, + query, + tiling_specification, + ctx, + self, + no_data_value, + ) } } @@ -757,6 +769,7 @@ mod tests { TileSubQueryIdentity { fold_fn: fold_by_blit_future, }, + no_data_value, ); let res = a .map(Result::unwrap) @@ -856,7 +869,14 @@ mod tests { fold_fn: fold_by_coordinate_lookup_future, in_spatial_res: query_rect.spatial_resolution, }; - let a = RasterSubQueryAdapter::new(&qp, query_rect, tiling_strat, &query_ctx, state_gen); + let a = RasterSubQueryAdapter::new( + &qp, + query_rect, + tiling_strat, + &query_ctx, + state_gen, + no_data_value, + ); let res = a .map(Result::unwrap) .collect::>>() diff --git a/operators/src/processing/reprojection.rs b/operators/src/processing/reprojection.rs index 86d5a4404..552fc883e 100644 --- a/operators/src/processing/reprojection.rs +++ b/operators/src/processing/reprojection.rs @@ -551,6 +551,7 @@ where self.tiling_spec, ctx, sub_query_spec, + Some(self.no_data_and_fill_value), ); Ok(s.boxed()) diff --git a/operators/src/processing/temporal_raster_aggregation/temporal_aggregation_operator.rs b/operators/src/processing/temporal_raster_aggregation/temporal_aggregation_operator.rs index 552a7ed7d..8abd379a3 100644 --- a/operators/src/processing/temporal_raster_aggregation/temporal_aggregation_operator.rs +++ b/operators/src/processing/temporal_raster_aggregation/temporal_aggregation_operator.rs @@ -199,13 +199,25 @@ where no_data_ignoring_fold_future::, P::max_value(), ) - .into_raster_overlap_adapter(&self.source, query, ctx, self.tiling_specification) + .into_raster_overlap_adapter( + &self.source, + query, + ctx, + self.tiling_specification, + self.no_data_value, + ) .boxed()), Aggregation::Min { ignore_no_data: false, } => Ok(self .create_subquery(fold_future::, P::max_value()) - .into_raster_overlap_adapter(&self.source, query, ctx, self.tiling_specification) + .into_raster_overlap_adapter( + &self.source, + query, + ctx, + self.tiling_specification, + self.no_data_value, + ) .boxed()), Aggregation::Max { ignore_no_data: true, @@ -214,13 +226,25 @@ where no_data_ignoring_fold_future::, P::min_value(), ) - .into_raster_overlap_adapter(&self.source, query, ctx, self.tiling_specification) + .into_raster_overlap_adapter( + &self.source, + query, + ctx, + self.tiling_specification, + self.no_data_value, + ) .boxed()), Aggregation::Max { ignore_no_data: false, } => Ok(self .create_subquery(fold_future::, P::min_value()) - .into_raster_overlap_adapter(&self.source, query, ctx, self.tiling_specification) + .into_raster_overlap_adapter( + &self.source, + query, + ctx, + self.tiling_specification, + self.no_data_value, + ) .boxed()), Aggregation::First { ignore_no_data: true, @@ -238,6 +262,7 @@ where query, ctx, self.tiling_specification, + self.no_data_value, ) .boxed()) } @@ -254,6 +279,7 @@ where query, ctx, self.tiling_specification, + self.no_data_value, ) .boxed()) } @@ -273,6 +299,7 @@ where query, ctx, self.tiling_specification, + self.no_data_value, ) .boxed()) } @@ -290,6 +317,7 @@ where query, ctx, self.tiling_specification, + self.no_data_value, ) .boxed()) } @@ -305,6 +333,7 @@ where query, ctx, self.tiling_specification, + self.no_data_value, ) .boxed()) } From 8a77ee55236bfe8d15329ad4a2c03525107420f4 Mon Sep 17 00:00:00 2001 From: Michael Mattig Date: Thu, 9 Sep 2021 20:15:07 +0200 Subject: [PATCH 06/10] split up GeoTransform into internal and external type with different assumptions about pixel sizes and origins --- datatypes/src/raster/geo_transform.rs | 5 +- operators/src/error.rs | 5 ++ operators/src/processing/meteosat/mod.rs | 18 ++--- operators/src/processing/reprojection.rs | 28 ++++--- operators/src/source/gdal_source.rs | 73 ++++++++++++++++--- operators/src/source/mod.rs | 6 +- operators/src/util/gdal.rs | 12 ++- services/src/datasets/external/nature40.rs | 12 ++- services/src/handlers/workflows.rs | 13 ++-- .../datasets/external/sentinel_s2_l2a_cogs.rs | 18 ++--- services/src/pro/handlers/drone_mapping.rs | 12 ++- 11 files changed, 138 insertions(+), 64 deletions(-) diff --git a/datatypes/src/raster/geo_transform.rs b/datatypes/src/raster/geo_transform.rs index 16ec2382b..9130e7917 100644 --- a/datatypes/src/raster/geo_transform.rs +++ b/datatypes/src/raster/geo_transform.rs @@ -10,7 +10,10 @@ use super::{GridBoundingBox2D, GridIdx, GridIdx2D}; /// This is a typedef for the `GDAL GeoTransform`. It represents an affine transformation matrix. pub type GdalGeoTransform = [f64; 6]; -/// The `GeoTransform` is a more user friendly representation of the `GDAL GeoTransform` affine transformation matrix. +/// The `GeoTransform` specifies the relation between pixel coordinates and geographic coordinates. +/// In Geo Engine x pixel size is always postive and y pixel size is always negative. For raster tiles +/// the origin is always the upper left corner. In the global grid for the `TilingStrategy` the origin +/// is always located at (0, 0). #[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct GeoTransform { diff --git a/operators/src/error.rs b/operators/src/error.rs index 278082032..a5672a0e0 100644 --- a/operators/src/error.rs +++ b/operators/src/error.rs @@ -263,6 +263,11 @@ pub enum Error { InvalidGdalFilePath { file_path: PathBuf, }, + + #[snafu(display( + "Raster data sets with a different origin than upper left are currently not supported" + ))] + GeoTransformOrigin, } impl From for Error { diff --git a/operators/src/processing/meteosat/mod.rs b/operators/src/processing/meteosat/mod.rs index df991773d..37d410462 100644 --- a/operators/src/processing/meteosat/mod.rs +++ b/operators/src/processing/meteosat/mod.rs @@ -45,8 +45,8 @@ mod test_util { TimeInterval, TimeStep, }; use geoengine_datatypes::raster::{ - EmptyGrid2D, GeoTransform, Grid2D, GridOrEmpty, RasterDataType, RasterProperties, - RasterPropertiesEntry, RasterPropertiesEntryType, RasterTile2D, TileInformation, + EmptyGrid2D, Grid2D, GridOrEmpty, RasterDataType, RasterProperties, RasterPropertiesEntry, + RasterPropertiesEntryType, RasterTile2D, TileInformation, }; use geoengine_datatypes::spatial_reference::{SpatialReference, SpatialReferenceAuthority}; use geoengine_datatypes::util::Identifier; @@ -58,8 +58,8 @@ mod test_util { use crate::mock::{MockRasterSource, MockRasterSourceParams}; use crate::processing::meteosat::{channel_key, offset_key, satellite_key, slope_key}; use crate::source::{ - FileNotFoundHandling, GdalDatasetParameters, GdalMetaDataRegular, GdalMetadataMapping, - GdalSource, GdalSourceParameters, + FileNotFoundHandling, GdalDatasetGeoTransform, GdalDatasetParameters, GdalMetaDataRegular, + GdalMetadataMapping, GdalSource, GdalSourceParameters, }; use crate::util::gdal::raster_dir; use crate::util::Result; @@ -215,11 +215,11 @@ mod test_util { params: GdalDatasetParameters { file_path: raster_dir().join("msg/%%%_START_TIME_%%%.tif"), rasterband_channel: 1, - geo_transform: GeoTransform::new( - (-5_570_248.477_339_745, 5_570_248.477_339_745).into(), - 3_000.403_165_817_261, - -3_000.403_165_817_261, - ), + geo_transform: GdalDatasetGeoTransform { + origin_coordinate: (-5_570_248.477_339_745, 5_570_248.477_339_745).into(), + x_pixel_size: 3_000.403_165_817_261, + y_pixel_size: -3_000.403_165_817_261, + }, width: 3712, height: 3712, file_not_found_handling: FileNotFoundHandling::Error, diff --git a/operators/src/processing/reprojection.rs b/operators/src/processing/reprojection.rs index 552fc883e..2b20ccb6b 100644 --- a/operators/src/processing/reprojection.rs +++ b/operators/src/processing/reprojection.rs @@ -566,8 +566,8 @@ mod tests { use crate::{ engine::{QueryRectangle, VectorOperator}, source::{ - FileNotFoundHandling, GdalDatasetParameters, GdalMetaDataRegular, GdalMetaDataStatic, - GdalSource, GdalSourceParameters, + FileNotFoundHandling, GdalDatasetGeoTransform, GdalDatasetParameters, + GdalMetaDataRegular, GdalMetaDataStatic, GdalSource, GdalSourceParameters, }, util::gdal::{add_ndvi_dataset, raster_dir}, }; @@ -582,9 +582,7 @@ mod tests { BoundingBox2D, Measurement, MultiLineString, MultiPoint, MultiPolygon, SpatialResolution, TimeGranularity, TimeInstance, TimeInterval, TimeStep, }, - raster::{ - GeoTransform, Grid, GridShape, GridShape2D, GridSize, RasterDataType, RasterTile2D, - }, + raster::{Grid, GridShape, GridShape2D, GridSize, RasterDataType, RasterTile2D}, spatial_reference::SpatialReferenceAuthority, util::{ well_known_data::{ @@ -1036,11 +1034,11 @@ mod tests { file_path: raster_dir() .join("modis_ndvi/projected_3857/MOD13A2_M_NDVI_%%%_START_TIME_%%%.TIFF"), rasterband_channel: 1, - geo_transform: GeoTransform::new( - (20_037_508.342_789_244, 19_971_868.880_408_563).into(), - 14_052.950_258_048_739, - -14_057.881_117_788_405, - ), + geo_transform: GdalDatasetGeoTransform { + origin_coordinate: (20_037_508.342_789_244, 19_971_868.880_408_563).into(), + x_pixel_size: 14_052.950_258_048_739, + y_pixel_size: -14_057.881_117_788_405, + }, width: 1000, height: 1000, file_not_found_handling: FileNotFoundHandling::Error, @@ -1165,11 +1163,11 @@ mod tests { params: GdalDatasetParameters { file_path: PathBuf::new(), rasterband_channel: 1, - geo_transform: GeoTransform::new( - (166_021.44, 9_329_005.188).into(), - (534_994.66 - 166_021.444) / 100., - -9_329_005.18 / 100., - ), + geo_transform: GdalDatasetGeoTransform { + origin_coordinate: (166_021.44, 9_329_005.188).into(), + x_pixel_size: (534_994.66 - 166_021.444) / 100., + y_pixel_size: -9_329_005.18 / 100., + }, width: 100, height: 100, file_not_found_handling: FileNotFoundHandling::NoData, diff --git a/operators/src/source/gdal_source.rs b/operators/src/source/gdal_source.rs index cd5ff064d..4800cd2d0 100755 --- a/operators/src/source/gdal_source.rs +++ b/operators/src/source/gdal_source.rs @@ -31,7 +31,8 @@ use geoengine_datatypes::{ }; use log::{debug, info}; use serde::{Deserialize, Serialize}; -use snafu::ResultExt; +use snafu::{ensure, ResultExt}; +use std::convert::{TryFrom, TryInto}; use std::{marker::PhantomData, path::PathBuf}; //use gdal::metadata::Metadata; // TODO: handle metadata @@ -148,8 +149,7 @@ pub struct GdalLoadingInfoPart { pub struct GdalDatasetParameters { pub file_path: PathBuf, pub rasterband_channel: usize, - #[serde(deserialize_with = "GeoTransform::deserialize_with_check")] - pub geo_transform: GeoTransform, + pub geo_transform: GdalDatasetGeoTransform, pub width: usize, pub height: usize, pub file_not_found_handling: FileNotFoundHandling, @@ -163,6 +163,56 @@ pub struct GdalDatasetParameters { pub gdal_config_options: Option>, } +/// A user friendly representation of Gdal's geo transform. In contrast to [`GeoTransform`] this +/// geo transform allows arbitrary pixel sizes and can thus also represent rasters where the origin is not located +/// in the upper left corner. It should only be used for loading rasters with Gdal and not internally. +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GdalDatasetGeoTransform { + pub origin_coordinate: Coordinate2D, + pub x_pixel_size: f64, + pub y_pixel_size: f64, +} + +/// Default implementation for testing purposes where geo transform doesn't matter +impl Default for GdalDatasetGeoTransform { + fn default() -> Self { + Self { + origin_coordinate: (0.0, 0.0).into(), + x_pixel_size: 1.0, + y_pixel_size: -1.0, + } + } +} + +/// Direct conversion from `GdalDatasetGeoTransform` to [`GeoTransform`] only works if origin is located in the upper left corner. +impl TryFrom for GeoTransform { + type Error = Error; + + fn try_from(dataset_geo_transform: GdalDatasetGeoTransform) -> Result { + ensure!( + dataset_geo_transform.x_pixel_size > 0.0 && dataset_geo_transform.y_pixel_size < 0.0, + error::GeoTransformOrigin + ); + + Ok(GeoTransform::new( + dataset_geo_transform.origin_coordinate, + dataset_geo_transform.x_pixel_size, + dataset_geo_transform.y_pixel_size, + )) + } +} + +impl From for GdalDatasetGeoTransform { + fn from(gdal_geo_transform: gdal::GeoTransform) -> Self { + Self { + origin_coordinate: (gdal_geo_transform[0], gdal_geo_transform[1]).into(), + x_pixel_size: gdal_geo_transform[3], + y_pixel_size: gdal_geo_transform[5], + } + } +} + /// Set thread local gdal options and revert them on drop struct TemporaryGdalThreadLocalConfigOptions { original_configs: Vec<(String, Option)>, @@ -209,8 +259,8 @@ impl SpatialPartitioned for GdalDatasetParameters { fn spatial_partition(&self) -> SpatialPartition2D { let lower_right_coordinate = self.geo_transform.origin_coordinate + Coordinate2D::from(( - self.geo_transform.x_pixel_size() * self.width as f64, - self.geo_transform.y_pixel_size() * self.height as f64, + self.geo_transform.x_pixel_size * self.width as f64, + self.geo_transform.y_pixel_size * self.height as f64, )); SpatialPartition2D::new_unchecked( self.geo_transform.origin_coordinate, @@ -438,7 +488,8 @@ where tile_information: &TileInformation, ) -> Result> { let dataset_bounds = dataset_params.spatial_partition(); - let geo_transform = dataset_params.geo_transform; + // TODO: handle datasets where origin is not in the upper left corner + let geo_transform: GeoTransform = dataset_params.geo_transform.try_into()?; let output_bounds = tile_information.spatial_partition(); let output_shape = tile_information.tile_size_in_pixels(); let output_geo_transform = tile_information.tile_geo_transform(); @@ -574,7 +625,7 @@ where let geo_transform = info.params.geo_transform; // adjust the spatial resolution to the sign of the geotransform - let x_signed = if geo_transform.x_pixel_size().is_sign_positive() + let x_signed = if geo_transform.x_pixel_size.is_sign_positive() && spatial_resolution.x.is_sign_positive() { spatial_resolution.x @@ -582,7 +633,7 @@ where spatial_resolution.x * -1.0 }; - let y_signed = if geo_transform.y_pixel_size().is_sign_positive() + let y_signed = if geo_transform.y_pixel_size.is_sign_positive() && spatial_resolution.y.is_sign_positive() { spatial_resolution.y @@ -894,7 +945,11 @@ mod tests { &GdalDatasetParameters { file_path: raster_dir().join("modis_ndvi/MOD13A2_M_NDVI_2014-01-01.TIFF"), rasterband_channel: 1, - geo_transform: GeoTransform::new((-180., 90.).into(), 0.1, -0.1), + geo_transform: GdalDatasetGeoTransform { + origin_coordinate: (-180., 90.).into(), + x_pixel_size: 0.1, + y_pixel_size: -0.1, + }, width: 3600, height: 1800, file_not_found_handling: FileNotFoundHandling::NoData, diff --git a/operators/src/source/mod.rs b/operators/src/source/mod.rs index ac9f2d6be..6711afaab 100755 --- a/operators/src/source/mod.rs +++ b/operators/src/source/mod.rs @@ -6,9 +6,9 @@ pub use self::csv::{ CsvGeometrySpecification, CsvSource, CsvSourceParameters, CsvSourceStream, CsvTimeSpecification, }; pub use self::gdal_source::{ - FileNotFoundHandling, GdalDatasetParameters, GdalLoadingInfo, GdalLoadingInfoPart, - GdalLoadingInfoPartIterator, GdalMetaDataRegular, GdalMetaDataStatic, GdalMetadataMapping, - GdalSource, GdalSourceParameters, GdalSourceProcessor, + FileNotFoundHandling, GdalDatasetGeoTransform, GdalDatasetParameters, GdalLoadingInfo, + GdalLoadingInfoPart, GdalLoadingInfoPartIterator, GdalMetaDataRegular, GdalMetaDataStatic, + GdalMetadataMapping, GdalSource, GdalSourceParameters, GdalSourceProcessor, }; pub use self::ogr_source::{ OgrSource, OgrSourceColumnSpec, OgrSourceDataset, OgrSourceDatasetTimeType, diff --git a/operators/src/util/gdal.rs b/operators/src/util/gdal.rs index d65e2f812..32786ed38 100644 --- a/operators/src/util/gdal.rs +++ b/operators/src/util/gdal.rs @@ -7,7 +7,7 @@ use gdal::{raster::GDALDataType, Dataset, DatasetOptions}; use geoengine_datatypes::{ dataset::{DatasetId, InternalDatasetId}, primitives::{Measurement, TimeGranularity, TimeInstance, TimeStep}, - raster::{GeoTransform, RasterDataType}, + raster::RasterDataType, spatial_reference::SpatialReference, util::Identifier, }; @@ -16,7 +16,9 @@ use snafu::ResultExt; use crate::{ engine::{MockExecutionContext, RasterResultDescriptor}, error::{self, Error}, - source::{FileNotFoundHandling, GdalDatasetParameters, GdalMetaDataRegular}, + source::{ + FileNotFoundHandling, GdalDatasetGeoTransform, GdalDatasetParameters, GdalMetaDataRegular, + }, util::Result, }; @@ -53,7 +55,11 @@ pub fn create_ndvi_meta_data() -> GdalMetaDataRegular { params: GdalDatasetParameters { file_path: raster_dir().join("modis_ndvi/MOD13A2_M_NDVI_%%%_START_TIME_%%%.TIFF"), rasterband_channel: 1, - geo_transform: GeoTransform::new((-180., 90.).into(), 0.1, -0.1), + geo_transform: GdalDatasetGeoTransform { + origin_coordinate: (-180., 90.).into(), + x_pixel_size: 0.1, + y_pixel_size: -0.1, + }, width: 3600, height: 1800, file_not_found_handling: FileNotFoundHandling::NoData, diff --git a/services/src/datasets/external/nature40.rs b/services/src/datasets/external/nature40.rs index 3ac5ad5d9..15745a7d3 100644 --- a/services/src/datasets/external/nature40.rs +++ b/services/src/datasets/external/nature40.rs @@ -389,14 +389,14 @@ mod tests { use geoengine_datatypes::{ primitives::{Measurement, SpatialPartition2D, SpatialResolution, TimeInterval}, - raster::{GeoTransform, RasterDataType}, + raster::RasterDataType, spatial_reference::{SpatialReference, SpatialReferenceAuthority}, }; use geoengine_operators::{ engine::QueryRectangle, source::{ - FileNotFoundHandling, GdalDatasetParameters, GdalLoadingInfoPart, - GdalLoadingInfoPartIterator, + FileNotFoundHandling, GdalDatasetGeoTransform, GdalDatasetParameters, + GdalLoadingInfoPart, GdalLoadingInfoPartIterator, }, }; use httptest::{ @@ -853,7 +853,11 @@ mod tests { params: GdalDatasetParameters { file_path: PathBuf::from(format!("WCS:{}rasterdb/lidar_2018_wetness_1m/wcs?VERSION=1.0.0&COVERAGE=lidar_2018_wetness_1m", server.url_str(""))), rasterband_channel: 1, - geo_transform: GeoTransform::new_with_coordinate_x_y(473_922.5, 1.0, 5_634_057.5, -1.0), + geo_transform: GdalDatasetGeoTransform { + origin_coordinate: (473_922.5, 5_634_057.5).into(), + x_pixel_size: 1.0, + y_pixel_size: -1.0, + }, width: 4295, height: 3294, file_not_found_handling: FileNotFoundHandling::Error, diff --git a/services/src/handlers/workflows.rs b/services/src/handlers/workflows.rs index 462444adf..68704c105 100755 --- a/services/src/handlers/workflows.rs +++ b/services/src/handlers/workflows.rs @@ -13,12 +13,11 @@ use crate::workflows::workflow::{Workflow, WorkflowId}; use futures::future::join_all; use geoengine_datatypes::dataset::{DatasetId, InternalDatasetId}; use geoengine_datatypes::primitives::AxisAlignedRectangle; -use geoengine_datatypes::raster::GeoTransform; use geoengine_datatypes::spatial_reference::SpatialReference; use geoengine_datatypes::util::Identifier; use geoengine_operators::engine::{OperatorDatasets, RasterQueryRectangle, TypedResultDescriptor}; use geoengine_operators::source::{ - FileNotFoundHandling, GdalDatasetParameters, GdalMetaDataStatic, + FileNotFoundHandling, GdalDatasetGeoTransform, GdalDatasetParameters, GdalMetaDataStatic, }; use geoengine_operators::util::raster_stream_to_geotiff::raster_stream_to_geotiff; use geoengine_operators::{call_on_generic_raster_processor_gdal_types, call_on_typed_operator}; @@ -407,11 +406,11 @@ async fn create_dataset( params: GdalDatasetParameters { file_path, rasterband_channel: 1, - geo_transform: GeoTransform::new( - info.query.spatial_bounds.upper_left(), - info.query.spatial_resolution.x, - -info.query.spatial_resolution.y, - ), + geo_transform: GdalDatasetGeoTransform { + origin_coordinate: info.query.spatial_bounds.upper_left(), + x_pixel_size: info.query.spatial_resolution.x, + y_pixel_size: -info.query.spatial_resolution.y, + }, width: (info.query.spatial_bounds.size_x() / info.query.spatial_resolution.x).ceil() as usize, height: (info.query.spatial_bounds.size_y() / info.query.spatial_resolution.y) diff --git a/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs b/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs index 3ed568528..02a964080 100644 --- a/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs +++ b/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs @@ -15,7 +15,7 @@ use geoengine_datatypes::operations::reproject::{ use geoengine_datatypes::primitives::{ AxisAlignedRectangle, BoundingBox2D, Measurement, SpatialPartitioned, TimeInterval, }; -use geoengine_datatypes::raster::{GeoTransform, RasterDataType}; +use geoengine_datatypes::raster::RasterDataType; use geoengine_datatypes::spatial_reference::{SpatialReference, SpatialReferenceAuthority}; use geoengine_operators::engine::{ MetaData, MetaDataProvider, RasterQueryRectangle, RasterResultDescriptor, VectorQueryRectangle, @@ -23,8 +23,8 @@ use geoengine_operators::engine::{ }; use geoengine_operators::mock::MockDatasetDataSourceLoadingInfo; use geoengine_operators::source::{ - GdalDatasetParameters, GdalLoadingInfo, GdalLoadingInfoPart, GdalLoadingInfoPartIterator, - OgrSourceDataset, + GdalDatasetGeoTransform, GdalDatasetParameters, GdalLoadingInfo, GdalLoadingInfoPart, + GdalLoadingInfoPartIterator, OgrSourceDataset, }; use log::debug; use serde::{Deserialize, Serialize}; @@ -280,7 +280,7 @@ impl SentinelS2L2aCogsMetaData { params: GdalDatasetParameters { file_path: PathBuf::from(format!("/vsicurl/{}", asset.href)), rasterband_channel: 1, - geo_transform: GeoTransform::from( + geo_transform: GdalDatasetGeoTransform::from( asset .gdal_geotransform() .ok_or(error::Error::StacInvalidGeoTransform)?, @@ -561,11 +561,11 @@ mod tests { params: GdalDatasetParameters { file_path: "/vsicurl/https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/32/R/PU/2021/1/S2B_32RPU_20210102_0_L2A/B01.tif".into(), rasterband_channel: 1, - geo_transform: GeoTransform::new( - (600_000.0, 3_400_020.0).into(), - 60., - -60., - ), + geo_transform: GdalDatasetGeoTransform { + origin_coordinate: (600_000.0, 3_400_020.0).into(), + x_pixel_size: 60., + y_pixel_size: -60., + }, width: 1830, height: 1830, file_not_found_handling: FileNotFoundHandling::NoData, diff --git a/services/src/pro/handlers/drone_mapping.rs b/services/src/pro/handlers/drone_mapping.rs index beac71f7b..cd5e55582 100644 --- a/services/src/pro/handlers/drone_mapping.rs +++ b/services/src/pro/handlers/drone_mapping.rs @@ -374,11 +374,11 @@ async fn unzip(zip_path: &Path, target_path: &Path) -> Result<(), error::Error> #[cfg(test)] mod tests { use geoengine_datatypes::primitives::{SpatialPartition2D, SpatialResolution, TimeInterval}; - use geoengine_datatypes::raster::{GeoTransform, RasterTile2D}; + use geoengine_datatypes::raster::RasterTile2D; use geoengine_datatypes::spatial_reference::SpatialReferenceAuthority; use geoengine_operators::source::{ - FileNotFoundHandling, GdalDatasetParameters, GdalLoadingInfo, GdalLoadingInfoPart, - GdalSource, GdalSourceParameters, + FileNotFoundHandling, GdalDatasetGeoTransform, GdalDatasetParameters, GdalLoadingInfo, + GdalLoadingInfoPart, GdalSource, GdalSourceParameters, }; use httptest::responders::status_code; use httptest::{matchers::request, responders::json_encoded, Expectation, Server}; @@ -571,7 +571,11 @@ mod tests { params: GdalDatasetParameters { file_path: file_path.clone(), rasterband_channel: 1, - geo_transform: GeoTransform::new_with_coordinate_x_y(0., 1., 0., -1.), + geo_transform: GdalDatasetGeoTransform { + origin_coordinate: (0.0, 0.0).into(), + x_pixel_size: 1.0, + y_pixel_size: -1.0, + }, width: 200, height: 200, file_not_found_handling: FileNotFoundHandling::Error, From 14a50895c8b70cb6e4b99113f91bf5508741db2a Mon Sep 17 00:00:00 2001 From: Michael Mattig Date: Thu, 9 Sep 2021 20:16:21 +0200 Subject: [PATCH 07/10] remove TODO --- operators/src/adapters/raster_subquery_adapter.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/operators/src/adapters/raster_subquery_adapter.rs b/operators/src/adapters/raster_subquery_adapter.rs index 5b4801239..8cb56f0d6 100644 --- a/operators/src/adapters/raster_subquery_adapter.rs +++ b/operators/src/adapters/raster_subquery_adapter.rs @@ -639,7 +639,6 @@ where }) } - // TODO: return Result> fn tile_query_rectangle( &self, tile_info: TileInformation, From b40eb50b3b109a5454847769fa42f5f1bf59ba33 Mon Sep 17 00:00:00 2001 From: Michael Mattig Date: Thu, 9 Sep 2021 20:52:43 +0200 Subject: [PATCH 08/10] fix geo transform --- operators/src/source/gdal_source.rs | 4 ++-- services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/operators/src/source/gdal_source.rs b/operators/src/source/gdal_source.rs index 4800cd2d0..baead06db 100755 --- a/operators/src/source/gdal_source.rs +++ b/operators/src/source/gdal_source.rs @@ -206,8 +206,8 @@ impl TryFrom for GeoTransform { impl From for GdalDatasetGeoTransform { fn from(gdal_geo_transform: gdal::GeoTransform) -> Self { Self { - origin_coordinate: (gdal_geo_transform[0], gdal_geo_transform[1]).into(), - x_pixel_size: gdal_geo_transform[3], + origin_coordinate: (gdal_geo_transform[0], gdal_geo_transform[3]).into(), + x_pixel_size: gdal_geo_transform[1], y_pixel_size: gdal_geo_transform[5], } } diff --git a/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs b/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs index 02a964080..2abe81aad 100644 --- a/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs +++ b/services/src/pro/datasets/external/sentinel_s2_l2a_cogs.rs @@ -563,7 +563,7 @@ mod tests { rasterband_channel: 1, geo_transform: GdalDatasetGeoTransform { origin_coordinate: (600_000.0, 3_400_020.0).into(), - x_pixel_size: 60., + x_pixel_size: 60., y_pixel_size: -60., }, width: 1830, From 7a6766483729089fac8b6f0ffcb2c6b1daf168c1 Mon Sep 17 00:00:00 2001 From: Michael Mattig Date: Fri, 10 Sep 2021 11:37:36 +0200 Subject: [PATCH 09/10] TestDefault trait for default values only to be used in tests --- datatypes/src/raster/geo_transform.rs | 9 +- datatypes/src/raster/macros_raster_tile.rs | 65 ++++++++++---- datatypes/src/raster/raster_tile.rs | 42 ++++----- datatypes/src/util/test.rs | 6 ++ .../src/adapters/raster_subquery_adapter.rs | 17 ++-- operators/src/adapters/raster_time.rs | 20 ++--- .../src/adapters/raster_time_substream.rs | 5 +- operators/src/mock/mock_raster_source.rs | 3 +- operators/src/plot/histogram.rs | 7 +- operators/src/plot/statistics.rs | 3 +- .../src/plot/temporal_raster_mean_plot.rs | 7 +- operators/src/processing/expression.rs | 3 +- operators/src/processing/meteosat/mod.rs | 3 +- .../raster_vector_join/aggregated.rs | 23 ++--- .../raster_vector_join/non_aggregated.rs | 17 ++-- operators/src/processing/reprojection.rs | 9 +- .../temporal_aggregation_operator.rs | 89 ++++++++++--------- operators/src/source/gdal_source.rs | 9 +- services/src/handlers/plots.rs | 3 +- 19 files changed, 196 insertions(+), 144 deletions(-) diff --git a/datatypes/src/raster/geo_transform.rs b/datatypes/src/raster/geo_transform.rs index 9130e7917..09dc76190 100644 --- a/datatypes/src/raster/geo_transform.rs +++ b/datatypes/src/raster/geo_transform.rs @@ -1,7 +1,8 @@ use std::cmp::max; -use crate::primitives::{ - AxisAlignedRectangle, Coordinate2D, SpatialPartition2D, SpatialResolution, +use crate::{ + primitives::{AxisAlignedRectangle, Coordinate2D, SpatialPartition2D, SpatialResolution}, + util::test::TestDefault, }; use serde::{de, Deserialize, Deserializer, Serialize}; @@ -190,8 +191,8 @@ impl GeoTransform { } } -impl Default for GeoTransform { - fn default() -> Self { +impl TestDefault for GeoTransform { + fn test_default() -> Self { GeoTransform::new_with_coordinate_x_y(0.0, 1.0, 0.0, -1.0) } } diff --git a/datatypes/src/raster/macros_raster_tile.rs b/datatypes/src/raster/macros_raster_tile.rs index 2ab42246a..a525914ce 100644 --- a/datatypes/src/raster/macros_raster_tile.rs +++ b/datatypes/src/raster/macros_raster_tile.rs @@ -243,6 +243,7 @@ mod tests { use crate::{ primitives::TimeInterval, raster::{GeoTransform, Grid2D, GridIndexAccess, Pixel, RasterTile2D, TypedRasterTile2D}, + util::test::TestDefault, }; use crate::{raster::RasterDataType, util::test::catch_unwind_silent}; use std::marker::PhantomData; @@ -254,8 +255,11 @@ mod tests { } let r = Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], None).unwrap(); - let t = - RasterTile2D::new_without_offset(TimeInterval::default(), GeoTransform::default(), r); + let t = RasterTile2D::new_without_offset( + TimeInterval::default(), + GeoTransform::test_default(), + r, + ); let typed_raster = TypedRasterTile2D::U32(t); assert_eq!( @@ -271,8 +275,11 @@ mod tests { } let r = Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], None).unwrap(); - let t = - RasterTile2D::new_without_offset(TimeInterval::default(), GeoTransform::default(), r); + let t = RasterTile2D::new_without_offset( + TimeInterval::default(), + GeoTransform::test_default(), + r, + ); let typed_raster = TypedRasterTile2D::U32(t); assert_eq!( @@ -299,14 +306,18 @@ mod tests { ]; let r = Grid2D::new([3, 2].into(), data, None).unwrap(); - RasterTile2D::new_without_offset(TimeInterval::default(), GeoTransform::default(), r) + RasterTile2D::new_without_offset( + TimeInterval::default(), + GeoTransform::test_default(), + r, + ) } assert_eq!( generate_generic_raster_tile_2d!(RasterDataType::U8, generate()), TypedRasterTile2D::U8(RasterTile2D::new_without_offset( TimeInterval::default(), - GeoTransform::default(), + GeoTransform::test_default(), Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], None,).unwrap() ),) ); @@ -321,13 +332,19 @@ mod tests { } let r = Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], None).unwrap(); - let t = - RasterTile2D::new_without_offset(TimeInterval::default(), GeoTransform::default(), r); + let t = RasterTile2D::new_without_offset( + TimeInterval::default(), + GeoTransform::test_default(), + r, + ); let typed_raster_a = TypedRasterTile2D::U32(t); let r = Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], None).unwrap(); - let t = - RasterTile2D::new_without_offset(TimeInterval::default(), GeoTransform::default(), r); + let t = RasterTile2D::new_without_offset( + TimeInterval::default(), + GeoTransform::test_default(), + r, + ); let typed_raster_b = TypedRasterTile2D::U16(t); assert_eq!( @@ -348,13 +365,19 @@ mod tests { } let r = Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], None).unwrap(); - let t = - RasterTile2D::new_without_offset(TimeInterval::default(), GeoTransform::default(), r); + let t = RasterTile2D::new_without_offset( + TimeInterval::default(), + GeoTransform::test_default(), + r, + ); let typed_raster_a = TypedRasterTile2D::U32(t); let r = Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], None).unwrap(); - let t = - RasterTile2D::new_without_offset(TimeInterval::default(), GeoTransform::default(), r); + let t = RasterTile2D::new_without_offset( + TimeInterval::default(), + GeoTransform::test_default(), + r, + ); let typed_raster_b = TypedRasterTile2D::U16(t); assert_eq!( @@ -391,13 +414,19 @@ mod tests { } let r = Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], None).unwrap(); - let t = - RasterTile2D::new_without_offset(TimeInterval::default(), GeoTransform::default(), r); + let t = RasterTile2D::new_without_offset( + TimeInterval::default(), + GeoTransform::test_default(), + r, + ); let typed_raster_a = TypedRasterTile2D::U32(t); let r = Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], None).unwrap(); - let t = - RasterTile2D::new_without_offset(TimeInterval::default(), GeoTransform::default(), r); + let t = RasterTile2D::new_without_offset( + TimeInterval::default(), + GeoTransform::test_default(), + r, + ); let typed_raster_b = TypedRasterTile2D::U16(t); assert_eq!( diff --git a/datatypes/src/raster/raster_tile.rs b/datatypes/src/raster/raster_tile.rs index 43a5244ae..2cd32501c 100644 --- a/datatypes/src/raster/raster_tile.rs +++ b/datatypes/src/raster/raster_tile.rs @@ -342,7 +342,7 @@ where #[cfg(test)] mod tests { - use crate::primitives::Coordinate2D; + use crate::{primitives::Coordinate2D, util::test::TestDefault}; use super::*; use crate::raster::{Grid2D, GridIdx}; @@ -372,7 +372,7 @@ mod tests { let raster_tile = RasterTile2D::new_with_tile_info( TimeInterval::default(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -396,7 +396,7 @@ mod tests { let raster_tile = RasterTile2D::new_with_tile_info( TimeInterval::default(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [1, 1].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -424,9 +424,9 @@ mod tests { let ti = TileInformation::new( GridIdx([0, 0]), GridShape2D::from([100, 100]), - GeoTransform::default(), + GeoTransform::test_default(), ); - assert_eq!(ti.global_geo_transform, GeoTransform::default()); + assert_eq!(ti.global_geo_transform, GeoTransform::test_default()); assert_eq!(ti.global_tile_position, GridIdx([0, 0])); assert_eq!(ti.tile_size_in_pixels, GridShape2D::from([100, 100])); } @@ -436,7 +436,7 @@ mod tests { let ti = TileInformation::new( GridIdx([0, 0]), GridShape2D::from([100, 100]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.global_tile_position(), GridIdx([0, 0])); } @@ -446,7 +446,7 @@ mod tests { let ti = TileInformation::new( GridIdx([0, 0]), GridShape2D::from([100, 100]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.local_upper_left_pixel_idx(), GridIdx([0, 0])); } @@ -456,7 +456,7 @@ mod tests { let ti = TileInformation::new( GridIdx([0, 0]), GridShape2D::from([100, 100]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.local_lower_left_pixel_idx(), GridIdx([99, 0])); } @@ -466,7 +466,7 @@ mod tests { let ti = TileInformation::new( GridIdx([0, 0]), GridShape2D::from([100, 100]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.local_upper_right_pixel_idx(), GridIdx([0, 99])); } @@ -476,7 +476,7 @@ mod tests { let ti = TileInformation::new( GridIdx([0, 0]), GridShape2D::from([100, 100]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.local_lower_right_pixel_idx(), GridIdx([99, 99])); } @@ -486,7 +486,7 @@ mod tests { let ti = TileInformation::new( GridIdx([0, 0]), GridShape2D::from([100, 100]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.global_upper_left_pixel_idx(), GridIdx([0, 0])); } @@ -496,7 +496,7 @@ mod tests { let ti = TileInformation::new( GridIdx([-2, 3]), GridShape2D::from([100, 1000]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.global_upper_left_pixel_idx(), GridIdx([-200, 3000])); } @@ -506,7 +506,7 @@ mod tests { let ti = TileInformation::new( GridIdx([0, 0]), GridShape2D::from([100, 100]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.global_upper_right_pixel_idx(), GridIdx([0, 99])); } @@ -516,7 +516,7 @@ mod tests { let ti = TileInformation::new( GridIdx([-2, 3]), GridShape2D::from([100, 1000]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.global_upper_right_pixel_idx(), GridIdx([-200, 3999])); } @@ -526,7 +526,7 @@ mod tests { let ti = TileInformation::new( GridIdx([0, 0]), GridShape2D::from([100, 100]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.global_lower_right_pixel_idx(), GridIdx([99, 99])); } @@ -536,7 +536,7 @@ mod tests { let ti = TileInformation::new( GridIdx([-2, 3]), GridShape2D::from([100, 1000]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.global_lower_right_pixel_idx(), GridIdx([-101, 3999])); } @@ -546,7 +546,7 @@ mod tests { let ti = TileInformation::new( GridIdx([0, 0]), GridShape2D::from([100, 100]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.global_lower_left_pixel_idx(), GridIdx([99, 0])); } @@ -556,7 +556,7 @@ mod tests { let ti = TileInformation::new( GridIdx([-2, 3]), GridShape2D::from([100, 1000]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!(ti.global_lower_left_pixel_idx(), GridIdx([-101, 3000])); } @@ -566,7 +566,7 @@ mod tests { let ti = TileInformation::new( GridIdx([0, 0]), GridShape2D::from([100, 100]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!( ti.local_to_global_pixel_idx(GridIdx([25, 75])), @@ -579,7 +579,7 @@ mod tests { let ti = TileInformation::new( GridIdx([-2, 3]), GridShape2D::from([100, 1000]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!( ti.local_to_global_pixel_idx(GridIdx([25, 75])), @@ -592,7 +592,7 @@ mod tests { let ti = TileInformation::new( GridIdx([-2, 3]), GridShape2D::from([100, 1000]), - GeoTransform::default(), + GeoTransform::test_default(), ); assert_eq!( ti.spatial_partition(), diff --git a/datatypes/src/util/test.rs b/datatypes/src/util/test.rs index d94de3513..8d514ff4a 100644 --- a/datatypes/src/util/test.rs +++ b/datatypes/src/util/test.rs @@ -1,6 +1,12 @@ use crate::raster::{EmptyGrid, Grid, GridOrEmpty, NoDataValue}; use std::panic; +pub trait TestDefault { + /// Generate a default value used for testing. Use this instead of the `Default` trait + /// if the default value only makes sense in tests and not in production code. + fn test_default() -> Self; +} + pub fn catch_unwind_silent R + panic::UnwindSafe, R>( f: F, ) -> std::thread::Result { diff --git a/operators/src/adapters/raster_subquery_adapter.rs b/operators/src/adapters/raster_subquery_adapter.rs index 8cb56f0d6..68f5c2c58 100644 --- a/operators/src/adapters/raster_subquery_adapter.rs +++ b/operators/src/adapters/raster_subquery_adapter.rs @@ -676,6 +676,7 @@ mod tests { primitives::{Measurement, SpatialPartition2D, SpatialResolution, TimeInterval}, raster::{Grid, GridShape, RasterDataType}, spatial_reference::SpatialReference, + util::test::TestDefault, }; use super::*; @@ -692,7 +693,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(0, 5), tile_position: [-1, 0].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![1, 2, 3, 4], no_data_value) .unwrap() .into(), @@ -701,7 +702,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(0, 5), tile_position: [-1, 1].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![7, 8, 9, 10], no_data_value) .unwrap() .into(), @@ -710,7 +711,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(5, 10), tile_position: [-1, 0].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![13, 14, 15, 16], no_data_value) .unwrap() .into(), @@ -719,7 +720,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(5, 10), tile_position: [-1, 1].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![19, 20, 21, 22], no_data_value) .unwrap() .into(), @@ -786,7 +787,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(0, 5), tile_position: [-1, 0].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![1, 2, 3, 4], no_data_value) .unwrap() .into(), @@ -795,7 +796,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(0, 5), tile_position: [-1, 1].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![7, 8, 9, 10], no_data_value) .unwrap() .into(), @@ -804,7 +805,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(5, 10), tile_position: [-1, 0].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![13, 14, 15, 16], no_data_value) .unwrap() .into(), @@ -813,7 +814,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(5, 10), tile_position: [-1, 1].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![19, 20, 21, 22], no_data_value) .unwrap() .into(), diff --git a/operators/src/adapters/raster_time.rs b/operators/src/adapters/raster_time.rs index 2c9dfb32f..e43f1aa43 100644 --- a/operators/src/adapters/raster_time.rs +++ b/operators/src/adapters/raster_time.rs @@ -197,7 +197,7 @@ where // RasterTile2D { // time: TimeInterval::new_unchecked(0, 5), // tile_position: [-1, 0].into(), -// global_geo_transform: Default::default(), +// global_geo_transform: TestDefault::test_default(), // grid_array: Grid::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], no_data_value) // .unwrap() // .into(), @@ -205,7 +205,7 @@ where // RasterTile2D { // time: TimeInterval::new_unchecked(0, 5), // tile_position: [-1, 1].into(), -// global_geo_transform: Default::default(), +// global_geo_transform: TestDefault::test_default(), // grid_array: Grid::new( // [3, 2].into(), // vec![7, 8, 9, 10, 11, 12], @@ -217,7 +217,7 @@ where // RasterTile2D { // time: TimeInterval::new_unchecked(5, 10), // tile_position: [-1, 0].into(), -// global_geo_transform: Default::default(), +// global_geo_transform: TestDefault::test_default(), // grid_array: Grid::new( // [3, 2].into(), // vec![13, 14, 15, 16, 17, 18], @@ -229,7 +229,7 @@ where // RasterTile2D { // time: TimeInterval::new_unchecked(5, 10), // tile_position: [-1, 1].into(), -// global_geo_transform: Default::default(), +// global_geo_transform: TestDefault::test_default(), // grid_array: Grid::new( // [3, 2].into(), // vec![19, 20, 21, 22, 23, 24], @@ -255,7 +255,7 @@ where // RasterTile2D { // time: TimeInterval::new_unchecked(0, 3), // tile_position: [-1, 0].into(), -// global_geo_transform: Default::default(), +// global_geo_transform: TestDefault::test_default(), // grid_array: Grid::new( // [3, 2].into(), // vec![101, 102, 103, 104, 105, 106], @@ -267,7 +267,7 @@ where // RasterTile2D { // time: TimeInterval::new_unchecked(0, 3), // tile_position: [-1, 1].into(), -// global_geo_transform: Default::default(), +// global_geo_transform: TestDefault::test_default(), // grid_array: Grid::new( // [3, 2].into(), // vec![107, 108, 109, 110, 111, 112], @@ -279,7 +279,7 @@ where // RasterTile2D { // time: TimeInterval::new_unchecked(3, 6), // tile_position: [-1, 0].into(), -// global_geo_transform: Default::default(), +// global_geo_transform: TestDefault::test_default(), // grid_array: Grid::new( // [3, 2].into(), // vec![113, 114, 115, 116, 117, 118], @@ -291,7 +291,7 @@ where // RasterTile2D { // time: TimeInterval::new_unchecked(3, 6), // tile_position: [-1, 1].into(), -// global_geo_transform: Default::default(), +// global_geo_transform: TestDefault::test_default(), // grid_array: Grid::new( // [3, 2].into(), // vec![119, 120, 121, 122, 123, 124], @@ -303,7 +303,7 @@ where // RasterTile2D { // time: TimeInterval::new_unchecked(6, 10), // tile_position: [-1, 0].into(), -// global_geo_transform: Default::default(), +// global_geo_transform: TestDefault::test_default(), // grid_array: Grid::new( // [3, 2].into(), // vec![125, 126, 127, 128, 129, 130], @@ -315,7 +315,7 @@ where // RasterTile2D { // time: TimeInterval::new_unchecked(6, 10), // tile_position: [-1, 1].into(), -// global_geo_transform: Default::default(), +// global_geo_transform: TestDefault::test_default(), // grid_array: Grid::new( // [3, 2].into(), // vec![131, 132, 133, 134, 135, 136], diff --git a/operators/src/adapters/raster_time_substream.rs b/operators/src/adapters/raster_time_substream.rs index 328abbf12..24ab6d1ae 100644 --- a/operators/src/adapters/raster_time_substream.rs +++ b/operators/src/adapters/raster_time_substream.rs @@ -145,12 +145,13 @@ mod tests { use futures::stream::{self, StreamExt}; use geoengine_datatypes::primitives::TimeInterval; use geoengine_datatypes::raster::{Grid2D, TileInformation}; + use geoengine_datatypes::util::test::TestDefault; use tokio::pin; #[tokio::test] async fn simple() { let tile_information = TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }; @@ -211,7 +212,7 @@ mod tests { #[tokio::test] async fn first_value() { let tile_information = TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }; diff --git a/operators/src/mock/mock_raster_source.rs b/operators/src/mock/mock_raster_source.rs index cf4dba7dd..46df0def6 100644 --- a/operators/src/mock/mock_raster_source.rs +++ b/operators/src/mock/mock_raster_source.rs @@ -124,6 +124,7 @@ mod tests { use crate::engine::MockExecutionContext; use geoengine_datatypes::primitives::Measurement; use geoengine_datatypes::raster::RasterDataType; + use geoengine_datatypes::util::test::TestDefault; use geoengine_datatypes::{ primitives::TimeInterval, raster::{Grid2D, TileInformation}, @@ -138,7 +139,7 @@ mod tests { let raster_tile = RasterTile2D::new_with_tile_info( TimeInterval::default(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, diff --git a/operators/src/plot/histogram.rs b/operators/src/plot/histogram.rs index 0c65740eb..1730f5b62 100644 --- a/operators/src/plot/histogram.rs +++ b/operators/src/plot/histogram.rs @@ -596,6 +596,7 @@ mod tests { }; use geoengine_datatypes::raster::{Grid2D, RasterDataType, RasterTile2D, TileInformation}; use geoengine_datatypes::spatial_reference::SpatialReference; + use geoengine_datatypes::util::test::TestDefault; use geoengine_datatypes::util::Identifier; use geoengine_datatypes::{ collections::{DataCollection, VectorDataType}, @@ -711,7 +712,7 @@ mod tests { data: vec![RasterTile2D::new_with_tile_info( TimeInterval::default(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -1053,7 +1054,7 @@ mod tests { data: vec![RasterTile2D::new_with_tile_info( TimeInterval::default(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -1240,7 +1241,7 @@ mod tests { data: vec![RasterTile2D::new_with_tile_info( TimeInterval::default(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, diff --git a/operators/src/plot/statistics.rs b/operators/src/plot/statistics.rs index 31f472cf2..ef1eca5ab 100644 --- a/operators/src/plot/statistics.rs +++ b/operators/src/plot/statistics.rs @@ -174,6 +174,7 @@ impl From<&NumberStatistics> for StatisticsOutput { #[cfg(test)] mod tests { + use geoengine_datatypes::util::test::TestDefault; use serde_json::json; use super::*; @@ -218,7 +219,7 @@ mod tests { data: vec![RasterTile2D::new_with_tile_info( TimeInterval::default(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, diff --git a/operators/src/plot/temporal_raster_mean_plot.rs b/operators/src/plot/temporal_raster_mean_plot.rs index c8c73339c..7b3294f64 100644 --- a/operators/src/plot/temporal_raster_mean_plot.rs +++ b/operators/src/plot/temporal_raster_mean_plot.rs @@ -250,7 +250,6 @@ mod tests { source::GdalSourceParameters, }; use chrono::NaiveDate; - use geoengine_datatypes::raster::{Grid2D, RasterDataType, TileInformation}; use geoengine_datatypes::spatial_reference::SpatialReference; use geoengine_datatypes::{ dataset::InternalDatasetId, @@ -260,6 +259,10 @@ mod tests { primitives::{BoundingBox2D, Measurement, SpatialResolution, TimeInterval}, util::Identifier, }; + use geoengine_datatypes::{ + raster::{Grid2D, RasterDataType, TileInformation}, + util::test::TestDefault, + }; use num_traits::AsPrimitive; use serde_json::json; @@ -378,7 +381,7 @@ mod tests { tiles.push(RasterTile2D::new_with_tile_info( time_interval, TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, diff --git a/operators/src/processing/expression.rs b/operators/src/processing/expression.rs index 7b6ebd1e4..e899cbfd4 100644 --- a/operators/src/processing/expression.rs +++ b/operators/src/processing/expression.rs @@ -447,6 +447,7 @@ mod tests { }; use geoengine_datatypes::raster::TileInformation; use geoengine_datatypes::spatial_reference::SpatialReference; + use geoengine_datatypes::util::test::TestDefault; #[test] fn deserialize_params() { @@ -582,7 +583,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, raster.into(), ); diff --git a/operators/src/processing/meteosat/mod.rs b/operators/src/processing/meteosat/mod.rs index 37d410462..c030b3248 100644 --- a/operators/src/processing/meteosat/mod.rs +++ b/operators/src/processing/meteosat/mod.rs @@ -37,6 +37,7 @@ fn satellite_key() -> RasterPropertiesKey { mod test_util { use chrono::{TimeZone, Utc}; use futures::StreamExt; + use geoengine_datatypes::util::test::TestDefault; use num_traits::AsPrimitive; use geoengine_datatypes::dataset::{DatasetId, InternalDatasetId}; @@ -174,7 +175,7 @@ mod test_util { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, raster, props, diff --git a/operators/src/processing/raster_vector_join/aggregated.rs b/operators/src/processing/raster_vector_join/aggregated.rs index 817fed179..9f9f09d99 100644 --- a/operators/src/processing/raster_vector_join/aggregated.rs +++ b/operators/src/processing/raster_vector_join/aggregated.rs @@ -226,6 +226,7 @@ mod tests { use geoengine_datatypes::primitives::MultiPolygon; use geoengine_datatypes::raster::{Grid2D, RasterTile2D, TileInformation}; use geoengine_datatypes::spatial_reference::SpatialReference; + use geoengine_datatypes::util::test::TestDefault; use geoengine_datatypes::{ primitives::{ BoundingBox2D, FeatureDataRef, Measurement, MultiPoint, SpatialResolution, TimeInterval, @@ -238,7 +239,7 @@ mod tests { let raster_tile = RasterTile2D::new_with_tile_info( TimeInterval::default(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -311,7 +312,7 @@ mod tests { let raster_tile_a = RasterTile2D::new_with_tile_info( TimeInterval::new(0, 10).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -322,7 +323,7 @@ mod tests { let raster_tile_b = RasterTile2D::new_with_tile_info( TimeInterval::new(10, 20).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -395,7 +396,7 @@ mod tests { let raster_tile_a_0 = RasterTile2D::new_with_tile_info( TimeInterval::new(0, 10).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -406,7 +407,7 @@ mod tests { let raster_tile_a_1 = RasterTile2D::new_with_tile_info( TimeInterval::new(0, 10).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 1].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -417,7 +418,7 @@ mod tests { let raster_tile_b_0 = RasterTile2D::new_with_tile_info( TimeInterval::new(10, 20).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -428,7 +429,7 @@ mod tests { let raster_tile_b_1 = RasterTile2D::new_with_tile_info( TimeInterval::new(10, 20).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 1].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -506,7 +507,7 @@ mod tests { let raster_tile_a_0 = RasterTile2D::new_with_tile_info( TimeInterval::new(0, 10).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -517,7 +518,7 @@ mod tests { let raster_tile_a_1 = RasterTile2D::new_with_tile_info( TimeInterval::new(0, 10).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 1].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -528,7 +529,7 @@ mod tests { let raster_tile_b_0 = RasterTile2D::new_with_tile_info( TimeInterval::new(10, 20).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -539,7 +540,7 @@ mod tests { let raster_tile_b_1 = RasterTile2D::new_with_tile_info( TimeInterval::new(10, 20).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 1].into(), tile_size_in_pixels: [3, 2].into(), }, diff --git a/operators/src/processing/raster_vector_join/non_aggregated.rs b/operators/src/processing/raster_vector_join/non_aggregated.rs index 42ee48b40..330499a30 100644 --- a/operators/src/processing/raster_vector_join/non_aggregated.rs +++ b/operators/src/processing/raster_vector_join/non_aggregated.rs @@ -297,6 +297,7 @@ mod tests { Grid2D, RasterDataType, TileInformation, TilingSpecification, }; use geoengine_datatypes::spatial_reference::SpatialReference; + use geoengine_datatypes::util::test::TestDefault; #[tokio::test] async fn both_instant() { @@ -714,7 +715,7 @@ mod tests { let raster_tile_a_0 = RasterTile2D::new_with_tile_info( TimeInterval::new(0, 10).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -725,7 +726,7 @@ mod tests { let raster_tile_a_1 = RasterTile2D::new_with_tile_info( TimeInterval::new(0, 10).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 1].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -736,7 +737,7 @@ mod tests { let raster_tile_b_0 = RasterTile2D::new_with_tile_info( TimeInterval::new(10, 20).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -747,7 +748,7 @@ mod tests { let raster_tile_b_1 = RasterTile2D::new_with_tile_info( TimeInterval::new(10, 20).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 1].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -871,7 +872,7 @@ mod tests { let raster_tile_a_0 = RasterTile2D::new_with_tile_info( TimeInterval::new(0, 10).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -882,7 +883,7 @@ mod tests { let raster_tile_a_1 = RasterTile2D::new_with_tile_info( TimeInterval::new(0, 10).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 1].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -893,7 +894,7 @@ mod tests { let raster_tile_b_0 = RasterTile2D::new_with_tile_info( TimeInterval::new(10, 20).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, @@ -904,7 +905,7 @@ mod tests { let raster_tile_b_1 = RasterTile2D::new_with_tile_info( TimeInterval::new(10, 20).unwrap(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 1].into(), tile_size_in_pixels: [3, 2].into(), }, diff --git a/operators/src/processing/reprojection.rs b/operators/src/processing/reprojection.rs index 2b20ccb6b..0d9da418f 100644 --- a/operators/src/processing/reprojection.rs +++ b/operators/src/processing/reprojection.rs @@ -585,6 +585,7 @@ mod tests { raster::{Grid, GridShape, GridShape2D, GridSize, RasterDataType, RasterTile2D}, spatial_reference::SpatialReferenceAuthority, util::{ + test::TestDefault, well_known_data::{ COLOGNE_EPSG_4326, COLOGNE_EPSG_900_913, HAMBURG_EPSG_4326, HAMBURG_EPSG_900_913, MARBURG_EPSG_4326, MARBURG_EPSG_900_913, @@ -822,7 +823,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(0, 5), tile_position: [-1, 0].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![1, 2, 3, 4], no_data_value) .unwrap() .into(), @@ -831,7 +832,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(0, 5), tile_position: [-1, 1].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![7, 8, 9, 10], no_data_value) .unwrap() .into(), @@ -840,7 +841,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(5, 10), tile_position: [-1, 0].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![13, 14, 15, 16], no_data_value) .unwrap() .into(), @@ -849,7 +850,7 @@ mod tests { RasterTile2D { time: TimeInterval::new_unchecked(5, 10), tile_position: [-1, 1].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), grid_array: Grid::new([2, 2].into(), vec![19, 20, 21, 22], no_data_value) .unwrap() .into(), diff --git a/operators/src/processing/temporal_raster_aggregation/temporal_aggregation_operator.rs b/operators/src/processing/temporal_raster_aggregation/temporal_aggregation_operator.rs index 8abd379a3..d1167998a 100644 --- a/operators/src/processing/temporal_raster_aggregation/temporal_aggregation_operator.rs +++ b/operators/src/processing/temporal_raster_aggregation/temporal_aggregation_operator.rs @@ -347,6 +347,7 @@ mod tests { primitives::{Measurement, SpatialResolution, TimeInterval}, raster::{EmptyGrid, EmptyGrid2D, Grid2D, GridOrEmpty, RasterDataType, TileInformation}, spatial_reference::SpatialReference, + util::test::TestDefault, }; use num_traits::AsPrimitive; @@ -427,7 +428,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], no_data_value).unwrap() @@ -442,7 +443,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1], no_data_value).unwrap() @@ -457,7 +458,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], no_data_value).unwrap() @@ -472,7 +473,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1], no_data_value).unwrap() @@ -551,7 +552,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![12, 11, 10, 9, 8, 7], no_data_value).unwrap() @@ -566,7 +567,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![7, 8, 9, 10, 11, 12], no_data_value).unwrap() @@ -581,7 +582,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![12, 11, 10, 9, 8, 7], no_data_value).unwrap() @@ -596,7 +597,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![7, 8, 9, 10, 11, 12], no_data_value).unwrap() @@ -680,7 +681,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new( @@ -700,7 +701,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![7, 8, 9, 10, 11, 12], no_data_value).unwrap() @@ -715,7 +716,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![12, 11, 10, 9, 8, 7], no_data_value).unwrap() @@ -730,7 +731,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![7, 8, 9, 10, 11, 12], no_data_value).unwrap() @@ -814,7 +815,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![12, 11, 10, 9, 8, 7], no_data_value).unwrap() @@ -829,7 +830,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![7, 8, 9, 10, 11, 12], no_data_value).unwrap() @@ -844,7 +845,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![12, 11, 10, 9, 8, 7], no_data_value).unwrap() @@ -859,7 +860,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![7, 8, 9, 10, 11, 12], no_data_value).unwrap() @@ -880,7 +881,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Empty(EmptyGrid::new([3, 2].into(), no_data_value.unwrap())), )], @@ -946,7 +947,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Empty(EmptyGrid::new([3, 2].into(), no_data_value.unwrap())), ) @@ -1022,7 +1023,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![7, 8, 9, 16, 11, 12], no_data_value).unwrap() @@ -1037,7 +1038,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![1, 2, 3, 42, 5, 6], no_data_value).unwrap() @@ -1115,7 +1116,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![13, 8, 15, 16, 17, 18], no_data_value).unwrap() @@ -1130,7 +1131,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![1, 2, 3, 42, 5, 6], no_data_value).unwrap() @@ -1208,7 +1209,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![13, 42, 15, 16, 17, 18], no_data_value) @@ -1224,7 +1225,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Empty(EmptyGrid2D::new([3, 2].into(), no_data_value.unwrap())) ) @@ -1300,7 +1301,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Empty(EmptyGrid2D::new([3, 2].into(), no_data_value.unwrap())) ) @@ -1313,7 +1314,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![1, 2, 3, 42, 5, 6], no_data_value).unwrap() @@ -1391,7 +1392,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Empty(EmptyGrid2D::new([3, 2].into(), no_data_value.unwrap())) ) @@ -1404,7 +1405,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![1, 2, 3, 42, 5, 6], no_data_value).unwrap() @@ -1482,7 +1483,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![10, 8, 12, 16, 14, 15], no_data_value).unwrap() @@ -1497,7 +1498,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![1, 2, 3, 42, 5, 6], no_data_value).unwrap() @@ -1517,7 +1518,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], no_data_value).unwrap(), @@ -1528,7 +1529,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![7, 8, 9, 10, 11, 12], no_data_value).unwrap(), @@ -1539,7 +1540,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![12, 11, 10, 9, 8, 7], no_data_value).unwrap(), @@ -1550,7 +1551,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1], no_data_value).unwrap(), @@ -1561,7 +1562,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6], no_data_value).unwrap(), @@ -1572,7 +1573,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![7, 8, 9, 10, 11, 12], no_data_value).unwrap(), @@ -1583,7 +1584,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![12, 11, 10, 9, 8, 7], no_data_value).unwrap(), @@ -1594,7 +1595,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1], no_data_value).unwrap(), @@ -1615,7 +1616,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Empty(EmptyGrid2D::new([3, 2].into(), no_data_value)), ), @@ -1624,7 +1625,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new([3, 2].into(), vec![1, 2, 3, 42, 5, 6], Some(no_data_value)) @@ -1636,7 +1637,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new( @@ -1652,7 +1653,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Empty(EmptyGrid2D::new([3, 2].into(), no_data_value)), ), @@ -1661,7 +1662,7 @@ mod tests { TileInformation { global_tile_position: [-1, 0].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Grid( Grid2D::new( @@ -1677,7 +1678,7 @@ mod tests { TileInformation { global_tile_position: [-1, 1].into(), tile_size_in_pixels: [3, 2].into(), - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), }, GridOrEmpty::Empty(EmptyGrid2D::new([3, 2].into(), no_data_value)), ), diff --git a/operators/src/source/gdal_source.rs b/operators/src/source/gdal_source.rs index baead06db..c787e9dc5 100755 --- a/operators/src/source/gdal_source.rs +++ b/operators/src/source/gdal_source.rs @@ -21,6 +21,7 @@ use geoengine_datatypes::raster::{ RasterProperties, RasterPropertiesEntry, RasterPropertiesEntryType, RasterPropertiesKey, RasterTile2D, }; +use geoengine_datatypes::util::test::TestDefault; use geoengine_datatypes::{dataset::DatasetId, raster::TileInformation}; use geoengine_datatypes::{ primitives::{TimeInstance, TimeInterval, TimeStep, TimeStepIter}, @@ -175,8 +176,8 @@ pub struct GdalDatasetGeoTransform { } /// Default implementation for testing purposes where geo transform doesn't matter -impl Default for GdalDatasetGeoTransform { - fn default() -> Self { +impl TestDefault for GdalDatasetGeoTransform { + fn test_default() -> Self { Self { origin_coordinate: (0.0, 0.0).into(), x_pixel_size: 1.0, @@ -1145,7 +1146,7 @@ mod tests { let params = GdalDatasetParameters { file_path: "/foo/bar_%TIME%.tiff".into(), rasterband_channel: 0, - geo_transform: Default::default(), + geo_transform: TestDefault::test_default(), width: 360, height: 180, file_not_found_handling: FileNotFoundHandling::NoData, @@ -1186,7 +1187,7 @@ mod tests { params: GdalDatasetParameters { file_path: "/foo/bar_%TIME%.tiff".into(), rasterband_channel: 0, - geo_transform: Default::default(), + geo_transform: TestDefault::test_default(), width: 360, height: 180, file_not_found_handling: FileNotFoundHandling::NoData, diff --git a/services/src/handlers/plots.rs b/services/src/handlers/plots.rs index d2bedea8c..249a958a0 100644 --- a/services/src/handlers/plots.rs +++ b/services/src/handlers/plots.rs @@ -197,6 +197,7 @@ struct WrappedPlotOutput { #[cfg(test)] mod tests { use chrono::NaiveDate; + use geoengine_datatypes::util::test::TestDefault; use num_traits::AsPrimitive; use serde_json::json; @@ -226,7 +227,7 @@ mod tests { data: vec![RasterTile2D::new_with_tile_info( TimeInterval::default(), TileInformation { - global_geo_transform: Default::default(), + global_geo_transform: TestDefault::test_default(), global_tile_position: [0, 0].into(), tile_size_in_pixels: [3, 2].into(), }, From 689bfd60e5d22148aac2b05d4f5b6a8e7141092c Mon Sep 17 00:00:00 2001 From: Michael Mattig Date: Fri, 10 Sep 2021 12:12:51 +0200 Subject: [PATCH 10/10] fix lower right pixel index calculation --- datatypes/src/raster/tiling.rs | 67 +++++++++++++++++-- .../src/adapters/raster_subquery_adapter.rs | 1 + 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/datatypes/src/raster/tiling.rs b/datatypes/src/raster/tiling.rs index 98ecc3047..8903e04bf 100644 --- a/datatypes/src/raster/tiling.rs +++ b/datatypes/src/raster/tiling.rs @@ -60,19 +60,37 @@ impl TilingStrategy { } } + /// compute the index of the upper left pixel that is contained in the `partition` pub fn upper_left_pixel_idx(&self, partition: SpatialPartition2D) -> GridIdx2D { self.geo_transform .coordinate_to_grid_idx_2d(partition.upper_left()) } + /// compute the index of the lower right pixel that is contained in the `partition` pub fn lower_right_pixel_idx(&self, partition: SpatialPartition2D) -> GridIdx2D { - let lr_idx = self - .geo_transform - .coordinate_to_grid_idx_2d(partition.lower_right()); - // TODO: only subtract if lower right coordinate is exactly on pixel edge because - // it is not included in the partition. We don't want to lose the pixel if - // it is actually contained in the partition - lr_idx - 1 + // as the lower right coordinate is not included in the partition we subtract an epsilon + // in order to not include the next pixel if the lower right coordinate is exactly on the + // edge of the next pixel + + // choose the epsilon relative to the pixel size + const EPSILON: f64 = 0.000_001; + let epsilon: Coordinate2D = ( + self.geo_transform.x_pixel_size() * EPSILON, + self.geo_transform.y_pixel_size() * EPSILON, + ) + .into(); + + // shift lower right by epsilon + let lower_right = partition.lower_right() - epsilon; + + // ensure we don't accidentally go beyond the upper left pixel + let lower_right = ( + lower_right.x.max(partition.upper_left().x), + lower_right.y.min(partition.upper_left().y), + ) + .into(); + + self.geo_transform.coordinate_to_grid_idx_2d(lower_right) } pub fn pixel_idx_to_tile_idx(&self, pixel_idx: GridIdx2D) -> GridIdx2D { @@ -225,3 +243,38 @@ impl SpatialPartitioned for TileInformation { SpatialPartition2D::new_unchecked(top_left_coord, lower_right_coord) } } + +#[cfg(test)] +mod tests { + use crate::util::test::TestDefault; + + use super::*; + + #[test] + fn lower_right_pixel_index_edge() { + let strat = TilingStrategy { + tile_size_in_pixels: [600, 600].into(), + geo_transform: GeoTransform::test_default(), + }; + + let partition = SpatialPartition2D::new((1., 1.).into(), (8., -8.).into()).unwrap(); + assert_eq!(strat.lower_right_pixel_idx(partition), [7, 7].into()); + + let partition = SpatialPartition2D::new((1., 1.).into(), (8.5, -8.).into()).unwrap(); + assert_eq!(strat.lower_right_pixel_idx(partition), [7, 8].into()); + + let partition = SpatialPartition2D::new((1., 1.).into(), (8., -8.5).into()).unwrap(); + assert_eq!(strat.lower_right_pixel_idx(partition), [8, 7].into()); + } + + #[test] + fn lower_right_pixel_index_inside() { + let strat = TilingStrategy { + tile_size_in_pixels: [600, 600].into(), + geo_transform: GeoTransform::test_default(), + }; + + let partition = SpatialPartition2D::new((1., 1.).into(), (7.5, -7.5).into()).unwrap(); + assert_eq!(strat.lower_right_pixel_idx(partition), [7, 7].into()); + } +} diff --git a/operators/src/adapters/raster_subquery_adapter.rs b/operators/src/adapters/raster_subquery_adapter.rs index 68f5c2c58..4fa367dd8 100644 --- a/operators/src/adapters/raster_subquery_adapter.rs +++ b/operators/src/adapters/raster_subquery_adapter.rs @@ -273,6 +273,7 @@ where fold_tile_spec, GridOrEmpty::Empty(EmptyGrid2D::::new( fold_tile_spec.tile_size_in_pixels, + // TODO: check if zero makes sense as default value or if we should return an error this.no_data_value.unwrap_or(PixelType::zero()), )), ))