1use arrow_array::StructArray;
18use datafusion_common::Result;
19use fastrand::Rng;
20use sedona_raster::array::RasterStructArray;
21use sedona_raster::builder::RasterBuilder;
22use sedona_raster::traits::{BandMetadata, RasterMetadata, RasterRef};
23use sedona_schema::crs::lnglat;
24use sedona_schema::raster::{BandDataType, StorageType};
25
26pub fn generate_test_rasters(
29 count: usize,
30 null_raster_index: Option<usize>,
31) -> Result<StructArray> {
32 let mut builder = RasterBuilder::new(count);
33 let crs = lnglat().unwrap().to_crs_string();
34 for i in 0..count {
35 if matches!(null_raster_index, Some(index) if index == i) {
38 builder.append_null()?;
39 continue;
40 }
41
42 let raster_metadata = RasterMetadata {
43 width: i as u64 + 1,
44 height: i as u64 + 2,
45 upperleft_x: i as f64 + 1.0,
46 upperleft_y: i as f64 + 2.0,
47 scale_x: i.max(1) as f64 * 0.1,
48 scale_y: i.max(1) as f64 * -0.2,
49 skew_x: i as f64 * 0.03,
50 skew_y: i as f64 * 0.04,
51 };
52 builder.start_raster(&raster_metadata, Some(&crs))?;
53 builder.start_band(BandMetadata {
54 datatype: BandDataType::UInt16,
55 nodata_value: Some(vec![0u8; 2]),
56 storage_type: StorageType::InDb,
57 outdb_url: None,
58 outdb_band_id: None,
59 })?;
60
61 let pixel_count = (i + 1) * (i + 2); let mut band_data = Vec::with_capacity(pixel_count * 2); for pixel_value in 0..pixel_count as u16 {
64 band_data.extend_from_slice(&pixel_value.to_le_bytes());
65 }
66
67 builder.band_data_writer().append_value(&band_data);
68 builder.finish_band()?;
69 builder.finish_raster()?;
70 }
71
72 Ok(builder.finish()?)
73}
74
75pub fn generate_tiled_rasters(
81 tile_size: (usize, usize),
82 number_of_tiles: (usize, usize),
83 data_type: BandDataType,
84 seed: Option<u64>,
85) -> Result<StructArray> {
86 let mut rng = match seed {
87 Some(s) => Rng::with_seed(s),
88 None => Rng::new(),
89 };
90 let (tile_width, tile_height) = tile_size;
91 let (x_tiles, y_tiles) = number_of_tiles;
92 let mut raster_builder = RasterBuilder::new(x_tiles * y_tiles);
93 let band_count = 3;
94 let crs = lnglat().unwrap().to_crs_string();
95
96 for tile_y in 0..y_tiles {
97 for tile_x in 0..x_tiles {
98 let origin_x = (tile_x * tile_width) as f64;
99 let origin_y = (tile_y * tile_height) as f64;
100
101 let raster_metadata = RasterMetadata {
102 width: tile_width as u64,
103 height: tile_height as u64,
104 upperleft_x: origin_x,
105 upperleft_y: origin_y,
106 scale_x: 1.0,
107 scale_y: 1.0,
108 skew_x: 0.0,
109 skew_y: 0.0,
110 };
111
112 raster_builder.start_raster(&raster_metadata, Some(&crs))?;
113
114 for _ in 0..band_count {
115 let nodata_value = get_nodata_value_for_type(&data_type);
117
118 let nodata_value_bytes = nodata_value.clone();
119
120 let band_metadata = BandMetadata {
121 nodata_value,
122 storage_type: StorageType::InDb,
123 datatype: data_type,
124 outdb_url: None,
125 outdb_band_id: None,
126 };
127
128 raster_builder.start_band(band_metadata)?;
129
130 let pixel_count = tile_width * tile_height;
131
132 let corner_position =
134 get_corner_position(tile_x, tile_y, x_tiles, y_tiles, tile_width, tile_height);
135 let band_data = generate_random_band_data(
136 pixel_count,
137 &data_type,
138 nodata_value_bytes.as_deref(),
139 corner_position,
140 &mut rng,
141 );
142
143 raster_builder.band_data_writer().append_value(&band_data);
144 raster_builder.finish_band()?;
145 }
146
147 raster_builder.finish_raster()?;
148 }
149 }
150
151 Ok(raster_builder.finish()?)
152}
153
154pub fn build_noninvertible_raster() -> StructArray {
157 let mut builder = RasterBuilder::new(1);
158 let metadata = RasterMetadata {
159 width: 1,
160 height: 1,
161 upperleft_x: 0.0,
162 upperleft_y: 0.0,
163 scale_x: 0.0,
164 scale_y: 0.0,
165 skew_x: 0.0,
166 skew_y: 0.0,
167 };
168 let crs = lnglat().unwrap().to_crs_string();
169 builder
170 .start_raster(&metadata, Some(&crs))
171 .expect("start raster");
172 builder
173 .start_band(BandMetadata {
174 datatype: BandDataType::UInt8,
175 nodata_value: None,
176 storage_type: StorageType::InDb,
177 outdb_url: None,
178 outdb_band_id: None,
179 })
180 .expect("start band");
181 builder.band_data_writer().append_value([0u8]);
182 builder.finish_band().expect("finish band");
183 builder.finish_raster().expect("finish raster");
184 builder.finish().expect("finish")
185}
186
187fn get_corner_position(
190 tile_x: usize,
191 tile_y: usize,
192 x_tiles: usize,
193 y_tiles: usize,
194 tile_width: usize,
195 tile_height: usize,
196) -> Option<usize> {
197 if tile_x == 0 && tile_y == 0 {
199 return Some(0);
200 }
201 if tile_x == x_tiles - 1 && tile_y == 0 {
203 return Some(tile_width - 1);
204 }
205 if tile_x == 0 && tile_y == y_tiles - 1 {
207 return Some((tile_height - 1) * tile_width);
208 }
209 if tile_x == x_tiles - 1 && tile_y == y_tiles - 1 {
211 return Some(tile_height * tile_width - 1);
212 }
213 None
214}
215
216fn generate_random_band_data(
217 pixel_count: usize,
218 data_type: &BandDataType,
219 nodata_bytes: Option<&[u8]>,
220 corner_position: Option<usize>,
221 rng: &mut Rng,
222) -> Vec<u8> {
223 macro_rules! gen_band {
226 ($byte_size:expr, $rng_expr:expr) => {{
227 let byte_size: usize = $byte_size;
228 let mut data = Vec::with_capacity(pixel_count * byte_size);
229 for _ in 0..pixel_count {
230 data.extend_from_slice(&$rng_expr.to_ne_bytes());
231 }
232 if let (Some(nodata), Some(pos)) = (nodata_bytes, corner_position) {
233 if nodata.len() >= byte_size && pos * byte_size + byte_size <= data.len() {
234 data[pos * byte_size..(pos * byte_size) + byte_size]
235 .copy_from_slice(&nodata[0..byte_size]);
236 }
237 }
238 data
239 }};
240 }
241
242 match data_type {
243 BandDataType::UInt8 => gen_band!(1, rng.u8(..)),
244 BandDataType::Int8 => gen_band!(1, rng.i8(..)),
245 BandDataType::UInt16 => gen_band!(2, rng.u16(..)),
246 BandDataType::Int16 => gen_band!(2, rng.i16(..)),
247 BandDataType::UInt32 => gen_band!(4, rng.u32(..)),
248 BandDataType::Int32 => gen_band!(4, rng.i32(..)),
249 BandDataType::UInt64 => gen_band!(8, rng.u64(..)),
250 BandDataType::Int64 => gen_band!(8, rng.i64(..)),
251 BandDataType::Float32 => gen_band!(4, rng.f32()),
252 BandDataType::Float64 => gen_band!(8, rng.f64()),
253 }
254}
255
256fn get_nodata_value_for_type(data_type: &BandDataType) -> Option<Vec<u8>> {
257 match data_type {
258 BandDataType::UInt8 => Some(vec![255u8]),
259 BandDataType::Int8 => Some(i8::MIN.to_ne_bytes().to_vec()),
260 BandDataType::UInt16 => Some(u16::MAX.to_ne_bytes().to_vec()),
261 BandDataType::Int16 => Some(i16::MIN.to_ne_bytes().to_vec()),
262 BandDataType::UInt32 => Some(u32::MAX.to_ne_bytes().to_vec()),
263 BandDataType::Int32 => Some(i32::MIN.to_ne_bytes().to_vec()),
264 BandDataType::UInt64 => Some(u64::MAX.to_ne_bytes().to_vec()),
265 BandDataType::Int64 => Some(i64::MIN.to_ne_bytes().to_vec()),
266 BandDataType::Float32 => Some(f32::NAN.to_ne_bytes().to_vec()),
267 BandDataType::Float64 => Some(f64::NAN.to_ne_bytes().to_vec()),
268 }
269}
270
271pub fn assert_raster_arrays_equal(
273 raster_array1: &RasterStructArray,
274 raster_array2: &RasterStructArray,
275) {
276 assert_eq!(
277 raster_array1.len(),
278 raster_array2.len(),
279 "Raster array lengths do not match"
280 );
281
282 for i in 0..raster_array1.len() {
283 let raster1 = raster_array1.get(i).unwrap();
284 let raster2 = raster_array2.get(i).unwrap();
285 assert_raster_equal(&raster1, &raster2);
286 }
287}
288
289pub fn assert_raster_equal(raster1: &impl RasterRef, raster2: &impl RasterRef) {
291 let meta1 = raster1.metadata();
293 let meta2 = raster2.metadata();
294 assert_eq!(meta1.width(), meta2.width(), "Raster widths do not match");
295 assert_eq!(
296 meta1.height(),
297 meta2.height(),
298 "Raster heights do not match"
299 );
300 assert_eq!(
301 meta1.upper_left_x(),
302 meta2.upper_left_x(),
303 "Raster upper left x does not match"
304 );
305 assert_eq!(
306 meta1.upper_left_y(),
307 meta2.upper_left_y(),
308 "Raster upper left y does not match"
309 );
310 assert_eq!(
311 meta1.scale_x(),
312 meta2.scale_x(),
313 "Raster scale x does not match"
314 );
315 assert_eq!(
316 meta1.scale_y(),
317 meta2.scale_y(),
318 "Raster scale y does not match"
319 );
320 assert_eq!(
321 meta1.skew_x(),
322 meta2.skew_x(),
323 "Raster skew x does not match"
324 );
325 assert_eq!(
326 meta1.skew_y(),
327 meta2.skew_y(),
328 "Raster skew y does not match"
329 );
330
331 let bands1 = raster1.bands();
333 let bands2 = raster2.bands();
334 assert_eq!(bands1.len(), bands2.len(), "Number of bands do not match");
335
336 for band_index in 0..bands1.len() {
337 let band1 = bands1.band(band_index + 1).unwrap();
338 let band2 = bands2.band(band_index + 1).unwrap();
339
340 let band_meta1 = band1.metadata();
341 let band_meta2 = band2.metadata();
342 assert_eq!(
343 band_meta1.data_type().unwrap(),
344 band_meta2.data_type().unwrap(),
345 "Band data types do not match"
346 );
347 assert_eq!(
348 band_meta1.nodata_value(),
349 band_meta2.nodata_value(),
350 "Band nodata values do not match"
351 );
352 assert_eq!(
353 band_meta1.storage_type().unwrap(),
354 band_meta2.storage_type().unwrap(),
355 "Band storage types do not match"
356 );
357 assert_eq!(
358 band_meta1.outdb_url(),
359 band_meta2.outdb_url(),
360 "Band outdb URLs do not match"
361 );
362 assert_eq!(
363 band_meta1.outdb_band_id(),
364 band_meta2.outdb_band_id(),
365 "Band outdb band IDs do not match"
366 );
367
368 assert_eq!(band1.data(), band2.data(), "Band data does not match");
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use sedona_raster::array::RasterStructArray;
376 use sedona_raster::traits::RasterRef;
377
378 #[test]
379 fn test_generate_test_rasters() {
380 let count = 5;
381 let struct_array = generate_test_rasters(count, None).unwrap();
382 let raster_array = RasterStructArray::new(&struct_array);
383 assert_eq!(raster_array.len(), count);
384
385 for i in 0..count {
386 let raster = raster_array.get(i).unwrap();
387 let metadata = raster.metadata();
388 assert_eq!(metadata.width(), i as u64 + 1);
389 assert_eq!(metadata.height(), i as u64 + 2);
390 assert_eq!(metadata.upper_left_x(), i as f64 + 1.0);
391 assert_eq!(metadata.upper_left_y(), i as f64 + 2.0);
392 assert_eq!(metadata.scale_x(), (i.max(1) as f64) * 0.1);
393 assert_eq!(metadata.scale_y(), (i.max(1) as f64) * -0.2);
394 assert_eq!(metadata.skew_x(), (i as f64) * 0.03);
395 assert_eq!(metadata.skew_y(), (i as f64) * 0.04);
396
397 let bands = raster.bands();
398 let band = bands.band(1).unwrap();
399 let band_metadata = band.metadata();
400 assert_eq!(band_metadata.data_type().unwrap(), BandDataType::UInt16);
401 assert_eq!(band_metadata.nodata_value(), Some(&[0u8, 0u8][..]));
402 assert_eq!(band_metadata.storage_type().unwrap(), StorageType::InDb);
403 assert_eq!(band_metadata.outdb_url(), None);
404 assert_eq!(band_metadata.outdb_band_id(), None);
405
406 let band_data = band.data();
407 let expected_pixel_count = (i + 1) * (i + 2); let mut actual_pixel_values = Vec::new();
411 for chunk in band_data.chunks_exact(2) {
412 let value = u16::from_le_bytes([chunk[0], chunk[1]]);
413 actual_pixel_values.push(value);
414 }
415 let expected_pixel_values: Vec<u16> = (0..expected_pixel_count as u16).collect();
416 assert_eq!(actual_pixel_values, expected_pixel_values);
417 }
418 }
419
420 #[test]
421 fn test_generate_tiled_rasters() {
422 let tile_size = (64, 64);
423 let number_of_tiles = (4, 4);
424 let data_type = BandDataType::UInt8;
425 let struct_array =
426 generate_tiled_rasters(tile_size, number_of_tiles, data_type, Some(43)).unwrap();
427 let raster_array = RasterStructArray::new(&struct_array);
428 assert_eq!(raster_array.len(), 16); for i in 0..16 {
430 let raster = raster_array.get(i).unwrap();
431 let metadata = raster.metadata();
432 assert_eq!(metadata.width(), 64);
433 assert_eq!(metadata.height(), 64);
434 assert_eq!(metadata.upper_left_x(), ((i % 4) * 64) as f64);
435 assert_eq!(metadata.upper_left_y(), ((i / 4) * 64) as f64);
436 let bands = raster.bands();
437 assert_eq!(bands.len(), 3);
438 for band_index in 0..3 {
439 let band = bands.band(band_index + 1).unwrap();
440 let band_metadata = band.metadata();
441 assert_eq!(band_metadata.data_type().unwrap(), BandDataType::UInt8);
442 assert_eq!(band_metadata.storage_type().unwrap(), StorageType::InDb);
443 let band_data = band.data();
444 assert_eq!(band_data.len(), 64 * 64); }
446 }
447 }
448
449 #[test]
450 fn test_raster_arrays_equal() {
451 let raster_array1 = generate_test_rasters(3, None).unwrap();
452 let raster_struct_array1 = RasterStructArray::new(&raster_array1);
453 assert_raster_arrays_equal(&raster_struct_array1, &raster_struct_array1);
455 }
456
457 #[test]
458 #[should_panic = "Raster array lengths do not match"]
459 fn test_raster_arrays_not_equal() {
460 let raster_array1 = generate_test_rasters(3, None).unwrap();
461 let raster_struct_array1 = RasterStructArray::new(&raster_array1);
462
463 let raster_array2 = generate_test_rasters(4, None).unwrap();
465 let raster_struct_array2 = RasterStructArray::new(&raster_array2);
466 assert_raster_arrays_equal(&raster_struct_array1, &raster_struct_array2);
467 }
468
469 #[test]
470 fn test_raster_equal() {
471 let raster_array1 =
472 generate_tiled_rasters((256, 256), (1, 1), BandDataType::UInt8, Some(43)).unwrap();
473 let raster1 = RasterStructArray::new(&raster_array1).get(0).unwrap();
474
475 assert_raster_equal(&raster1, &raster1);
477 }
478
479 #[test]
480 #[should_panic = "Band data does not match"]
481 fn test_raster_different_band_data() {
482 let raster_array1 =
483 generate_tiled_rasters((128, 128), (1, 1), BandDataType::UInt8, Some(43)).unwrap();
484 let raster_array2 =
485 generate_tiled_rasters((128, 128), (1, 1), BandDataType::UInt8, Some(47)).unwrap();
486
487 let raster1 = RasterStructArray::new(&raster_array1).get(0).unwrap();
488 let raster2 = RasterStructArray::new(&raster_array2).get(0).unwrap();
489 assert_raster_equal(&raster1, &raster2);
490 }
491
492 #[test]
493 #[should_panic = "Raster upper left x does not match"]
494 fn test_raster_different_metadata() {
495 let raster_array =
496 generate_tiled_rasters((128, 128), (2, 1), BandDataType::UInt8, Some(43)).unwrap();
497 let raster1 = RasterStructArray::new(&raster_array).get(0).unwrap();
498 let raster2 = RasterStructArray::new(&raster_array).get(1).unwrap();
499 assert_raster_equal(&raster1, &raster2);
500 }
501}