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