oxigeo_core/io/traits.rs
1//! I/O traits for data sources
2//!
3//! This module provides abstract traits for reading and writing geospatial data
4//! from various sources (local files, HTTP, cloud storage, etc.).
5
6#[cfg(not(feature = "std"))]
7#[allow(unused_imports)]
8use crate::compat::*;
9use crate::error::Result;
10
11/// Byte range for partial reads
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct ByteRange {
14 /// Start offset (inclusive)
15 pub start: u64,
16 /// End offset (exclusive)
17 pub end: u64,
18}
19
20impl ByteRange {
21 /// Creates a new byte range
22 #[must_use]
23 pub const fn new(start: u64, end: u64) -> Self {
24 Self { start, end }
25 }
26
27 /// Creates a byte range from an offset and length
28 #[must_use]
29 pub const fn from_offset_length(offset: u64, length: u64) -> Self {
30 Self {
31 start: offset,
32 end: offset + length,
33 }
34 }
35
36 /// Returns the length of this range
37 #[must_use]
38 pub const fn len(&self) -> u64 {
39 self.end - self.start
40 }
41
42 /// Returns true if the range is empty
43 #[must_use]
44 pub const fn is_empty(&self) -> bool {
45 self.start >= self.end
46 }
47
48 /// Returns true if this range overlaps with another
49 #[must_use]
50 pub const fn overlaps(&self, other: &Self) -> bool {
51 self.start < other.end && self.end > other.start
52 }
53
54 /// Returns true if this range is adjacent to another
55 #[must_use]
56 pub const fn is_adjacent(&self, other: &Self) -> bool {
57 self.end == other.start || self.start == other.end
58 }
59
60 /// Merges two overlapping or adjacent ranges
61 #[must_use]
62 pub fn merge(&self, other: &Self) -> Option<Self> {
63 if self.overlaps(other) || self.is_adjacent(other) {
64 Some(Self {
65 start: self.start.min(other.start),
66 end: self.end.max(other.end),
67 })
68 } else {
69 None
70 }
71 }
72}
73
74/// Converts `range.len()` to a `usize`, the length of the buffer a read of that
75/// range needs.
76///
77/// Shared by every in-tree [`DataSource`] so they agree on what an
78/// unrepresentable length means: on a 64-bit target the conversion is infallible,
79/// on a 32-bit one (`wasm32`, `thumbv7em`, …) a range wider than `usize::MAX`
80/// is reported rather than silently truncated to a shorter read.
81///
82/// # Errors
83/// Returns [`OxiGeoError::OutOfBounds`] if the range is wider than `usize::MAX`.
84pub(crate) fn range_len_usize(range: ByteRange) -> Result<usize> {
85 usize::try_from(range.len()).map_err(|_| crate::error::OxiGeoError::OutOfBounds {
86 message: format!(
87 "byte range {}..{} is {} bytes long, which exceeds usize::MAX ({})",
88 range.start,
89 range.end,
90 range.len(),
91 usize::MAX
92 ),
93 })
94}
95
96/// Converts a range to the `(offset, length)` pair an in-memory source indexes
97/// with, rejecting values that do not fit a `usize`.
98///
99/// # Errors
100/// Returns [`OxiGeoError::OutOfBounds`] if either value exceeds `usize::MAX`.
101// Only the memory-mapped sources index by `(offset, len)`, and those are std-only.
102#[cfg(feature = "std")]
103pub(crate) fn range_bounds_usize(range: ByteRange) -> Result<(usize, usize)> {
104 let start =
105 usize::try_from(range.start).map_err(|_| crate::error::OxiGeoError::OutOfBounds {
106 message: format!(
107 "byte range start {} exceeds usize::MAX ({})",
108 range.start,
109 usize::MAX
110 ),
111 })?;
112 Ok((start, range_len_usize(range)?))
113}
114
115/// Builds the error every [`DataSource::read_range_into`] implementation returns
116/// when the caller's destination buffer cannot hold the whole range.
117pub(crate) fn dst_too_small(needed: usize, available: usize) -> crate::error::OxiGeoError {
118 crate::error::OxiGeoError::invalid_parameter(
119 "dst",
120 format!(
121 "destination buffer is {available} bytes but the requested range needs {needed}; \
122 size it with ByteRange::len()"
123 ),
124 )
125}
126
127/// Trait for synchronous data sources
128pub trait DataSource: Send + Sync {
129 /// Returns the total size of the data source in bytes
130 fn size(&self) -> Result<u64>;
131
132 /// Reads bytes from the specified range
133 fn read_range(&self, range: ByteRange) -> Result<Vec<u8>>;
134
135 /// Reads `range` directly into `dst`, avoiding the intermediate allocation of
136 /// [`DataSource::read_range`]. Returns the number of bytes written.
137 ///
138 /// This is the allocation-free entry point for block-oriented readers: a
139 /// caller walking thousands of tiles or strips can size one scratch buffer
140 /// up front and reuse it, instead of paying a heap allocation per block
141 /// (cool-japan/oxigeo#14).
142 ///
143 /// # Buffer sizing
144 ///
145 /// * `dst` **longer** than the range is fine — only `dst[..n]` is written and
146 /// the tail keeps whatever the caller left there.
147 /// * `dst` **shorter** than `range.len()` is rejected with
148 /// [`OxiGeoError::InvalidParameter`](crate::error::OxiGeoError::InvalidParameter)
149 /// *before* any I/O happens, leaving `dst` untouched. Truncating silently
150 /// would hand back a partial block that looks like a complete read.
151 /// * An **empty** range writes nothing and returns `Ok(0)`; `dst` may itself
152 /// be empty in that case.
153 ///
154 /// The returned count is what the source actually produced. It equals
155 /// `range.len()` for every source that reports a short read as an error
156 /// (all of the built-in ones); a source whose `read_range` clamps to its own
157 /// end returns the same clamped length here.
158 ///
159 /// On error the contents of `dst` are unspecified — an implementation is free
160 /// to write directly into it and fail part-way through.
161 ///
162 /// # Errors
163 /// Returns exactly what [`DataSource::read_range`] returns for `range`, plus
164 /// the `dst`-too-short error described above.
165 fn read_range_into(&self, range: ByteRange, dst: &mut [u8]) -> Result<usize> {
166 let needed = range_len_usize(range)?;
167 if dst.len() < needed {
168 return Err(dst_too_small(needed, dst.len()));
169 }
170 let data = self.read_range(range)?;
171 // A source whose `read_range` clamps may return fewer bytes than the
172 // range asked for; never more than `dst` can hold, because `needed` is an
173 // upper bound on a well-behaved source's output and `dst` holds `needed`.
174 let written = data.len().min(dst.len());
175 dst[..written].copy_from_slice(&data[..written]);
176 Ok(written)
177 }
178
179 /// Returns a borrowed view of `range` when this source can serve it without
180 /// copying — a memory-mapped file or a fully in-memory buffer, for instance.
181 ///
182 /// The default implementation returns `None`, meaning "copy me instead", so
183 /// callers must always keep a copying fallback ([`DataSource::read_range`] or
184 /// [`DataSource::read_range_into`]). An implementation that returns `Some`
185 /// must return exactly the bytes [`DataSource::read_range`] would have
186 /// returned; any range it cannot serve in full (out of bounds, inverted, or
187 /// wider than `usize`) must yield `None` so the fallback reports the error.
188 fn range_slice(&self, range: ByteRange) -> Option<&[u8]> {
189 let _ = range;
190 None
191 }
192
193 /// Reads bytes from multiple ranges (for optimization)
194 fn read_ranges(&self, ranges: &[ByteRange]) -> Result<Vec<Vec<u8>>> {
195 ranges.iter().map(|r| self.read_range(*r)).collect()
196 }
197
198 /// Returns true if this data source supports range requests
199 fn supports_range_requests(&self) -> bool {
200 true
201 }
202}
203
204/// Trait for async data sources
205#[cfg(feature = "async")]
206#[async_trait::async_trait]
207pub trait AsyncDataSource: Send + Sync {
208 /// Returns the total size of the data source in bytes
209 async fn size(&self) -> Result<u64>;
210
211 /// Reads bytes from the specified range
212 async fn read_range(&self, range: ByteRange) -> Result<Vec<u8>>;
213
214 /// Reads `range` directly into `dst`, avoiding the intermediate allocation of
215 /// [`AsyncDataSource::read_range`]. Returns the number of bytes written.
216 ///
217 /// The buffer-sizing and error contract is identical to the synchronous
218 /// [`DataSource::read_range_into`]: a longer `dst` is fine, a `dst` shorter
219 /// than `range.len()` is rejected before any I/O, and an empty range returns
220 /// `Ok(0)`.
221 ///
222 /// # Errors
223 /// Returns exactly what [`AsyncDataSource::read_range`] returns for `range`,
224 /// plus the `dst`-too-short error.
225 async fn read_range_into(&self, range: ByteRange, dst: &mut [u8]) -> Result<usize> {
226 let needed = range_len_usize(range)?;
227 if dst.len() < needed {
228 return Err(dst_too_small(needed, dst.len()));
229 }
230 let data = self.read_range(range).await?;
231 let written = data.len().min(dst.len());
232 dst[..written].copy_from_slice(&data[..written]);
233 Ok(written)
234 }
235
236 /// Reads bytes from multiple ranges concurrently
237 async fn read_ranges(&self, ranges: &[ByteRange]) -> Result<Vec<Vec<u8>>> {
238 let mut results = Vec::with_capacity(ranges.len());
239 for range in ranges {
240 results.push(self.read_range(*range).await?);
241 }
242 Ok(results)
243 }
244
245 /// Returns true if this data source supports range requests
246 fn supports_range_requests(&self) -> bool {
247 true
248 }
249}
250
251/// Trait for seekable byte-level writes
252pub trait DataSink: Send + Sync {
253 /// Writes bytes at the specified offset
254 fn write_at(&mut self, offset: u64, data: &[u8]) -> Result<()>;
255
256 /// Appends bytes to the end
257 fn append(&mut self, data: &[u8]) -> Result<u64>;
258
259 /// Flushes any buffered data
260 fn flush(&mut self) -> Result<()>;
261
262 /// Truncates the data to the specified size
263 fn truncate(&mut self, size: u64) -> Result<()>;
264
265 /// Returns the current size
266 fn size(&self) -> Result<u64>;
267}
268
269/// Read capability for raster datasets
270pub trait RasterRead {
271 /// The buffer type returned by read operations
272 type Buffer;
273
274 /// Reads a region of the raster
275 fn read_region(
276 &self,
277 band: u32,
278 x_offset: u64,
279 y_offset: u64,
280 width: u64,
281 height: u64,
282 ) -> Result<Self::Buffer>;
283
284 /// Reads a single tile (for tiled datasets)
285 fn read_tile(&self, band: u32, tile_col: u32, tile_row: u32) -> Result<Self::Buffer>;
286}
287
288/// Write capability for raster datasets
289pub trait RasterWrite {
290 /// The buffer type for write operations
291 type Buffer;
292
293 /// Writes a region to the raster
294 fn write_region(
295 &mut self,
296 band: u32,
297 x_offset: u64,
298 y_offset: u64,
299 data: &Self::Buffer,
300 ) -> Result<()>;
301
302 /// Writes a single tile
303 fn write_tile(
304 &mut self,
305 band: u32,
306 tile_col: u32,
307 tile_row: u32,
308 data: &Self::Buffer,
309 ) -> Result<()>;
310}
311
312/// Async read capability for raster datasets
313#[cfg(feature = "async")]
314#[async_trait::async_trait]
315pub trait AsyncRasterRead: Send + Sync {
316 /// The buffer type returned by read operations
317 type Buffer: Send;
318
319 /// Reads a region of the raster asynchronously
320 async fn read_region(
321 &self,
322 band: u32,
323 x_offset: u64,
324 y_offset: u64,
325 width: u64,
326 height: u64,
327 ) -> Result<Self::Buffer>;
328
329 /// Reads a single tile asynchronously
330 async fn read_tile(&self, band: u32, tile_col: u32, tile_row: u32) -> Result<Self::Buffer>;
331}
332
333/// Overview (pyramid) level support
334pub trait OverviewSupport {
335 /// Returns the number of overview levels
336 fn overview_count(&self) -> u32;
337
338 /// Returns the dimensions of an overview level
339 fn overview_dimensions(&self, level: u32) -> Option<(u64, u64)>;
340}
341
342/// COG-specific operations
343pub trait CogSupport: OverviewSupport {
344 /// Returns the tile size
345 fn tile_size(&self) -> (u32, u32);
346
347 /// Returns the number of tiles in X and Y
348 fn tile_count(&self) -> (u32, u32);
349
350 /// Returns the byte range for a specific tile
351 fn tile_byte_range(&self, level: u32, tile_col: u32, tile_row: u32) -> Option<ByteRange>;
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
359 fn test_byte_range() {
360 let range = ByteRange::new(100, 200);
361 assert_eq!(range.len(), 100);
362 assert!(!range.is_empty());
363
364 let empty = ByteRange::new(100, 100);
365 assert!(empty.is_empty());
366 }
367
368 #[test]
369 fn test_byte_range_overlap() {
370 let a = ByteRange::new(0, 100);
371 let b = ByteRange::new(50, 150);
372 let c = ByteRange::new(200, 300);
373
374 assert!(a.overlaps(&b));
375 assert!(b.overlaps(&a));
376 assert!(!a.overlaps(&c));
377 }
378
379 #[test]
380 fn test_byte_range_merge() {
381 let a = ByteRange::new(0, 100);
382 let b = ByteRange::new(100, 200);
383 let c = ByteRange::new(50, 150);
384
385 // Adjacent merge
386 let merged_adj = a.merge(&b);
387 assert!(merged_adj.is_some());
388 let merged = merged_adj.expect("merge should work");
389 assert_eq!(merged.start, 0);
390 assert_eq!(merged.end, 200);
391
392 // Overlapping merge
393 let merged_overlap = a.merge(&c);
394 assert!(merged_overlap.is_some());
395 let merged2 = merged_overlap.expect("merge should work");
396 assert_eq!(merged2.start, 0);
397 assert_eq!(merged2.end, 150);
398
399 // Non-overlapping - no merge
400 let d = ByteRange::new(300, 400);
401 assert!(a.merge(&d).is_none());
402 }
403
404 #[test]
405 fn test_from_offset_length() {
406 let range = ByteRange::from_offset_length(100, 50);
407 assert_eq!(range.start, 100);
408 assert_eq!(range.end, 150);
409 assert_eq!(range.len(), 50);
410 }
411}