Skip to main content

oxigdal_dev_tools/
generator.rs

1//! Test data generation utilities
2//!
3//! This module provides tools for generating test data for OxiGDAL operations.
4
5use crate::Result;
6use serde::{Deserialize, Serialize};
7use std::path::Path;
8
9/// Test data generator
10pub struct DataGenerator {
11    /// Random seed
12    seed: u64,
13}
14
15impl DataGenerator {
16    /// Create a new data generator
17    pub fn new() -> Self {
18        Self { seed: 12345 }
19    }
20
21    /// Create a generator with a specific seed
22    pub fn with_seed(seed: u64) -> Self {
23        Self { seed }
24    }
25
26    /// Generate raster data
27    pub fn generate_raster(&self, width: usize, height: usize, pattern: RasterPattern) -> Vec<f64> {
28        let mut data = vec![0.0; width * height];
29
30        match pattern {
31            RasterPattern::Flat(value) => {
32                data.fill(value);
33            }
34            RasterPattern::Gradient {
35                from,
36                to,
37                direction,
38            } => {
39                for y in 0..height {
40                    for x in 0..width {
41                        let t = match direction {
42                            GradientDirection::Horizontal => x as f64 / (width - 1) as f64,
43                            GradientDirection::Vertical => y as f64 / (height - 1) as f64,
44                            GradientDirection::Diagonal => {
45                                ((x + y) as f64) / ((width + height - 2) as f64)
46                            }
47                        };
48                        data[y * width + x] = from + (to - from) * t;
49                    }
50                }
51            }
52            RasterPattern::Checkerboard {
53                size,
54                color1,
55                color2,
56            } => {
57                for y in 0..height {
58                    for x in 0..width {
59                        let is_odd = ((x / size) + (y / size)) % 2 == 1;
60                        data[y * width + x] = if is_odd { color1 } else { color2 };
61                    }
62                }
63            }
64            RasterPattern::Noise { min, max } => {
65                for (i, item) in data.iter_mut().enumerate() {
66                    *item = min + (max - min) * self.pseudo_random(i);
67                }
68            }
69            RasterPattern::Sine {
70                amplitude,
71                frequency,
72            } => {
73                use std::f64::consts::PI;
74                for y in 0..height {
75                    for x in 0..width {
76                        let phase = 2.0 * PI * frequency * (x as f64 / width as f64);
77                        data[y * width + x] = amplitude * phase.sin();
78                    }
79                }
80            }
81        }
82
83        data
84    }
85
86    /// Simple pseudo-random number generator (LCG)
87    fn pseudo_random(&self, index: usize) -> f64 {
88        let a = 1103515245u64;
89        let c = 12345u64;
90        let m = 2u64.pow(31);
91
92        let x = ((a
93            .wrapping_mul(self.seed.wrapping_add(index as u64))
94            .wrapping_add(c))
95            % m) as f64;
96        x / m as f64
97    }
98
99    /// Generate vector features (points)
100    pub fn generate_points(&self, count: usize, bounds: Bounds) -> Vec<Point> {
101        let mut points = Vec::with_capacity(count);
102
103        for i in 0..count {
104            let x = bounds.min_x + (bounds.max_x - bounds.min_x) * self.pseudo_random(i * 2);
105            let y = bounds.min_y + (bounds.max_y - bounds.min_y) * self.pseudo_random(i * 2 + 1);
106
107            points.push(Point { x, y });
108        }
109
110        points
111    }
112
113    /// Generate regular grid of points
114    pub fn generate_grid(&self, rows: usize, cols: usize, bounds: Bounds) -> Vec<Point> {
115        let mut points = Vec::with_capacity(rows * cols);
116
117        let dx = (bounds.max_x - bounds.min_x) / (cols - 1) as f64;
118        let dy = (bounds.max_y - bounds.min_y) / (rows - 1) as f64;
119
120        for row in 0..rows {
121            for col in 0..cols {
122                let x = bounds.min_x + col as f64 * dx;
123                let y = bounds.min_y + row as f64 * dy;
124                points.push(Point { x, y });
125            }
126        }
127
128        points
129    }
130}
131
132impl Default for DataGenerator {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138/// Raster pattern
139#[derive(Debug, Clone)]
140pub enum RasterPattern {
141    /// Flat value
142    Flat(f64),
143    /// Gradient
144    Gradient {
145        /// Start value
146        from: f64,
147        /// End value
148        to: f64,
149        /// Direction
150        direction: GradientDirection,
151    },
152    /// Checkerboard pattern
153    Checkerboard {
154        /// Cell size
155        size: usize,
156        /// Color 1
157        color1: f64,
158        /// Color 2
159        color2: f64,
160    },
161    /// Random noise
162    Noise {
163        /// Minimum value
164        min: f64,
165        /// Maximum value
166        max: f64,
167    },
168    /// Sine wave
169    Sine {
170        /// Amplitude
171        amplitude: f64,
172        /// Frequency
173        frequency: f64,
174    },
175}
176
177/// Gradient direction
178#[derive(Debug, Clone, Copy)]
179pub enum GradientDirection {
180    /// Horizontal (left to right)
181    Horizontal,
182    /// Vertical (top to bottom)
183    Vertical,
184    /// Diagonal (top-left to bottom-right)
185    Diagonal,
186}
187
188/// 2D point
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct Point {
191    /// X coordinate
192    pub x: f64,
193    /// Y coordinate
194    pub y: f64,
195}
196
197/// Bounding box
198#[derive(Debug, Clone, Copy)]
199pub struct Bounds {
200    /// Minimum X
201    pub min_x: f64,
202    /// Minimum Y
203    pub min_y: f64,
204    /// Maximum X
205    pub max_x: f64,
206    /// Maximum Y
207    pub max_y: f64,
208}
209
210impl Bounds {
211    /// Create new bounds
212    pub fn new(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
213        Self {
214            min_x,
215            min_y,
216            max_x,
217            max_y,
218        }
219    }
220}
221
222/// File generator for creating test files
223pub struct FileGenerator;
224
225impl FileGenerator {
226    /// Generate a simple GeoTIFF-like file
227    pub fn generate_geotiff(_path: &Path, _width: usize, _height: usize) -> Result<()> {
228        // Placeholder - would need actual GeoTIFF writer
229        Ok(())
230    }
231
232    /// Generate a simple GeoJSON file
233    pub fn generate_geojson(path: &Path, points: &[Point]) -> Result<()> {
234        use std::io::Write;
235
236        let mut geojson =
237            String::from("{\n  \"type\": \"FeatureCollection\",\n  \"features\": [\n");
238
239        for (i, point) in points.iter().enumerate() {
240            geojson.push_str(&format!(
241                "    {{\n      \"type\": \"Feature\",\n      \"geometry\": {{\n        \"type\": \"Point\",\n        \"coordinates\": [{}, {}]\n      }},\n      \"properties\": {{\n        \"id\": {}\n      }}\n    }}",
242                point.x, point.y, i
243            ));
244
245            if i < points.len() - 1 {
246                geojson.push_str(",\n");
247            } else {
248                geojson.push('\n');
249            }
250        }
251
252        geojson.push_str("  ]\n}");
253
254        let mut file = std::fs::File::create(path)?;
255        file.write_all(geojson.as_bytes())?;
256
257        Ok(())
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264
265    #[test]
266    fn test_generator_creation() {
267        let generator = DataGenerator::new();
268        assert_eq!(generator.seed, 12345);
269    }
270
271    #[test]
272    fn test_generate_flat_raster() {
273        let generator = DataGenerator::new();
274        let data = generator.generate_raster(10, 10, RasterPattern::Flat(42.0));
275        assert_eq!(data.len(), 100);
276        assert!(data.iter().all(|&v| v == 42.0));
277    }
278
279    #[test]
280    fn test_generate_gradient_raster() {
281        let generator = DataGenerator::new();
282        let data = generator.generate_raster(
283            10,
284            10,
285            RasterPattern::Gradient {
286                from: 0.0,
287                to: 100.0,
288                direction: GradientDirection::Horizontal,
289            },
290        );
291        assert_eq!(data.len(), 100);
292        assert_eq!(data[0], 0.0); // First column
293        assert!((data[9] - 100.0).abs() < 0.01); // Last column
294    }
295
296    #[test]
297    fn test_generate_checkerboard() {
298        let generator = DataGenerator::new();
299        let data = generator.generate_raster(
300            10,
301            10,
302            RasterPattern::Checkerboard {
303                size: 5,
304                color1: 0.0,
305                color2: 100.0,
306            },
307        );
308        assert_eq!(data.len(), 100);
309    }
310
311    #[test]
312    fn test_generate_noise() {
313        let generator = DataGenerator::new();
314        let data = generator.generate_raster(
315            10,
316            10,
317            RasterPattern::Noise {
318                min: 0.0,
319                max: 100.0,
320            },
321        );
322        assert_eq!(data.len(), 100);
323        assert!(data.iter().all(|&v| (0.0..=100.0).contains(&v)));
324    }
325
326    #[test]
327    fn test_generate_points() {
328        let generator = DataGenerator::new();
329        let bounds = Bounds::new(0.0, 0.0, 100.0, 100.0);
330        let points = generator.generate_points(10, bounds);
331        assert_eq!(points.len(), 10);
332        assert!(points.iter().all(|p| p.x >= 0.0 && p.x <= 100.0));
333        assert!(points.iter().all(|p| p.y >= 0.0 && p.y <= 100.0));
334    }
335
336    #[test]
337    fn test_generate_grid() {
338        let generator = DataGenerator::new();
339        let bounds = Bounds::new(0.0, 0.0, 100.0, 100.0);
340        let points = generator.generate_grid(5, 5, bounds);
341        assert_eq!(points.len(), 25);
342    }
343
344    #[test]
345    fn test_generate_geojson() -> Result<()> {
346        use tempfile::NamedTempFile;
347
348        let temp_file = NamedTempFile::new()?;
349        let points = vec![Point { x: 0.0, y: 0.0 }, Point { x: 1.0, y: 1.0 }];
350
351        FileGenerator::generate_geojson(temp_file.path(), &points)?;
352
353        let content = std::fs::read_to_string(temp_file.path())?;
354        assert!(content.contains("FeatureCollection"));
355        assert!(content.contains("Point"));
356
357        Ok(())
358    }
359}