1use crate::error::{RenderError, Result};
2use crate::projection::{lat_lon_to_web_mercator, tile_to_bounds, BoundingBox};
3use crate::svg::{generate_svg_items, SvgContext};
4use crate::types::{Settings, TileCoordinate};
5use geojson::{Feature, FeatureCollection};
6use rayon::prelude::*;
7
8pub struct TileRenderer {
10 settings: Settings,
11}
12
13pub struct TileRendererBuilder {
15 settings: Option<Settings>,
16}
17
18impl Default for TileRendererBuilder {
19 fn default() -> Self {
20 Self { settings: None }
21 }
22}
23
24impl TileRendererBuilder {
25 pub fn settings(mut self, settings: Settings) -> Self {
27 self.settings = Some(settings);
28 self
29 }
30
31 pub fn build(self) -> Result<TileRenderer> {
33 let settings = self.settings.unwrap_or_default();
34 Ok(TileRenderer { settings })
35 }
36}
37
38impl TileRenderer {
39 pub fn new() -> Self {
41 Self {
42 settings: Settings::default(),
43 }
44 }
45
46 pub fn builder() -> TileRendererBuilder {
48 TileRendererBuilder::default()
49 }
50
51 pub fn render(&self, geojson: &FeatureCollection, tile: TileCoordinate) -> Result<Vec<u8>> {
60 self.render_internal(&geojson.features, tile)
61 }
62
63 pub fn render_feature(&self, feature: &Feature, tile: TileCoordinate) -> Result<Vec<u8>> {
72 self.render_internal(&[feature.clone()], tile)
73 }
74
75 pub fn render_many(&self, geojson: &FeatureCollection, tiles: &[TileCoordinate]) -> Result<Vec<Vec<u8>>> {
84 tiles
86 .par_iter()
87 .map(|&tile| self.render(geojson, tile))
88 .collect()
89 }
90
91 fn render_internal(&self, features: &[Feature], tile: TileCoordinate) -> Result<Vec<u8>> {
93 let size = self.settings.size as f64;
94
95 let image_polygon = tile_to_bounds(tile);
97
98 let mercator_bbox = {
100 let exterior = image_polygon.exterior();
101 let coords: Vec<_> = exterior.coords().collect();
102
103 let min_coord = lat_lon_to_web_mercator(coords[0].x, coords[0].y)?;
105 let max_coord = lat_lon_to_web_mercator(coords[2].x, coords[2].y)?;
106
107 BoundingBox::new(min_coord.x, min_coord.y, max_coord.x, max_coord.y)
108 };
109
110 let x_scaling_factor = size / mercator_bbox.width();
112 let y_scaling_factor = size / mercator_bbox.height();
113
114 let context = SvgContext::new(
116 mercator_bbox,
117 size,
118 x_scaling_factor,
119 y_scaling_factor,
120 image_polygon,
121 );
122
123 let svg_items = generate_svg_items(features, &context)?;
125
126 let svg = format!(
128 r#"<svg xmlns="http://www.w3.org/2000/svg" width="{}" height="{}">{}</svg>"#,
129 self.settings.size,
130 self.settings.size,
131 svg_items.join("")
132 );
133
134 self.svg_to_png(&svg)
136 }
137
138 fn svg_to_png(&self, svg: &str) -> Result<Vec<u8>> {
140 let opts = usvg::Options::default();
142 let tree = usvg::Tree::from_str(svg, &opts)
143 .map_err(|e| RenderError::SvgGeneration(format!("Failed to parse SVG: {}", e)))?;
144
145 let size = self.settings.size;
147 let mut pixmap = tiny_skia::Pixmap::new(size, size)
148 .ok_or_else(|| RenderError::ImageRendering("Failed to create pixmap".to_string()))?;
149
150 let bg = &self.settings.background_color;
152 let color = tiny_skia::Color::from_rgba8(bg.r, bg.g, bg.b, bg.a);
153 pixmap.fill(color);
154
155 resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());
157
158 pixmap
160 .encode_png()
161 .map_err(|e| RenderError::ImageRendering(format!("Failed to encode PNG: {}", e)))
162 }
163}
164
165impl Default for TileRenderer {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::types::BackgroundColor;
175 use serde_json::json;
176
177 #[test]
178 fn test_tile_renderer_new() {
179 let renderer = TileRenderer::new();
180 assert_eq!(renderer.settings.size, 256);
181 }
182
183 #[test]
184 fn test_tile_renderer_builder() {
185 let renderer = TileRenderer::builder()
186 .settings(
187 Settings::builder()
188 .size(512)
189 .background_color(BackgroundColor::white())
190 .build()
191 .unwrap(),
192 )
193 .build()
194 .unwrap();
195
196 assert_eq!(renderer.settings.size, 512);
197 assert_eq!(renderer.settings.background_color, BackgroundColor::white());
198 }
199
200 #[test]
201 fn test_render_empty_collection() {
202 let renderer = TileRenderer::new();
203 let collection = FeatureCollection {
204 bbox: None,
205 features: vec![],
206 foreign_members: None,
207 };
208
209 let tile = TileCoordinate::new(10, 163, 395).unwrap();
210 let result = renderer.render(&collection, tile);
211
212 assert!(result.is_ok());
213 let png_data = result.unwrap();
214 assert!(!png_data.is_empty());
215
216 assert_eq!(&png_data[0..8], &[137, 80, 78, 71, 13, 10, 26, 10]);
218 }
219
220 #[test]
221 fn test_render_polygon() {
222 let renderer = TileRenderer::new();
223
224 let feature: Feature = serde_json::from_value(json!({
225 "type": "Feature",
226 "geometry": {
227 "type": "Polygon",
228 "coordinates": [[
229 [-122.5, 37.7],
230 [-122.4, 37.7],
231 [-122.4, 37.8],
232 [-122.5, 37.8],
233 [-122.5, 37.7]
234 ]]
235 },
236 "properties": {
237 "fill": "red",
238 "fill-opacity": "0.5"
239 }
240 }))
241 .unwrap();
242
243 let collection = FeatureCollection {
244 bbox: None,
245 features: vec![feature],
246 foreign_members: None,
247 };
248
249 let tile = TileCoordinate::new(10, 163, 395).unwrap();
250 let result = renderer.render(&collection, tile);
251
252 assert!(result.is_ok());
253 let png_data = result.unwrap();
254 assert!(!png_data.is_empty());
255 assert_eq!(&png_data[0..8], &[137, 80, 78, 71, 13, 10, 26, 10]);
256 }
257
258 #[test]
259 fn test_render_feature() {
260 let renderer = TileRenderer::new();
261
262 let feature: Feature = serde_json::from_value(json!({
263 "type": "Feature",
264 "geometry": {
265 "type": "Point",
266 "coordinates": [-122.45, 37.75]
267 },
268 "properties": {
269 "text": "San Francisco"
270 }
271 }))
272 .unwrap();
273
274 let tile = TileCoordinate::new(10, 163, 395).unwrap();
275 let result = renderer.render_feature(&feature, tile);
276
277 assert!(result.is_ok());
278 let png_data = result.unwrap();
279 assert!(!png_data.is_empty());
280 }
281
282 #[test]
283 fn test_render_many() {
284 let renderer = TileRenderer::new();
285 let collection = FeatureCollection {
286 bbox: None,
287 features: vec![],
288 foreign_members: None,
289 };
290
291 let tiles = vec![
292 TileCoordinate::new(10, 163, 395).unwrap(),
293 TileCoordinate::new(10, 164, 395).unwrap(),
294 TileCoordinate::new(10, 163, 396).unwrap(),
295 ];
296
297 let results = renderer.render_many(&collection, &tiles);
298 assert!(results.is_ok());
299
300 let png_buffers = results.unwrap();
301 assert_eq!(png_buffers.len(), 3);
302
303 for png_data in png_buffers {
304 assert!(!png_data.is_empty());
305 assert_eq!(&png_data[0..8], &[137, 80, 78, 71, 13, 10, 26, 10]);
306 }
307 }
308
309 #[test]
310 fn test_render_with_background_color() {
311 let renderer = TileRenderer::builder()
312 .settings(
313 Settings::builder()
314 .background_color(BackgroundColor::rgb(255, 0, 0))
315 .build()
316 .unwrap(),
317 )
318 .build()
319 .unwrap();
320
321 let collection = FeatureCollection {
322 bbox: None,
323 features: vec![],
324 foreign_members: None,
325 };
326
327 let tile = TileCoordinate::new(0, 0, 0).unwrap();
328 let result = renderer.render(&collection, tile);
329
330 assert!(result.is_ok());
331 let png_data = result.unwrap();
332 assert!(!png_data.is_empty());
333 }
334
335 #[test]
336 fn test_render_different_tile_sizes() {
337 for size in [128, 256, 512] {
338 let renderer = TileRenderer::builder()
339 .settings(Settings::builder().size(size).build().unwrap())
340 .build()
341 .unwrap();
342
343 let collection = FeatureCollection {
344 bbox: None,
345 features: vec![],
346 foreign_members: None,
347 };
348
349 let tile = TileCoordinate::new(5, 10, 12).unwrap();
350 let result = renderer.render(&collection, tile);
351
352 assert!(result.is_ok());
353 let png_data = result.unwrap();
354 assert!(!png_data.is_empty());
355 }
356 }
357}