Skip to main content

oxigdal_vrt/
reader.rs

1//! VRT reader with lazy evaluation
2
3use crate::band::PixelFunction;
4use crate::dataset::VrtDataset;
5use crate::error::{Result, VrtError};
6use crate::mosaic::MosaicCompositor;
7use crate::source::{PixelRect, VrtSource};
8use crate::xml::VrtXmlParser;
9use lru::LruCache;
10use oxigdal_core::buffer::RasterBuffer;
11use oxigdal_core::io::FileDataSource;
12use oxigdal_core::types::{GeoTransform, NoDataValue, RasterDataType, RasterMetadata};
13use oxigdal_geotiff::GeoTiffReader;
14use std::num::NonZeroUsize;
15use std::path::{Path, PathBuf};
16use std::sync::{Arc, Mutex};
17
18/// VRT reader with lazy source loading
19pub struct VrtReader {
20    /// VRT dataset definition
21    dataset: VrtDataset,
22    /// Cache of opened source datasets
23    source_cache: Arc<Mutex<LruCache<PathBuf, Arc<SourceDataset>>>>,
24    /// Mosaic compositor
25    compositor: MosaicCompositor,
26}
27
28impl VrtReader {
29    /// Opens a VRT file
30    ///
31    /// # Errors
32    /// Returns an error if the file cannot be opened or parsed
33    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
34        let dataset = VrtXmlParser::parse_file(&path)?;
35        Self::from_dataset(dataset)
36    }
37
38    /// Creates a reader from a VRT dataset
39    ///
40    /// # Errors
41    /// Returns an error if the dataset is invalid
42    pub fn from_dataset(dataset: VrtDataset) -> Result<Self> {
43        dataset.validate()?;
44
45        // Create source cache (default 32 open files)
46        let cache_size =
47            NonZeroUsize::new(32).ok_or_else(|| VrtError::cache_error("Invalid cache size"))?;
48        let source_cache = Arc::new(Mutex::new(LruCache::new(cache_size)));
49
50        let compositor = MosaicCompositor::new();
51
52        Ok(Self {
53            dataset,
54            source_cache,
55            compositor,
56        })
57    }
58
59    /// Gets the raster width
60    #[must_use]
61    pub fn width(&self) -> u64 {
62        self.dataset.raster_x_size
63    }
64
65    /// Gets the raster height
66    #[must_use]
67    pub fn height(&self) -> u64 {
68        self.dataset.raster_y_size
69    }
70
71    /// Gets the number of bands
72    #[must_use]
73    pub fn band_count(&self) -> usize {
74        self.dataset.band_count()
75    }
76
77    /// Gets the GeoTransform
78    #[must_use]
79    pub fn geo_transform(&self) -> Option<&GeoTransform> {
80        self.dataset.geo_transform.as_ref()
81    }
82
83    /// Gets the spatial reference system
84    #[must_use]
85    pub fn srs(&self) -> Option<&str> {
86        self.dataset.srs.as_deref()
87    }
88
89    /// Gets the block size
90    #[must_use]
91    pub fn block_size(&self) -> (u32, u32) {
92        self.dataset.effective_block_size()
93    }
94
95    /// Gets the metadata
96    #[must_use]
97    pub fn metadata(&self) -> RasterMetadata {
98        let (tile_width, tile_height) = self.block_size();
99        RasterMetadata {
100            width: self.dataset.raster_x_size,
101            height: self.dataset.raster_y_size,
102            band_count: self.dataset.band_count() as u32,
103            data_type: self
104                .dataset
105                .primary_data_type()
106                .unwrap_or(RasterDataType::UInt8),
107            geo_transform: self.dataset.geo_transform,
108            crs_wkt: self.dataset.srs.clone(),
109            nodata: NoDataValue::None,
110            color_interpretation: Vec::new(),
111            layout: oxigdal_core::types::PixelLayout::Tiled {
112                tile_width,
113                tile_height,
114            },
115            driver_metadata: Vec::new(),
116            statistics: None,
117        }
118    }
119
120    /// Reads a band's data for a specific window
121    ///
122    /// # Errors
123    /// Returns an error if reading fails
124    pub fn read_window(&self, band: usize, window: PixelRect) -> Result<RasterBuffer> {
125        let band_idx = band - 1;
126        let vrt_band = self
127            .dataset
128            .get_band(band_idx)
129            .ok_or_else(|| VrtError::band_out_of_range(band, self.dataset.band_count()))?;
130
131        // Get sources that intersect with the window
132        let contributing_sources: Vec<&VrtSource> = vrt_band
133            .sources
134            .iter()
135            .filter(|s| s.dst_rect().map(|r| r.intersects(&window)).unwrap_or(false))
136            .collect();
137
138        if contributing_sources.is_empty() {
139            return Err(VrtError::invalid_window(
140                "No sources contribute to this window",
141            ));
142        }
143
144        // Create output buffer
145        let data_size = (window.x_size * window.y_size) as usize * vrt_band.data_type.size_bytes();
146        let mut data = vec![0u8; data_size];
147
148        // If pixel function is present, read all sources separately and apply function
149        if let Some(ref pixel_func) = vrt_band.pixel_function {
150            self.apply_pixel_function(
151                &contributing_sources,
152                &window,
153                vrt_band.data_type,
154                vrt_band.nodata,
155                pixel_func,
156                &mut data,
157            )?;
158        } else {
159            // Composite data from all contributing sources (no pixel function)
160            for source in &contributing_sources {
161                self.read_source_contribution(source, &window, vrt_band.data_type, &mut data)?;
162            }
163        }
164
165        RasterBuffer::new(
166            data,
167            window.x_size,
168            window.y_size,
169            vrt_band.data_type,
170            vrt_band.nodata,
171        )
172        .map_err(|e| e.into())
173    }
174
175    /// Reads a full band
176    ///
177    /// # Errors
178    /// Returns an error if reading fails
179    pub fn read_band(&self, band: usize) -> Result<RasterBuffer> {
180        let window = PixelRect::new(0, 0, self.width(), self.height());
181        self.read_window(band, window)
182    }
183
184    /// Reads a source's contribution to a window
185    fn read_source_contribution(
186        &self,
187        source: &VrtSource,
188        dst_window: &PixelRect,
189        data_type: RasterDataType,
190        output: &mut [u8],
191    ) -> Result<()> {
192        let source_dst_rect = source
193            .dst_rect()
194            .ok_or_else(|| VrtError::invalid_source("Source has no destination rectangle"))?;
195
196        // Calculate intersection between source and requested window
197        let intersection = source_dst_rect
198            .intersect(dst_window)
199            .ok_or_else(|| VrtError::invalid_window("Source does not intersect window"))?;
200
201        // Open source dataset
202        let dataset = self.open_source(source)?;
203
204        // Calculate source rectangle
205        let src_window = source
206            .window
207            .as_ref()
208            .ok_or_else(|| VrtError::invalid_source("Source has no window configuration"))?;
209
210        // Calculate offset within source
211        let src_x_off = src_window.src_rect.x_off + (intersection.x_off - source_dst_rect.x_off);
212        let src_y_off = src_window.src_rect.y_off + (intersection.y_off - source_dst_rect.y_off);
213
214        let src_rect = PixelRect::new(
215            src_x_off,
216            src_y_off,
217            intersection.x_size,
218            intersection.y_size,
219        );
220
221        // Read from source
222        let source_data = dataset.read_window(source.source_band, src_rect)?;
223
224        // Copy to output buffer at correct position
225        let dst_x_off = intersection.x_off - dst_window.x_off;
226        let dst_y_off = intersection.y_off - dst_window.y_off;
227
228        let params = crate::mosaic::CompositeParams::new(
229            dst_x_off,
230            dst_y_off,
231            intersection.x_size,
232            intersection.y_size,
233            dst_window.x_size,
234            data_type,
235        );
236        self.compositor
237            .composite(source_data.as_bytes(), output, &params)?;
238
239        Ok(())
240    }
241
242    /// Applies pixel function to source data
243    fn apply_pixel_function(
244        &self,
245        sources: &[&VrtSource],
246        window: &PixelRect,
247        data_type: RasterDataType,
248        nodata: NoDataValue,
249        pixel_func: &PixelFunction,
250        output: &mut [u8],
251    ) -> Result<()> {
252        let pixel_count = (window.x_size * window.y_size) as usize;
253        let _bytes_per_pixel = data_type.size_bytes();
254
255        // Read all source bands
256        let mut source_buffers = Vec::new();
257        for source in sources {
258            let source_dst_rect = source
259                .dst_rect()
260                .ok_or_else(|| VrtError::invalid_source("Source has no destination rectangle"))?;
261
262            let intersection = source_dst_rect
263                .intersect(window)
264                .ok_or_else(|| VrtError::invalid_window("Source does not intersect window"))?;
265
266            let dataset = self.open_source(source)?;
267
268            let src_window = source
269                .window
270                .as_ref()
271                .ok_or_else(|| VrtError::invalid_source("Source has no window configuration"))?;
272
273            let src_x_off =
274                src_window.src_rect.x_off + (intersection.x_off - source_dst_rect.x_off);
275            let src_y_off =
276                src_window.src_rect.y_off + (intersection.y_off - source_dst_rect.y_off);
277
278            let src_rect = PixelRect::new(
279                src_x_off,
280                src_y_off,
281                intersection.x_size,
282                intersection.y_size,
283            );
284
285            let source_data = dataset.read_window(source.source_band, src_rect)?;
286            source_buffers.push((source_data, intersection));
287        }
288
289        // Apply pixel function to each pixel
290        for pixel_idx in 0..pixel_count {
291            let y = pixel_idx as u64 / window.x_size;
292            let x = pixel_idx as u64 % window.x_size;
293            let global_x = window.x_off + x;
294            let global_y = window.y_off + y;
295
296            // Collect values from all sources for this pixel
297            let mut values = Vec::new();
298            for (source_buffer, intersection) in &source_buffers {
299                if global_x >= intersection.x_off
300                    && global_x < intersection.x_off + intersection.x_size
301                    && global_y >= intersection.y_off
302                    && global_y < intersection.y_off + intersection.y_size
303                {
304                    let local_x = global_x - intersection.x_off;
305                    let local_y = global_y - intersection.y_off;
306                    let local_idx = (local_y * intersection.x_size + local_x) as usize;
307
308                    // Read value from source buffer
309                    let value = self.read_pixel_value(
310                        source_buffer.as_bytes(),
311                        local_idx,
312                        data_type,
313                        nodata,
314                    )?;
315                    values.push(value);
316                } else {
317                    values.push(None);
318                }
319            }
320
321            // Apply pixel function
322            let result = pixel_func.apply(&values)?;
323
324            // Write result to output
325            self.write_pixel_value(output, pixel_idx, result, data_type)?;
326        }
327
328        Ok(())
329    }
330
331    /// Reads a single pixel value from a buffer
332    fn read_pixel_value(
333        &self,
334        buffer: &[u8],
335        pixel_idx: usize,
336        data_type: RasterDataType,
337        nodata: NoDataValue,
338    ) -> Result<Option<f64>> {
339        let bytes_per_pixel = data_type.size_bytes();
340        let offset = pixel_idx * bytes_per_pixel;
341
342        if offset + bytes_per_pixel > buffer.len() {
343            return Ok(None);
344        }
345
346        let value = match data_type {
347            RasterDataType::UInt8 => buffer[offset] as f64,
348            RasterDataType::Int8 => buffer[offset] as i8 as f64,
349            RasterDataType::UInt16 => {
350                let val = u16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
351                val as f64
352            }
353            RasterDataType::Int16 => {
354                let val = i16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
355                val as f64
356            }
357            RasterDataType::UInt32 => {
358                let val = u32::from_le_bytes([
359                    buffer[offset],
360                    buffer[offset + 1],
361                    buffer[offset + 2],
362                    buffer[offset + 3],
363                ]);
364                val as f64
365            }
366            RasterDataType::Int32 => {
367                let val = i32::from_le_bytes([
368                    buffer[offset],
369                    buffer[offset + 1],
370                    buffer[offset + 2],
371                    buffer[offset + 3],
372                ]);
373                val as f64
374            }
375            RasterDataType::Float32 => {
376                let val = f32::from_le_bytes([
377                    buffer[offset],
378                    buffer[offset + 1],
379                    buffer[offset + 2],
380                    buffer[offset + 3],
381                ]);
382                val as f64
383            }
384            RasterDataType::Float64 => f64::from_le_bytes([
385                buffer[offset],
386                buffer[offset + 1],
387                buffer[offset + 2],
388                buffer[offset + 3],
389                buffer[offset + 4],
390                buffer[offset + 5],
391                buffer[offset + 6],
392                buffer[offset + 7],
393            ]),
394            _ => return Err(VrtError::invalid_source("Unsupported data type")),
395        };
396
397        // Check for NoData
398        let is_nodata = match nodata {
399            NoDataValue::None => false,
400            NoDataValue::Integer(nd) => (value - nd as f64).abs() < f64::EPSILON,
401            NoDataValue::Float(nd) => (value - nd).abs() < f64::EPSILON,
402        };
403
404        if is_nodata { Ok(None) } else { Ok(Some(value)) }
405    }
406
407    /// Writes a single pixel value to a buffer
408    fn write_pixel_value(
409        &self,
410        buffer: &mut [u8],
411        pixel_idx: usize,
412        value: Option<f64>,
413        data_type: RasterDataType,
414    ) -> Result<()> {
415        let bytes_per_pixel = data_type.size_bytes();
416        let offset = pixel_idx * bytes_per_pixel;
417
418        if offset + bytes_per_pixel > buffer.len() {
419            return Err(VrtError::invalid_window("Pixel offset out of bounds"));
420        }
421
422        let write_val = value.unwrap_or(0.0);
423
424        match data_type {
425            RasterDataType::UInt8 => {
426                buffer[offset] = write_val.clamp(0.0, 255.0) as u8;
427            }
428            RasterDataType::Int8 => {
429                buffer[offset] = write_val.clamp(-128.0, 127.0) as i8 as u8;
430            }
431            RasterDataType::UInt16 => {
432                let val = write_val.clamp(0.0, 65535.0) as u16;
433                buffer[offset..offset + 2].copy_from_slice(&val.to_le_bytes());
434            }
435            RasterDataType::Int16 => {
436                let val = write_val.clamp(-32768.0, 32767.0) as i16;
437                buffer[offset..offset + 2].copy_from_slice(&val.to_le_bytes());
438            }
439            RasterDataType::UInt32 => {
440                let val = write_val.clamp(0.0, u32::MAX as f64) as u32;
441                buffer[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
442            }
443            RasterDataType::Int32 => {
444                let val = write_val.clamp(i32::MIN as f64, i32::MAX as f64) as i32;
445                buffer[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
446            }
447            RasterDataType::Float32 => {
448                let val = write_val as f32;
449                buffer[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
450            }
451            RasterDataType::Float64 => {
452                buffer[offset..offset + 8].copy_from_slice(&write_val.to_le_bytes());
453            }
454            _ => return Err(VrtError::invalid_source("Unsupported data type")),
455        }
456
457        Ok(())
458    }
459
460    /// Opens a source dataset (with caching)
461    fn open_source(&self, source: &VrtSource) -> Result<Arc<SourceDataset>> {
462        let path = if let Some(ref vrt_path) = self.dataset.vrt_path {
463            source.filename.resolve(vrt_path)?
464        } else {
465            source.filename.path.clone()
466        };
467
468        // Check cache first
469        {
470            let mut cache = self
471                .source_cache
472                .lock()
473                .map_err(|_| VrtError::cache_error("Failed to lock source cache"))?;
474
475            if let Some(dataset) = cache.get(&path) {
476                return Ok(Arc::clone(dataset));
477            }
478        }
479
480        // Open new dataset
481        let dataset = SourceDataset::open(&path)?;
482        let arc_dataset = Arc::new(dataset);
483
484        // Add to cache
485        {
486            let mut cache = self
487                .source_cache
488                .lock()
489                .map_err(|_| VrtError::cache_error("Failed to lock source cache"))?;
490            cache.put(path, Arc::clone(&arc_dataset));
491        }
492
493        Ok(arc_dataset)
494    }
495
496    /// Clears the source cache
497    pub fn clear_cache(&mut self) {
498        if let Ok(mut cache) = self.source_cache.lock() {
499            cache.clear();
500        }
501    }
502
503    /// Gets the current cache size
504    pub fn cache_size(&self) -> usize {
505        self.source_cache
506            .lock()
507            .map(|cache| cache.len())
508            .unwrap_or(0)
509    }
510}
511
512/// Wrapper for source datasets
513pub struct SourceDataset {
514    /// GeoTIFF reader (for now, only GeoTIFF sources are supported)
515    geotiff: Option<GeoTiffReader<FileDataSource>>,
516}
517
518impl SourceDataset {
519    /// Opens a source dataset
520    ///
521    /// # Errors
522    /// Returns an error if the file cannot be opened
523    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
524        // Try to open as GeoTIFF
525        match FileDataSource::open(path.as_ref()) {
526            Ok(source) => match GeoTiffReader::open(source) {
527                Ok(reader) => Ok(Self {
528                    geotiff: Some(reader),
529                }),
530                Err(e) => Err(VrtError::source_error(
531                    path.as_ref().display().to_string(),
532                    format!("Failed to open as GeoTIFF: {}", e),
533                )),
534            },
535            Err(e) => Err(VrtError::source_error(
536                path.as_ref().display().to_string(),
537                format!("Failed to open file: {}", e),
538            )),
539        }
540    }
541
542    /// Reads a window from the source dataset
543    ///
544    /// # Errors
545    /// Returns an error if reading fails
546    pub fn read_window(&self, band: usize, window: PixelRect) -> Result<RasterBuffer> {
547        if let Some(ref geotiff) = self.geotiff {
548            // For now, we read the full band and extract the window
549            // A more efficient implementation would read only the necessary tiles
550            let full_band = geotiff.read_band(0, band - 1).map_err(|e| {
551                VrtError::source_error("source", format!("Failed to read band: {}", e))
552            })?;
553
554            // Extract window
555            let width = geotiff.width() as usize;
556            let height = geotiff.height() as usize;
557            let data_type = geotiff.data_type().unwrap_or(RasterDataType::UInt8);
558            let bytes_per_pixel = data_type.size_bytes();
559
560            let mut window_data = Vec::new();
561
562            for y in 0..window.y_size {
563                let src_y = (window.y_off + y) as usize;
564                if src_y >= height {
565                    break;
566                }
567
568                let src_offset = (src_y * width + window.x_off as usize) * bytes_per_pixel;
569                let copy_width = window.x_size.min((width as u64) - window.x_off) as usize;
570                let copy_bytes = copy_width * bytes_per_pixel;
571
572                if src_offset + copy_bytes <= full_band.len() {
573                    window_data.extend_from_slice(&full_band[src_offset..src_offset + copy_bytes]);
574                }
575            }
576
577            RasterBuffer::new(
578                window_data,
579                window.x_size,
580                window.y_size,
581                data_type,
582                geotiff.nodata(),
583            )
584            .map_err(|e| e.into())
585        } else {
586            Err(VrtError::source_error(
587                "unknown",
588                "Unsupported source format",
589            ))
590        }
591    }
592}
593
594#[cfg(test)]
595mod tests {
596    use super::*;
597    use crate::band::VrtBand;
598    use crate::source::VrtSource;
599
600    #[test]
601    fn test_vrt_reader_creation() {
602        let mut dataset = VrtDataset::new(512, 512);
603        let source = VrtSource::simple("/test.tif", 1);
604        let band = VrtBand::simple(1, RasterDataType::UInt8, source);
605        dataset.add_band(band);
606
607        let reader = VrtReader::from_dataset(dataset);
608        assert!(reader.is_ok());
609        let r = reader.expect("Should create reader");
610        assert_eq!(r.width(), 512);
611        assert_eq!(r.height(), 512);
612        assert_eq!(r.band_count(), 1);
613    }
614
615    #[test]
616    fn test_cache() {
617        let mut dataset = VrtDataset::new(512, 512);
618        let source = VrtSource::simple("/test.tif", 1);
619        let band = VrtBand::simple(1, RasterDataType::UInt8, source);
620        dataset.add_band(band);
621
622        let mut reader = VrtReader::from_dataset(dataset).expect("Should create reader");
623        assert_eq!(reader.cache_size(), 0);
624
625        reader.clear_cache();
626        assert_eq!(reader.cache_size(), 0);
627    }
628}