geojson_tile_renderer/
types.rs

1use crate::error::{RenderError, Result};
2
3/// Tile coordinate in the Web Mercator tile scheme (z/x/y)
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub struct TileCoordinate {
6	/// Zoom level (0-30)
7	pub z: u8,
8	/// X coordinate (column)
9	pub x: u32,
10	/// Y coordinate (row)
11	pub y: u32,
12}
13
14impl TileCoordinate {
15	/// Create a new tile coordinate
16	///
17	/// # Arguments
18	/// * `z` - Zoom level (0-30)
19	/// * `x` - X coordinate (column)
20	/// * `y` - Y coordinate (row)
21	pub fn new(z: u8, x: u32, y: u32) -> Result<Self> {
22		// Validate tile coordinates
23		let max_coord = 2u32.pow(z as u32);
24		if x >= max_coord || y >= max_coord {
25			return Err(RenderError::InvalidCoordinate(format!(
26				"Tile coordinate ({}, {}) out of range for zoom level {} (max: {})",
27				x, y, z, max_coord - 1
28			)));
29		}
30
31		Ok(Self { z, x, y })
32	}
33}
34
35/// RGBA color with values in the range [0, 255]
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct BackgroundColor {
38	/// Red component (0-255)
39	pub r: u8,
40	/// Green component (0-255)
41	pub g: u8,
42	/// Blue component (0-255)
43	pub b: u8,
44	/// Alpha component (0-255), where 0 is fully transparent and 255 is fully opaque
45	pub a: u8,
46}
47
48impl BackgroundColor {
49	/// Create a new RGBA color
50	pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
51		Self { r, g, b, a }
52	}
53
54	/// Create a fully opaque color from RGB values
55	pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
56		Self::new(r, g, b, 255)
57	}
58
59	/// Create a transparent color
60	pub const fn transparent() -> Self {
61		Self::new(0, 0, 0, 0)
62	}
63
64	/// Create a white color
65	pub const fn white() -> Self {
66		Self::rgb(255, 255, 255)
67	}
68
69	/// Create a black color
70	pub const fn black() -> Self {
71		Self::rgb(0, 0, 0)
72	}
73}
74
75impl Default for BackgroundColor {
76	fn default() -> Self {
77		Self::transparent()
78	}
79}
80
81/// Settings for tile rendering
82#[derive(Debug, Clone)]
83pub struct Settings {
84	/// Background color for the tile
85	pub background_color: BackgroundColor,
86	/// Size of the tile in pixels (width and height)
87	pub size: u32,
88}
89
90impl Settings {
91	/// Create a new Settings instance with default values
92	pub fn new() -> Self {
93		Self::default()
94	}
95
96	/// Create a builder for Settings
97	pub fn builder() -> SettingsBuilder {
98		SettingsBuilder::default()
99	}
100}
101
102impl Default for Settings {
103	fn default() -> Self {
104		Self {
105			background_color: BackgroundColor::default(),
106			size: 256,
107		}
108	}
109}
110
111/// Builder for Settings
112#[derive(Debug, Clone, Default)]
113pub struct SettingsBuilder {
114	background_color: Option<BackgroundColor>,
115	size: Option<u32>,
116}
117
118impl SettingsBuilder {
119	/// Set the background color
120	pub fn background_color(mut self, color: BackgroundColor) -> Self {
121		self.background_color = Some(color);
122		self
123	}
124
125	/// Set the tile size in pixels
126	pub fn size(mut self, size: u32) -> Self {
127		self.size = Some(size);
128		self
129	}
130
131	/// Build the Settings
132	pub fn build(self) -> Result<Settings> {
133		let size = self.size.unwrap_or(256);
134
135		// Validate size
136		if size == 0 {
137			return Err(RenderError::InvalidSettings(
138				"Tile size must be greater than 0".to_string(),
139			));
140		}
141
142		// Reasonable upper limit to prevent memory issues
143		if size > 4096 {
144			return Err(RenderError::InvalidSettings(
145				"Tile size must be 4096 or less".to_string(),
146			));
147		}
148
149		Ok(Settings {
150			background_color: self.background_color.unwrap_or_default(),
151			size,
152		})
153	}
154}
155
156#[cfg(test)]
157mod tests {
158	use super::*;
159
160	#[test]
161	fn test_tile_coordinate_new() {
162		// Valid coordinates
163		assert!(TileCoordinate::new(0, 0, 0).is_ok());
164		assert!(TileCoordinate::new(5, 15, 20).is_ok());
165		assert!(TileCoordinate::new(10, 1023, 1023).is_ok());
166
167		// Invalid coordinates (out of range)
168		assert!(TileCoordinate::new(0, 1, 0).is_err()); // Only one tile at z=0
169		assert!(TileCoordinate::new(5, 32, 0).is_err()); // Max is 31 at z=5
170		assert!(TileCoordinate::new(5, 0, 32).is_err());
171	}
172
173	#[test]
174	fn test_background_color() {
175		let color = BackgroundColor::new(255, 128, 64, 200);
176		assert_eq!(color.r, 255);
177		assert_eq!(color.g, 128);
178		assert_eq!(color.b, 64);
179		assert_eq!(color.a, 200);
180
181		let rgb = BackgroundColor::rgb(100, 150, 200);
182		assert_eq!(rgb.a, 255);
183
184		let transparent = BackgroundColor::transparent();
185		assert_eq!(transparent.a, 0);
186	}
187
188	#[test]
189	fn test_settings_builder() {
190		let settings = Settings::builder()
191			.size(512)
192			.background_color(BackgroundColor::white())
193			.build()
194			.unwrap();
195
196		assert_eq!(settings.size, 512);
197		assert_eq!(settings.background_color, BackgroundColor::white());
198	}
199
200	#[test]
201	fn test_settings_builder_defaults() {
202		let settings = Settings::builder().build().unwrap();
203		assert_eq!(settings.size, 256);
204		assert_eq!(settings.background_color, BackgroundColor::transparent());
205	}
206
207	#[test]
208	fn test_settings_validation() {
209		// Size must be > 0
210		assert!(Settings::builder().size(0).build().is_err());
211
212		// Size must be <= 4096
213		assert!(Settings::builder().size(4097).build().is_err());
214
215		// Valid sizes
216		assert!(Settings::builder().size(1).build().is_ok());
217		assert!(Settings::builder().size(256).build().is_ok());
218		assert!(Settings::builder().size(4096).build().is_ok());
219	}
220}