1use super::{RasterChunk, RasterStream, RasterStreamConfig, RasterStreaming};
7use crate::error::{Result, StreamingError};
8use async_trait::async_trait;
9use oxigdal_core::{
10 buffer::RasterBuffer,
11 io::FileDataSource,
12 types::{BoundingBox, GeoTransform, RasterMetadata},
13};
14use oxigdal_geotiff::GeoTiffReader;
15use std::path::{Path, PathBuf};
16use std::sync::Arc;
17use tokio::sync::Semaphore;
18use tokio::task;
19use tracing::{debug, error, info};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum RasterFormat {
24 GeoTiff,
26}
27
28pub(crate) fn detect_format_from_extension(path: &Path) -> Option<RasterFormat> {
30 let ext = path.extension()?.to_str()?.to_ascii_lowercase();
31 match ext.as_str() {
32 "tif" | "tiff" | "geotiff" | "gtiff" => Some(RasterFormat::GeoTiff),
33 _ => None,
34 }
35}
36
37pub(crate) fn detect_format_from_magic(path: &Path) -> Option<RasterFormat> {
39 let data = std::fs::read(path).ok()?;
40 if data.len() >= 4 && oxigdal_geotiff::is_tiff(&data[..4.min(data.len())]) {
41 return Some(RasterFormat::GeoTiff);
42 }
43 None
44}
45
46pub(crate) fn detect_format(path: &Path) -> Result<RasterFormat> {
48 if let Some(fmt) = detect_format_from_extension(path) {
49 return Ok(fmt);
50 }
51 if let Some(fmt) = detect_format_from_magic(path) {
52 return Ok(fmt);
53 }
54 Err(StreamingError::Other(format!(
55 "Unsupported raster format for file: {}",
56 path.display()
57 )))
58}
59
60pub struct RasterStreamReader {
65 path: PathBuf,
67
68 config: RasterStreamConfig,
70
71 metadata: RasterMetadata,
73
74 stream: Option<RasterStream>,
76
77 prefetch_semaphore: Arc<Semaphore>,
79
80 bands: Vec<usize>,
82
83 format: RasterFormat,
85}
86
87impl RasterStreamReader {
88 pub async fn new<P: AsRef<Path>>(path: P, config: RasterStreamConfig) -> Result<Self> {
93 let path = path.as_ref().to_path_buf();
94
95 if !path.exists() {
97 return Err(StreamingError::Io(std::io::Error::new(
98 std::io::ErrorKind::NotFound,
99 format!("File not found: {}", path.display()),
100 )));
101 }
102
103 let format = detect_format(&path)?;
105
106 let metadata = Self::read_metadata_from_file(&path, format).await?;
108
109 info!(
110 "Created raster stream reader for {}x{} raster with {} bands ({})",
111 metadata.width,
112 metadata.height,
113 metadata.band_count,
114 path.display()
115 );
116
117 let prefetch_semaphore = Arc::new(Semaphore::new(config.prefetch_count));
118
119 Ok(Self {
120 path,
121 config,
122 metadata,
123 stream: None,
124 prefetch_semaphore,
125 bands: vec![0], format,
127 })
128 }
129
130 async fn read_metadata_from_file(path: &Path, format: RasterFormat) -> Result<RasterMetadata> {
132 let path = path.to_path_buf();
133 task::spawn_blocking(move || match format {
134 RasterFormat::GeoTiff => Self::read_geotiff_metadata(&path),
135 })
136 .await
137 .map_err(|e| StreamingError::Other(format!("Task join error: {}", e)))?
138 }
139
140 fn read_geotiff_metadata(path: &Path) -> Result<RasterMetadata> {
142 let source = FileDataSource::open(path).map_err(|e| {
143 StreamingError::Other(format!(
144 "Failed to open GeoTIFF file '{}': {}",
145 path.display(),
146 e
147 ))
148 })?;
149
150 let reader = GeoTiffReader::open(source).map_err(|e| {
151 StreamingError::Other(format!(
152 "Failed to parse GeoTIFF '{}': {}",
153 path.display(),
154 e
155 ))
156 })?;
157
158 Ok(reader.metadata())
159 }
160
161 pub fn with_bands(mut self, bands: Vec<usize>) -> Self {
163 self.bands = bands;
164 self
165 }
166
167 pub async fn start(&mut self) -> Result<()> {
169 let stream = RasterStream::new(self.config.clone(), self.metadata.clone())?;
170
171 if self.config.parallel {
173 self.start_prefetch_workers().await?;
174 }
175
176 self.stream = Some(stream);
177 Ok(())
178 }
179
180 async fn start_prefetch_workers(&self) -> Result<()> {
182 let num_workers = self.config.num_workers;
183
184 for worker_id in 0..num_workers {
185 let _path = self.path.clone();
186 let _config = self.config.clone();
187 let _metadata = self.metadata.clone();
188 let _bands = self.bands.clone();
189 let _semaphore = Arc::clone(&self.prefetch_semaphore);
190
191 tokio::spawn(async move {
192 debug!("Prefetch worker {} started", worker_id);
193 debug!("Prefetch worker {} finished", worker_id);
195 });
196 }
197
198 Ok(())
199 }
200
201 pub async fn read_chunk(&self, row: usize, col: usize) -> Result<RasterChunk> {
206 let _permit = self
207 .prefetch_semaphore
208 .acquire()
209 .await
210 .map_err(|e| StreamingError::Other(e.to_string()))?;
211
212 let path = self.path.clone();
213 let config = self.config.clone();
214 let metadata = self.metadata.clone();
215 let bands = self.bands.clone();
216 let format = self.format;
217
218 task::spawn_blocking(move || {
219 Self::read_chunk_blocking(path, row, col, config, metadata, bands, format)
220 })
221 .await
222 .map_err(|e| StreamingError::Other(e.to_string()))?
223 }
224
225 fn read_chunk_blocking(
227 path: PathBuf,
228 row: usize,
229 col: usize,
230 config: RasterStreamConfig,
231 metadata: RasterMetadata,
232 _bands: Vec<usize>,
233 format: RasterFormat,
234 ) -> Result<RasterChunk> {
235 let chunk_width = config.chunk_size.0;
236 let chunk_height = config.chunk_size.1;
237 let overlap = config.overlap;
238
239 let effective_width = chunk_width.saturating_sub(overlap).max(1);
240 let effective_height = chunk_height.saturating_sub(overlap).max(1);
241
242 let x_start = col * effective_width;
243 let y_start = row * effective_height;
244 let x_end = (x_start + chunk_width).min(metadata.width as usize);
245 let y_end = (y_start + chunk_height).min(metadata.height as usize);
246
247 let actual_width = x_end.saturating_sub(x_start);
248 let actual_height = y_end.saturating_sub(y_start);
249
250 if actual_width == 0 || actual_height == 0 {
251 return Err(StreamingError::InvalidOperation(format!(
252 "Empty chunk at ({}, {}): {}x{}",
253 row, col, actual_width, actual_height
254 )));
255 }
256
257 let buffer = match format {
259 RasterFormat::GeoTiff => Self::read_geotiff_chunk(
260 &path,
261 &metadata,
262 x_start,
263 y_start,
264 actual_width,
265 actual_height,
266 )?,
267 };
268
269 let gt = metadata
271 .geo_transform
272 .as_ref()
273 .ok_or_else(|| StreamingError::InvalidState("No geotransform available".to_string()))?;
274
275 let min_x = gt.origin_x + (x_start as f64) * gt.pixel_width;
276 let max_y = gt.origin_y + (y_start as f64) * gt.pixel_height;
277 let max_x = gt.origin_x + (x_end as f64) * gt.pixel_width;
278 let min_y = gt.origin_y + (y_end as f64) * gt.pixel_height;
279
280 let bbox = BoundingBox::new(min_x, min_y, max_x, max_y).map_err(StreamingError::Core)?;
281
282 let chunk_gt = GeoTransform {
284 origin_x: min_x,
285 origin_y: max_y,
286 pixel_width: gt.pixel_width,
287 pixel_height: gt.pixel_height,
288 row_rotation: gt.row_rotation,
289 col_rotation: gt.col_rotation,
290 };
291
292 Ok(RasterChunk::new(buffer, bbox, chunk_gt, (row, col)))
293 }
294
295 fn read_geotiff_chunk(
298 path: &Path,
299 metadata: &RasterMetadata,
300 x_start: usize,
301 y_start: usize,
302 width: usize,
303 height: usize,
304 ) -> Result<RasterBuffer> {
305 let source = FileDataSource::open(path).map_err(|e| {
306 StreamingError::Other(format!("Failed to open GeoTIFF for chunk read: {}", e))
307 })?;
308
309 let reader = GeoTiffReader::open(source).map_err(|e| {
310 StreamingError::Other(format!("Failed to parse GeoTIFF for chunk read: {}", e))
311 })?;
312
313 let info = reader.metadata();
314 let data_type = info.data_type;
315 let bytes_per_pixel = data_type.size_bytes() * info.band_count as usize;
316 let img_width = info.width as usize;
317 let img_height = info.height as usize;
318
319 let geotiff_info = reader.metadata();
321 let (tile_w, tile_h) = match geotiff_info.layout {
322 oxigdal_core::types::PixelLayout::Tiled {
323 tile_width,
324 tile_height,
325 } => (tile_width as usize, tile_height as usize),
326 _ => {
327 return Self::read_geotiff_chunk_full_band(
330 path, metadata, x_start, y_start, width, height,
331 );
332 }
333 };
334
335 let out_size = width * height * bytes_per_pixel;
337 let mut out_data = vec![0u8; out_size];
338
339 let tile_col_start = x_start / tile_w;
341 let tile_col_end = (x_start + width).min(img_width).div_ceil(tile_w);
342 let tile_row_start = y_start / tile_h;
343 let tile_row_end = (y_start + height).min(img_height).div_ceil(tile_h);
344
345 for ty in tile_row_start..tile_row_end {
346 for tx in tile_col_start..tile_col_end {
347 let tile_data = reader.read_tile(0, tx as u32, ty as u32).map_err(|e| {
349 StreamingError::Other(format!("Failed to read tile ({}, {}): {}", tx, ty, e))
350 })?;
351
352 let tile_x0 = tx * tile_w;
354 let tile_y0 = ty * tile_h;
355 let tile_x1 = (tile_x0 + tile_w).min(img_width);
356 let tile_y1 = (tile_y0 + tile_h).min(img_height);
357
358 let overlap_x0 = x_start.max(tile_x0);
359 let overlap_y0 = y_start.max(tile_y0);
360 let overlap_x1 = (x_start + width).min(tile_x1);
361 let overlap_y1 = (y_start + height).min(tile_y1);
362
363 if overlap_x0 >= overlap_x1 || overlap_y0 >= overlap_y1 {
364 continue;
365 }
366
367 let copy_width = overlap_x1 - overlap_x0;
369 for row_idx in overlap_y0..overlap_y1 {
370 let src_row_in_tile = row_idx - tile_y0;
371 let src_col_in_tile = overlap_x0 - tile_x0;
372 let src_offset = (src_row_in_tile * tile_w + src_col_in_tile) * bytes_per_pixel;
373
374 let dst_row = row_idx - y_start;
375 let dst_col = overlap_x0 - x_start;
376 let dst_offset = (dst_row * width + dst_col) * bytes_per_pixel;
377
378 let copy_bytes = copy_width * bytes_per_pixel;
379
380 if src_offset + copy_bytes <= tile_data.len()
381 && dst_offset + copy_bytes <= out_data.len()
382 {
383 out_data[dst_offset..dst_offset + copy_bytes]
384 .copy_from_slice(&tile_data[src_offset..src_offset + copy_bytes]);
385 }
386 }
387 }
388 }
389
390 let band_count = metadata.band_count as u64;
396 let effective_width = width as u64 * band_count;
397 RasterBuffer::new(
398 out_data,
399 effective_width,
400 height as u64,
401 data_type,
402 metadata.nodata,
403 )
404 .map_err(|e| StreamingError::Other(format!("Failed to create RasterBuffer: {}", e)))
405 }
406
407 fn read_geotiff_chunk_full_band(
409 path: &Path,
410 metadata: &RasterMetadata,
411 x_start: usize,
412 y_start: usize,
413 width: usize,
414 height: usize,
415 ) -> Result<RasterBuffer> {
416 let source = FileDataSource::open(path).map_err(|e| {
417 StreamingError::Other(format!("Failed to open GeoTIFF for band read: {}", e))
418 })?;
419
420 let reader = GeoTiffReader::open(source).map_err(|e| {
421 StreamingError::Other(format!("Failed to parse GeoTIFF for band read: {}", e))
422 })?;
423
424 let info = reader.metadata();
425 let data_type = info.data_type;
426 let bytes_per_pixel = data_type.size_bytes() * info.band_count as usize;
427 let img_width = info.width as usize;
428
429 let band_data = reader
431 .read_band(0, 0)
432 .map_err(|e| StreamingError::Other(format!("Failed to read band: {}", e)))?;
433
434 let out_size = width * height * bytes_per_pixel;
436 let mut out_data = vec![0u8; out_size];
437
438 for row_idx in 0..height {
439 let src_y = y_start + row_idx;
440 if src_y >= info.height as usize {
441 break;
442 }
443 let src_offset = (src_y * img_width + x_start) * bytes_per_pixel;
444 let dst_offset = row_idx * width * bytes_per_pixel;
445 let copy_width = width.min(img_width.saturating_sub(x_start));
446 let copy_bytes = copy_width * bytes_per_pixel;
447
448 if src_offset + copy_bytes <= band_data.len()
449 && dst_offset + copy_bytes <= out_data.len()
450 {
451 out_data[dst_offset..dst_offset + copy_bytes]
452 .copy_from_slice(&band_data[src_offset..src_offset + copy_bytes]);
453 }
454 }
455
456 let band_count = metadata.band_count as u64;
457 let effective_width = width as u64 * band_count;
458 RasterBuffer::new(
459 out_data,
460 effective_width,
461 height as u64,
462 data_type,
463 metadata.nodata,
464 )
465 .map_err(|e| StreamingError::Other(format!("Failed to create RasterBuffer: {}", e)))
466 }
467
468 pub async fn read_chunks(&self, chunks: Vec<(usize, usize)>) -> Result<Vec<RasterChunk>> {
470 let mut handles = Vec::with_capacity(chunks.len());
471
472 for (row, col) in chunks {
473 let path = self.path.clone();
474 let config = self.config.clone();
475 let metadata = self.metadata.clone();
476 let bands = self.bands.clone();
477 let semaphore = Arc::clone(&self.prefetch_semaphore);
478 let format = self.format;
479
480 let handle = tokio::spawn(async move {
481 let _permit = semaphore
482 .acquire()
483 .await
484 .map_err(|e| StreamingError::Other(e.to_string()))?;
485
486 task::spawn_blocking(move || {
487 Self::read_chunk_blocking(path, row, col, config, metadata, bands, format)
488 })
489 .await
490 .map_err(|e| StreamingError::Other(e.to_string()))?
491 });
492
493 handles.push(handle);
494 }
495
496 let mut results = Vec::with_capacity(handles.len());
497 for handle in handles {
498 match handle.await {
499 Ok(Ok(chunk)) => results.push(chunk),
500 Ok(Err(e)) => {
501 error!("Failed to read chunk: {}", e);
502 return Err(e);
503 }
504 Err(e) => {
505 error!("Task panicked: {}", e);
506 return Err(StreamingError::Other(e.to_string()));
507 }
508 }
509 }
510
511 Ok(results)
512 }
513
514 pub fn metadata(&self) -> &RasterMetadata {
516 &self.metadata
517 }
518
519 pub fn config(&self) -> &RasterStreamConfig {
521 &self.config
522 }
523
524 pub fn format(&self) -> RasterFormat {
526 self.format
527 }
528}
529
530#[async_trait]
531impl RasterStreaming for RasterStreamReader {
532 async fn next_chunk(&mut self) -> Result<Option<RasterChunk>> {
533 let stream = self
534 .stream
535 .as_mut()
536 .ok_or_else(|| StreamingError::InvalidState("Stream not started".to_string()))?;
537 stream.next_chunk().await
538 }
539
540 async fn next_chunks(&mut self, count: usize) -> Result<Vec<RasterChunk>> {
541 let stream = self
542 .stream
543 .as_mut()
544 .ok_or_else(|| StreamingError::InvalidState("Stream not started".to_string()))?;
545 stream.next_chunks(count).await
546 }
547
548 async fn seek_to_chunk(&mut self, row: usize, col: usize) -> Result<()> {
549 let stream = self
550 .stream
551 .as_mut()
552 .ok_or_else(|| StreamingError::InvalidState("Stream not started".to_string()))?;
553 stream.seek_to_chunk(row, col).await
554 }
555
556 fn total_chunks(&self) -> (usize, usize) {
557 self.stream
558 .as_ref()
559 .map(|s| s.total_chunks())
560 .unwrap_or((0, 0))
561 }
562
563 fn current_position(&self) -> (usize, usize) {
564 self.stream
565 .as_ref()
566 .map(|s| s.current_position())
567 .unwrap_or((0, 0))
568 }
569
570 fn has_more_chunks(&self) -> bool {
571 self.stream
572 .as_ref()
573 .map(|s| s.has_more_chunks())
574 .unwrap_or(false)
575 }
576}
577
578pub struct RasterStreamReaderBuilder {
580 path: PathBuf,
581 config: RasterStreamConfig,
582 bands: Vec<usize>,
583}
584
585impl RasterStreamReaderBuilder {
586 pub fn new<P: AsRef<Path>>(path: P) -> Self {
588 Self {
589 path: path.as_ref().to_path_buf(),
590 config: RasterStreamConfig::default(),
591 bands: vec![0],
592 }
593 }
594
595 pub fn chunk_size(mut self, width: usize, height: usize) -> Self {
597 self.config = self.config.with_chunk_size(width, height);
598 self
599 }
600
601 pub fn overlap(mut self, overlap: usize) -> Self {
603 self.config = self.config.with_overlap(overlap);
604 self
605 }
606
607 pub fn compression(mut self, level: u8) -> Self {
609 self.config = self.config.with_compression(level);
610 self
611 }
612
613 pub fn bands(mut self, bands: Vec<usize>) -> Self {
615 self.bands = bands;
616 self
617 }
618
619 pub fn parallel(mut self, num_workers: usize) -> Self {
621 self.config = self.config.with_parallel(true, num_workers);
622 self
623 }
624
625 pub async fn build(self) -> Result<RasterStreamReader> {
627 let reader = RasterStreamReader::new(self.path, self.config).await?;
628 Ok(reader.with_bands(self.bands))
629 }
630}
631
632#[cfg(test)]
633mod tests;