1use super::{RasterChunk, RasterStream, RasterStreamConfig, RasterStreaming};
7use crate::error::{Result, StreamingError};
8use async_trait::async_trait;
9use oxigeo_core::{
10 buffer::RasterBuffer,
11 io::FileDataSource,
12 types::{BoundingBox, GeoTransform, RasterMetadata},
13};
14use oxigeo_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 && oxigeo_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 Some((tile_w, tile_h)) = reader.tile_size() else {
328 return Self::read_geotiff_chunk_full_band(
329 path, metadata, x_start, y_start, width, height,
330 );
331 };
332 let (tile_w, tile_h) = (tile_w as usize, tile_h as usize);
333
334 let out_size = width * height * bytes_per_pixel;
336 let mut out_data = vec![0u8; out_size];
337
338 let tile_col_start = x_start / tile_w;
340 let tile_col_end = (x_start + width).min(img_width).div_ceil(tile_w);
341 let tile_row_start = y_start / tile_h;
342 let tile_row_end = (y_start + height).min(img_height).div_ceil(tile_h);
343
344 for ty in tile_row_start..tile_row_end {
345 for tx in tile_col_start..tile_col_end {
346 let tile_data = reader.read_tile(0, tx as u32, ty as u32).map_err(|e| {
348 StreamingError::Other(format!("Failed to read tile ({}, {}): {}", tx, ty, e))
349 })?;
350
351 let tile_x0 = tx * tile_w;
353 let tile_y0 = ty * tile_h;
354 let tile_x1 = (tile_x0 + tile_w).min(img_width);
355 let tile_y1 = (tile_y0 + tile_h).min(img_height);
356
357 let overlap_x0 = x_start.max(tile_x0);
358 let overlap_y0 = y_start.max(tile_y0);
359 let overlap_x1 = (x_start + width).min(tile_x1);
360 let overlap_y1 = (y_start + height).min(tile_y1);
361
362 if overlap_x0 >= overlap_x1 || overlap_y0 >= overlap_y1 {
363 continue;
364 }
365
366 let copy_width = overlap_x1 - overlap_x0;
368 for row_idx in overlap_y0..overlap_y1 {
369 let src_row_in_tile = row_idx - tile_y0;
370 let src_col_in_tile = overlap_x0 - tile_x0;
371 let src_offset = (src_row_in_tile * tile_w + src_col_in_tile) * bytes_per_pixel;
372
373 let dst_row = row_idx - y_start;
374 let dst_col = overlap_x0 - x_start;
375 let dst_offset = (dst_row * width + dst_col) * bytes_per_pixel;
376
377 let copy_bytes = copy_width * bytes_per_pixel;
378
379 if src_offset + copy_bytes <= tile_data.len()
380 && dst_offset + copy_bytes <= out_data.len()
381 {
382 out_data[dst_offset..dst_offset + copy_bytes]
383 .copy_from_slice(&tile_data[src_offset..src_offset + copy_bytes]);
384 }
385 }
386 }
387 }
388
389 let band_count = info.band_count as u64;
401 let effective_width = width as u64 * band_count;
402 RasterBuffer::new(
403 out_data,
404 effective_width,
405 height as u64,
406 data_type,
407 metadata.nodata,
408 )
409 .map_err(|e| StreamingError::Other(format!("Failed to create RasterBuffer: {}", e)))
410 }
411
412 fn read_geotiff_chunk_full_band(
424 path: &Path,
425 metadata: &RasterMetadata,
426 x_start: usize,
427 y_start: usize,
428 width: usize,
429 height: usize,
430 ) -> Result<RasterBuffer> {
431 let source = FileDataSource::open(path).map_err(|e| {
432 StreamingError::Other(format!("Failed to open GeoTIFF for band read: {}", e))
433 })?;
434
435 let reader = GeoTiffReader::open(source).map_err(|e| {
436 StreamingError::Other(format!("Failed to parse GeoTIFF for band read: {}", e))
437 })?;
438
439 let info = reader.metadata();
440 let data_type = info.data_type;
441 let bytes_per_sample = data_type.size_bytes();
442 let band_count = info.band_count.max(1) as usize;
443 let bytes_per_pixel = bytes_per_sample * band_count;
444 let img_width = info.width as usize;
445 let img_height = info.height as usize;
446
447 let out_size = width * height * bytes_per_pixel;
448 let mut out_data = vec![0u8; out_size];
449
450 let copy_w = width.min(img_width.saturating_sub(x_start));
455 let copy_h = height.min(img_height.saturating_sub(y_start));
456
457 if copy_w > 0 && copy_h > 0 {
458 for band in 0..band_count {
459 let plane = reader
460 .read_window(
461 0,
462 band,
463 x_start as u64,
464 y_start as u64,
465 copy_w as u64,
466 copy_h as u64,
467 )
468 .map_err(|e| {
469 StreamingError::Other(format!("Failed to read band {}: {}", band, e))
470 })?;
471
472 for row in 0..copy_h {
473 for col in 0..copy_w {
474 let src = (row * copy_w + col) * bytes_per_sample;
475 let dst = ((row * width + col) * band_count + band) * bytes_per_sample;
476 out_data[dst..dst + bytes_per_sample]
477 .copy_from_slice(&plane[src..src + bytes_per_sample]);
478 }
479 }
480 }
481 }
482
483 let effective_width = width as u64 * band_count as u64;
487 RasterBuffer::new(
488 out_data,
489 effective_width,
490 height as u64,
491 data_type,
492 metadata.nodata,
493 )
494 .map_err(|e| StreamingError::Other(format!("Failed to create RasterBuffer: {}", e)))
495 }
496
497 pub async fn read_chunks(&self, chunks: Vec<(usize, usize)>) -> Result<Vec<RasterChunk>> {
499 let mut handles = Vec::with_capacity(chunks.len());
500
501 for (row, col) in chunks {
502 let path = self.path.clone();
503 let config = self.config.clone();
504 let metadata = self.metadata.clone();
505 let bands = self.bands.clone();
506 let semaphore = Arc::clone(&self.prefetch_semaphore);
507 let format = self.format;
508
509 let handle = tokio::spawn(async move {
510 let _permit = semaphore
511 .acquire()
512 .await
513 .map_err(|e| StreamingError::Other(e.to_string()))?;
514
515 task::spawn_blocking(move || {
516 Self::read_chunk_blocking(path, row, col, config, metadata, bands, format)
517 })
518 .await
519 .map_err(|e| StreamingError::Other(e.to_string()))?
520 });
521
522 handles.push(handle);
523 }
524
525 let mut results = Vec::with_capacity(handles.len());
526 for handle in handles {
527 match handle.await {
528 Ok(Ok(chunk)) => results.push(chunk),
529 Ok(Err(e)) => {
530 error!("Failed to read chunk: {}", e);
531 return Err(e);
532 }
533 Err(e) => {
534 error!("Task panicked: {}", e);
535 return Err(StreamingError::Other(e.to_string()));
536 }
537 }
538 }
539
540 Ok(results)
541 }
542
543 pub fn metadata(&self) -> &RasterMetadata {
545 &self.metadata
546 }
547
548 pub fn config(&self) -> &RasterStreamConfig {
550 &self.config
551 }
552
553 pub fn format(&self) -> RasterFormat {
555 self.format
556 }
557}
558
559#[async_trait]
560impl RasterStreaming for RasterStreamReader {
561 async fn next_chunk(&mut self) -> Result<Option<RasterChunk>> {
562 let stream = self
563 .stream
564 .as_mut()
565 .ok_or_else(|| StreamingError::InvalidState("Stream not started".to_string()))?;
566 stream.next_chunk().await
567 }
568
569 async fn next_chunks(&mut self, count: usize) -> Result<Vec<RasterChunk>> {
570 let stream = self
571 .stream
572 .as_mut()
573 .ok_or_else(|| StreamingError::InvalidState("Stream not started".to_string()))?;
574 stream.next_chunks(count).await
575 }
576
577 async fn seek_to_chunk(&mut self, row: usize, col: usize) -> Result<()> {
578 let stream = self
579 .stream
580 .as_mut()
581 .ok_or_else(|| StreamingError::InvalidState("Stream not started".to_string()))?;
582 stream.seek_to_chunk(row, col).await
583 }
584
585 fn total_chunks(&self) -> (usize, usize) {
586 self.stream
587 .as_ref()
588 .map(|s| s.total_chunks())
589 .unwrap_or((0, 0))
590 }
591
592 fn current_position(&self) -> (usize, usize) {
593 self.stream
594 .as_ref()
595 .map(|s| s.current_position())
596 .unwrap_or((0, 0))
597 }
598
599 fn has_more_chunks(&self) -> bool {
600 self.stream
601 .as_ref()
602 .map(|s| s.has_more_chunks())
603 .unwrap_or(false)
604 }
605}
606
607pub struct RasterStreamReaderBuilder {
609 path: PathBuf,
610 config: RasterStreamConfig,
611 bands: Vec<usize>,
612}
613
614impl RasterStreamReaderBuilder {
615 pub fn new<P: AsRef<Path>>(path: P) -> Self {
617 Self {
618 path: path.as_ref().to_path_buf(),
619 config: RasterStreamConfig::default(),
620 bands: vec![0],
621 }
622 }
623
624 pub fn chunk_size(mut self, width: usize, height: usize) -> Self {
626 self.config = self.config.with_chunk_size(width, height);
627 self
628 }
629
630 pub fn overlap(mut self, overlap: usize) -> Self {
632 self.config = self.config.with_overlap(overlap);
633 self
634 }
635
636 pub fn compression(mut self, level: u8) -> Self {
638 self.config = self.config.with_compression(level);
639 self
640 }
641
642 pub fn bands(mut self, bands: Vec<usize>) -> Self {
644 self.bands = bands;
645 self
646 }
647
648 pub fn parallel(mut self, num_workers: usize) -> Self {
650 self.config = self.config.with_parallel(true, num_workers);
651 self
652 }
653
654 pub async fn build(self) -> Result<RasterStreamReader> {
656 let reader = RasterStreamReader::new(self.path, self.config).await?;
657 Ok(reader.with_bands(self.bands))
658 }
659}
660
661#[cfg(test)]
662mod tests;