1use crate::core::StreamElement;
4use crate::error::{Result, StreamingError};
5use async_trait::async_trait;
6use chrono::{DateTime, Utc};
7use oxigdal_core::{
8 buffer::RasterBuffer,
9 types::{BoundingBox, GeoTransform, RasterMetadata},
10};
11use serde::{Deserialize, Serialize};
12use std::sync::Arc;
13use tokio::sync::{RwLock, mpsc};
14use tracing::{info, warn};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct RasterStreamConfig {
19 pub chunk_size: (usize, usize),
21
22 pub overlap: usize,
24
25 pub buffer_size: usize,
27
28 pub compression: bool,
30
31 pub compression_level: u8,
33
34 pub max_memory_bytes: usize,
36
37 pub prefetch_count: usize,
39
40 pub parallel: bool,
42
43 pub num_workers: usize,
45}
46
47impl Default for RasterStreamConfig {
48 fn default() -> Self {
49 Self {
50 chunk_size: (512, 512),
51 overlap: 0,
52 buffer_size: 100,
53 compression: false,
54 compression_level: 6,
55 max_memory_bytes: 1024 * 1024 * 1024, prefetch_count: 2,
57 parallel: true,
58 num_workers: std::thread::available_parallelism()
59 .map(|n| n.get())
60 .unwrap_or(4),
61 }
62 }
63}
64
65impl RasterStreamConfig {
66 pub fn new() -> Self {
68 Self::default()
69 }
70
71 pub fn with_chunk_size(mut self, width: usize, height: usize) -> Self {
73 self.chunk_size = (width, height);
74 self
75 }
76
77 pub fn with_overlap(mut self, overlap: usize) -> Self {
79 self.overlap = overlap;
80 self
81 }
82
83 pub fn with_compression(mut self, level: u8) -> Self {
85 self.compression = true;
86 self.compression_level = level;
87 self
88 }
89
90 pub fn with_max_memory(mut self, bytes: usize) -> Self {
92 self.max_memory_bytes = bytes;
93 self
94 }
95
96 pub fn with_prefetch(mut self, count: usize) -> Self {
98 self.prefetch_count = count;
99 self
100 }
101
102 pub fn with_parallel(mut self, parallel: bool, num_workers: usize) -> Self {
104 self.parallel = parallel;
105 self.num_workers = num_workers;
106 self
107 }
108}
109
110#[derive(Debug, Clone)]
112pub struct RasterChunk {
113 pub buffer: RasterBuffer,
115
116 pub bbox: BoundingBox,
118
119 pub geotransform: GeoTransform,
121
122 pub indices: (usize, usize),
124
125 pub timestamp: DateTime<Utc>,
127
128 pub metadata: ChunkMetadata,
130}
131
132impl RasterChunk {
133 pub fn new(
135 buffer: RasterBuffer,
136 bbox: BoundingBox,
137 geotransform: GeoTransform,
138 indices: (usize, usize),
139 ) -> Self {
140 Self {
141 buffer,
142 bbox,
143 geotransform,
144 indices,
145 timestamp: Utc::now(),
146 metadata: ChunkMetadata::default(),
147 }
148 }
149
150 pub fn size_bytes(&self) -> usize {
152 self.buffer.as_bytes().len()
153 }
154
155 pub fn overlaps_with(&self, other: &RasterChunk) -> bool {
157 self.bbox.intersects(&other.bbox)
158 }
159
160 pub fn overlap_region(&self, other: &RasterChunk) -> Option<BoundingBox> {
162 self.bbox.intersection(&other.bbox)
163 }
164
165 pub fn to_stream_element(&self) -> Result<StreamElement> {
168 let meta = serde_json::json!({
171 "bbox": {
172 "min_x": self.bbox.min_x(),
173 "min_y": self.bbox.min_y(),
174 "max_x": self.bbox.max_x(),
175 "max_y": self.bbox.max_y(),
176 },
177 "gt": {
178 "origin_x": self.geotransform.origin_x,
179 "origin_y": self.geotransform.origin_y,
180 "pixel_width": self.geotransform.pixel_width,
181 "pixel_height": self.geotransform.pixel_height,
182 "row_rotation": self.geotransform.row_rotation,
183 "col_rotation": self.geotransform.col_rotation,
184 },
185 "row": self.indices.0,
186 "col": self.indices.1,
187 "width": self.buffer.width(),
188 "height": self.buffer.height(),
189 "data_type": format!("{:?}", self.buffer.data_type()),
190 });
191 let json_bytes = serde_json::to_vec(&meta)
192 .map_err(|e| StreamingError::SerializationError(e.to_string()))?;
193 let json_len = json_bytes.len() as u64;
194 let pixel_bytes = self.buffer.as_bytes();
195
196 let mut data = Vec::with_capacity(8 + json_bytes.len() + pixel_bytes.len());
197 data.extend_from_slice(&json_len.to_le_bytes());
198 data.extend_from_slice(&json_bytes);
199 data.extend_from_slice(pixel_bytes);
200
201 Ok(StreamElement::new(data, self.timestamp))
202 }
203
204 pub fn from_stream_element(element: &StreamElement) -> Result<Self> {
207 use oxigdal_core::types::{BoundingBox, NoDataValue, RasterDataType};
208
209 let data = &element.data;
210 if data.len() < 8 {
211 return Err(StreamingError::DeserializationError(
212 "stream element too short to contain header".to_string(),
213 ));
214 }
215 let json_len = u64::from_le_bytes(
216 data[..8]
217 .try_into()
218 .map_err(|_| StreamingError::DeserializationError("bad header".to_string()))?,
219 ) as usize;
220 if data.len() < 8 + json_len {
221 return Err(StreamingError::DeserializationError(
222 "stream element truncated: JSON metadata missing".to_string(),
223 ));
224 }
225 let meta: serde_json::Value = serde_json::from_slice(&data[8..8 + json_len])
226 .map_err(|e| StreamingError::DeserializationError(e.to_string()))?;
227
228 let pixel_bytes = data[8 + json_len..].to_vec();
229
230 let width = meta["width"]
231 .as_u64()
232 .ok_or_else(|| StreamingError::DeserializationError("missing width".to_string()))?;
233 let height = meta["height"]
234 .as_u64()
235 .ok_or_else(|| StreamingError::DeserializationError("missing height".to_string()))?;
236 let row = meta["row"]
237 .as_u64()
238 .ok_or_else(|| StreamingError::DeserializationError("missing row index".to_string()))?
239 as usize;
240 let col = meta["col"]
241 .as_u64()
242 .ok_or_else(|| StreamingError::DeserializationError("missing col index".to_string()))?
243 as usize;
244
245 let gt = GeoTransform {
246 origin_x: meta["gt"]["origin_x"].as_f64().unwrap_or(0.0),
247 origin_y: meta["gt"]["origin_y"].as_f64().unwrap_or(0.0),
248 pixel_width: meta["gt"]["pixel_width"].as_f64().unwrap_or(1.0),
249 pixel_height: meta["gt"]["pixel_height"].as_f64().unwrap_or(-1.0),
250 row_rotation: meta["gt"]["row_rotation"].as_f64().unwrap_or(0.0),
251 col_rotation: meta["gt"]["col_rotation"].as_f64().unwrap_or(0.0),
252 };
253
254 let min_x = meta["bbox"]["min_x"].as_f64().unwrap_or(0.0);
255 let min_y = meta["bbox"]["min_y"].as_f64().unwrap_or(0.0);
256 let max_x = meta["bbox"]["max_x"].as_f64().unwrap_or(0.0);
257 let max_y = meta["bbox"]["max_y"].as_f64().unwrap_or(0.0);
258 let bbox = BoundingBox::new(min_x, min_y, max_x, max_y).map_err(StreamingError::Core)?;
259
260 let buffer = RasterBuffer::new(
261 pixel_bytes,
262 width,
263 height,
264 RasterDataType::UInt8, NoDataValue::None,
266 )
267 .map_err(|e| StreamingError::DeserializationError(e.to_string()))?;
268
269 Ok(Self {
270 buffer,
271 bbox,
272 geotransform: gt,
273 indices: (row, col),
274 timestamp: element.event_time,
275 metadata: ChunkMetadata::default(),
276 })
277 }
278}
279
280#[derive(Debug, Clone, Default, Serialize, Deserialize)]
282pub struct ChunkMetadata {
283 pub bands: Vec<usize>,
285
286 pub compression: Option<String>,
288
289 pub original_size: Option<usize>,
291
292 pub compressed_size: Option<usize>,
294
295 pub checksum: Option<String>,
297
298 pub attributes: std::collections::HashMap<String, String>,
300}
301
302#[async_trait]
304pub trait RasterStreaming: Send + Sync {
305 async fn next_chunk(&mut self) -> Result<Option<RasterChunk>>;
307
308 async fn next_chunks(&mut self, count: usize) -> Result<Vec<RasterChunk>>;
310
311 async fn seek_to_chunk(&mut self, row: usize, col: usize) -> Result<()>;
313
314 fn total_chunks(&self) -> (usize, usize);
316
317 fn current_position(&self) -> (usize, usize);
319
320 fn has_more_chunks(&self) -> bool;
322}
323
324pub struct RasterStream {
326 config: RasterStreamConfig,
328
329 metadata: RasterMetadata,
331
332 total_chunks: (usize, usize),
334
335 current_position: Arc<RwLock<(usize, usize)>>,
337
338 receiver: mpsc::Receiver<RasterChunk>,
340
341 prefetch_sender: Option<mpsc::Sender<(usize, usize)>>,
343
344 memory_usage: Arc<RwLock<usize>>,
346}
347
348impl RasterStream {
349 pub fn new(config: RasterStreamConfig, metadata: RasterMetadata) -> Result<Self> {
351 let total_chunks = Self::calculate_chunks(
353 metadata.width as usize,
354 metadata.height as usize,
355 config.chunk_size.0,
356 config.chunk_size.1,
357 config.overlap,
358 );
359
360 let (_sender, receiver) = mpsc::channel(config.buffer_size);
361
362 info!(
363 "Created raster stream with {} x {} chunks",
364 total_chunks.0, total_chunks.1
365 );
366
367 Ok(Self {
368 config,
369 metadata,
370 total_chunks,
371 current_position: Arc::new(RwLock::new((0, 0))),
372 receiver,
373 prefetch_sender: None,
374 memory_usage: Arc::new(RwLock::new(0)),
375 })
376 }
377
378 pub fn calculate_chunks(
380 width: usize,
381 height: usize,
382 chunk_width: usize,
383 chunk_height: usize,
384 overlap: usize,
385 ) -> (usize, usize) {
386 let effective_chunk_width = chunk_width - overlap;
387 let effective_chunk_height = chunk_height - overlap;
388
389 let num_cols = width.div_ceil(effective_chunk_width);
390 let num_rows = height.div_ceil(effective_chunk_height);
391
392 (num_rows, num_cols)
393 }
394
395 pub fn chunk_bbox(&self, row: usize, col: usize) -> Result<BoundingBox> {
397 if row >= self.total_chunks.0 || col >= self.total_chunks.1 {
398 return Err(StreamingError::InvalidOperation(format!(
399 "Chunk ({}, {}) out of bounds",
400 row, col
401 )));
402 }
403
404 let chunk_width = self.config.chunk_size.0;
405 let chunk_height = self.config.chunk_size.1;
406 let overlap = self.config.overlap;
407
408 let effective_width = chunk_width - overlap;
409 let effective_height = chunk_height - overlap;
410
411 let x_start = col * effective_width;
412 let y_start = row * effective_height;
413 let x_end = (x_start + chunk_width).min(self.metadata.width as usize);
414 let y_end = (y_start + chunk_height).min(self.metadata.height as usize);
415
416 let gt =
418 self.metadata.geo_transform.as_ref().ok_or_else(|| {
419 StreamingError::InvalidState("No geotransform available".to_string())
420 })?;
421
422 let min_x = gt.origin_x + (x_start as f64) * gt.pixel_width;
423 let max_y = gt.origin_y + (y_start as f64) * gt.pixel_height;
424 let max_x = gt.origin_x + (x_end as f64) * gt.pixel_width;
425 let min_y = gt.origin_y + (y_end as f64) * gt.pixel_height;
426
427 BoundingBox::new(min_x, min_y, max_x, max_y).map_err(StreamingError::Core)
428 }
429
430 pub fn chunk_geotransform(&self, row: usize, col: usize) -> Result<GeoTransform> {
432 let gt =
433 self.metadata.geo_transform.as_ref().ok_or_else(|| {
434 StreamingError::InvalidState("No geotransform available".to_string())
435 })?;
436
437 let chunk_width = self.config.chunk_size.0;
438 let chunk_height = self.config.chunk_size.1;
439 let overlap = self.config.overlap;
440
441 let effective_width = chunk_width - overlap;
442 let effective_height = chunk_height - overlap;
443
444 let x_start = col * effective_width;
445 let y_start = row * effective_height;
446
447 let origin_x = gt.origin_x + (x_start as f64) * gt.pixel_width;
448 let origin_y = gt.origin_y + (y_start as f64) * gt.pixel_height;
449
450 Ok(GeoTransform {
451 origin_x,
452 origin_y,
453 pixel_width: gt.pixel_width,
454 pixel_height: gt.pixel_height,
455 row_rotation: gt.row_rotation,
456 col_rotation: gt.col_rotation,
457 })
458 }
459
460 pub async fn memory_stats(&self) -> MemoryStats {
462 let current = *self.memory_usage.read().await;
463 MemoryStats {
464 current_bytes: current,
465 max_bytes: self.config.max_memory_bytes,
466 utilization: (current as f64) / (self.config.max_memory_bytes as f64),
467 }
468 }
469
470 #[allow(dead_code)]
472 async fn update_memory(&self, delta: isize) -> Result<()> {
473 let mut usage = self.memory_usage.write().await;
474 if delta > 0 {
475 let new_usage = *usage + delta as usize;
476 if new_usage > self.config.max_memory_bytes {
477 return Err(StreamingError::Other("Memory limit exceeded".to_string()));
478 }
479 *usage = new_usage;
480 } else {
481 *usage = usage.saturating_sub((-delta) as usize);
482 }
483 Ok(())
484 }
485}
486
487#[async_trait]
488impl RasterStreaming for RasterStream {
489 async fn next_chunk(&mut self) -> Result<Option<RasterChunk>> {
490 match self.receiver.recv().await {
491 Some(chunk) => {
492 let mut pos = self.current_position.write().await;
494 pos.1 += 1;
495 if pos.1 >= self.total_chunks.1 {
496 pos.1 = 0;
497 pos.0 += 1;
498 }
499
500 Ok(Some(chunk))
501 }
502 None => Ok(None),
503 }
504 }
505
506 async fn next_chunks(&mut self, count: usize) -> Result<Vec<RasterChunk>> {
507 let mut chunks = Vec::with_capacity(count);
508 for _ in 0..count {
509 match self.next_chunk().await? {
510 Some(chunk) => chunks.push(chunk),
511 None => break,
512 }
513 }
514 Ok(chunks)
515 }
516
517 async fn seek_to_chunk(&mut self, row: usize, col: usize) -> Result<()> {
518 if row >= self.total_chunks.0 || col >= self.total_chunks.1 {
519 return Err(StreamingError::InvalidOperation(format!(
520 "Chunk ({}, {}) out of bounds",
521 row, col
522 )));
523 }
524
525 let mut pos = self.current_position.write().await;
526 *pos = (row, col);
527
528 if let Some(sender) = &self.prefetch_sender
530 && sender.try_send((row, col)).is_err()
531 {
532 warn!("Failed to send prefetch request");
533 }
534
535 Ok(())
536 }
537
538 fn total_chunks(&self) -> (usize, usize) {
539 self.total_chunks
540 }
541
542 fn current_position(&self) -> (usize, usize) {
543 match self.current_position.try_read() {
546 Ok(pos) => *pos,
547 Err(_) => (0, 0),
548 }
549 }
550
551 fn has_more_chunks(&self) -> bool {
552 let pos = self.current_position();
553 pos.0 < self.total_chunks.0
554 }
555}
556
557#[derive(Debug, Clone)]
559pub struct MemoryStats {
560 pub current_bytes: usize,
562
563 pub max_bytes: usize,
565
566 pub utilization: f64,
568}
569
570#[derive(Debug, Clone, Default)]
572pub struct ChunkStats {
573 pub chunks_processed: usize,
575
576 pub chunks_failed: usize,
578
579 pub bytes_processed: usize,
581
582 pub processing_time_ms: u64,
584
585 pub avg_chunk_time_ms: f64,
587}
588
589impl ChunkStats {
590 pub fn new() -> Self {
592 Self::default()
593 }
594
595 pub fn record_chunk(&mut self, size_bytes: usize, time_ms: u64) {
597 self.chunks_processed += 1;
598 self.bytes_processed += size_bytes;
599 self.processing_time_ms += time_ms;
600 self.avg_chunk_time_ms = (self.processing_time_ms as f64) / (self.chunks_processed as f64);
601 }
602
603 pub fn record_failure(&mut self) {
605 self.chunks_failed += 1;
606 }
607
608 pub fn throughput_mbps(&self) -> f64 {
610 if self.processing_time_ms == 0 {
611 return 0.0;
612 }
613 let mb = (self.bytes_processed as f64) / (1024.0 * 1024.0);
614 let seconds = (self.processing_time_ms as f64) / 1000.0;
615 mb / seconds
616 }
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 #[test]
624 fn test_chunk_calculation() {
625 let chunks = RasterStream::calculate_chunks(1024, 1024, 256, 256, 0);
626 assert_eq!(chunks, (4, 4));
627
628 let chunks = RasterStream::calculate_chunks(1000, 1000, 256, 256, 0);
629 assert_eq!(chunks, (4, 4));
630
631 let chunks = RasterStream::calculate_chunks(1024, 1024, 256, 256, 16);
632 assert_eq!(chunks, (5, 5));
633 }
634
635 #[test]
636 fn test_config_builder() {
637 let config = RasterStreamConfig::new()
638 .with_chunk_size(1024, 1024)
639 .with_overlap(32)
640 .with_compression(9)
641 .with_prefetch(4);
642
643 assert_eq!(config.chunk_size, (1024, 1024));
644 assert_eq!(config.overlap, 32);
645 assert!(config.compression);
646 assert_eq!(config.compression_level, 9);
647 assert_eq!(config.prefetch_count, 4);
648 }
649
650 #[test]
651 fn test_chunk_stats() {
652 let mut stats = ChunkStats::new();
653 stats.record_chunk(1024 * 1024, 100);
654 stats.record_chunk(1024 * 1024, 150);
655
656 assert_eq!(stats.chunks_processed, 2);
657 assert_eq!(stats.bytes_processed, 2 * 1024 * 1024);
658 assert_eq!(stats.avg_chunk_time_ms, 125.0);
659 assert!(stats.throughput_mbps() > 0.0);
660 }
661}