1#![expect(
3 clippy::cast_possible_truncation,
4 clippy::cast_possible_wrap,
5 clippy::cast_sign_loss
6)]
7use crate::{Result, UtilesCoverError};
8use geojson::GeoJson;
9use std::collections::{BTreeMap, HashSet};
10use utiles_core::{Tile, lnglat2tile_frac, simplify, tile, utile};
11
12#[expect(clippy::cast_precision_loss)]
13fn line_string_cover(
14 tiles_set: &mut HashSet<Tile>,
15 coords: &[(f64, f64)],
16 maxzoom: u8,
17 mut ring: Option<&mut Vec<(u32, u32)>>,
18) {
19 let mut prev_x: Option<i64> = None;
20 let mut prev_y: Option<i64> = None;
21 let mut y_value: Option<i64> = None;
22 let minxy = (1u32 << maxzoom) - 1; for segment in coords.windows(2) {
24 let start_coord = segment[0];
25 let stop_coord = segment[1];
26
27 let (x0f, y0f, _) = lnglat2tile_frac(start_coord.0, start_coord.1, maxzoom);
28 let (x1f, y1f, _) = lnglat2tile_frac(stop_coord.0, stop_coord.1, maxzoom);
29
30 let dx = x1f - x0f;
31 let dy = y1f - y0f;
32
33 if dx == 0.0 && dy == 0.0 {
35 continue;
36 }
37 let sx = dx.signum() as i64;
38 let sy = dy.signum() as i64;
39
40 let mut x = x0f.floor() as i64;
41 let mut y = y0f.floor() as i64;
42 y_value = Some(y);
43
44 let tdx = if dx == 0.0 {
45 f64::INFINITY
46 } else {
47 (sx as f64 / dx).abs()
48 };
49 let tdy = if dy == 0.0 {
50 f64::INFINITY
51 } else {
52 (sy as f64 / dy).abs()
53 };
54
55 let mut t_max_x = if dx == 0.0 {
56 f64::INFINITY
57 } else {
58 ((if dx > 0.0 { 1.0 } else { 0.0 } + x as f64 - x0f) / dx).abs()
59 };
60 let mut t_max_y = if dy == 0.0 {
61 f64::INFINITY
62 } else {
63 ((if dy > 0.0 { 1.0 } else { 0.0 } + y as f64 - y0f) / dy).abs()
64 };
65
66 if prev_x != Some(x) || prev_y != Some(y) {
68 let tile = utile!(x as u32, y as u32, maxzoom);
69 tiles_set.insert(tile);
70 if let Some(ring) = &mut ring
71 && prev_y != Some(y)
72 {
73 ring.push((x as u32, y as u32));
74 }
75 prev_x = Some(x);
76 prev_y = Some(y);
77 }
78
79 let mut max_it = (dx.abs() + dy.abs()) as i64;
81 while (t_max_x < 1.0 || t_max_y < 1.0) && max_it >= 0 {
82 if t_max_x < t_max_y {
83 t_max_x += tdx;
84 x += sx;
85 } else {
86 t_max_y += tdy;
87 y += sy;
88 }
89
90 if x > i64::from(minxy) + 1 || y > i64::from(minxy) + 1 {
92 break;
93 }
94
95 if prev_x != Some(x) || prev_y != Some(y) {
96 let tile = utile!(x as u32, y as u32, maxzoom);
97 tiles_set.insert(tile);
98 if let Some(ring) = &mut ring
99 && prev_y != Some(y)
100 {
101 ring.push((x as u32, y as u32));
102 }
103 prev_x = Some(x);
104 prev_y = Some(y);
105 }
106
107 max_it -= 1; }
109 }
110
111 if let Some(ring) = &mut ring
113 && let (Some(first_ring), Some(y_value)) = (ring.first(), y_value)
114 && y_value == i64::from(first_ring.1)
115 {
116 ring.pop();
117 }
118}
119
120fn polygon_cover(tiles_set: &mut HashSet<Tile>, geom: &[Vec<(f64, f64)>], zoom: u8) {
121 let mut scanlines: BTreeMap<u32, Vec<u32>> = BTreeMap::new();
123
124 for ring_pts in geom {
125 let mut boundary: Vec<(u32, u32)> = Vec::new();
127 line_string_cover(tiles_set, ring_pts, zoom, Some(&mut boundary));
128 if boundary.is_empty() {
129 continue;
130 }
131
132 let iter = boundary
134 .windows(2)
135 .map(|w| (w[0], w[1]))
136 .chain(std::iter::once((boundary[boundary.len() - 1], boundary[0])));
137
138 for ((x0f, y0f), (x1f, y1f)) in iter {
139 let x0 = x0f as i32;
140 let y0 = y0f as i32;
141 let x1 = x1f as i32;
142 let y1 = y1f as i32;
143
144 if y0 == y1 {
146 continue;
147 }
148
149 let (ymin, ymax) = (y0.min(y1), y0.max(y1));
151 let dx = x1 - x0;
152 let dy = y1 - y0;
153
154 for y in ymin..ymax {
155 let t = f64::from(y - y0) / f64::from(dy);
157 let x = t.mul_add(f64::from(dx), f64::from(x0)).floor() as u32;
158 scanlines.entry(y as u32).or_default().push(x);
159 }
160 }
161 }
162
163 for (y, mut xs) in scanlines {
165 xs.sort_unstable();
166 for pair in xs.chunks(2).filter(|c| c.len() == 2) {
168 let x_start = pair[0];
169 let x_end = pair[1];
170 for x in x_start..x_end {
171 tiles_set.insert(Tile::new(x, y, zoom));
172 }
173 }
174 }
175}
176
177fn geom2tiles(geom: &geojson::Geometry, zoom: u8) -> Result<Vec<Tile>> {
178 let mut tiles_set = HashSet::new();
179 let res = match &geom.value {
180 geojson::Value::Point(coords) => {
181 let tile = tile(coords[0], coords[1], zoom, None)?;
182 tiles_set.insert(tile);
183 Ok::<(), UtilesCoverError>(())
184 }
185 geojson::Value::MultiPoint(coords_list) => {
186 coords_list
187 .iter()
188 .map(|coords| {
189 tile(coords[0], coords[1], zoom, None)
190 .map_err(UtilesCoverError::from)
191 })
192 .collect::<Result<HashSet<_>>>()
193 .map(|set| {
194 tiles_set.extend(set);
195 })?;
196 Ok(())
197 }
198 geojson::Value::LineString(coords_list) => {
199 let coords: Vec<(f64, f64)> =
200 coords_list.iter().map(|c| (c[0], c[1])).collect();
201 line_string_cover(&mut tiles_set, &coords, zoom, None);
202 Ok(())
203 }
204 geojson::Value::MultiLineString(coords_lists) => {
205 for coords_list in coords_lists {
206 let coords: Vec<(f64, f64)> =
207 coords_list.iter().map(|c| (c[0], c[1])).collect();
208 line_string_cover(&mut tiles_set, &coords, zoom, None);
209 }
210 Ok(())
211 }
212 geojson::Value::Polygon(coords_lists) => {
213 let coords: Vec<Vec<(f64, f64)>> = coords_lists
214 .iter()
215 .map(|ring| ring.iter().map(|c| (c[0], c[1])).collect())
216 .collect();
217 polygon_cover(&mut tiles_set, &coords, zoom);
218 Ok(())
219 }
220 geojson::Value::MultiPolygon(coords_list_of_lists) => {
221 for coords_lists in coords_list_of_lists {
222 let coords: Vec<Vec<(f64, f64)>> = coords_lists
223 .iter()
224 .map(|ring| ring.iter().map(|c| (c[0], c[1])).collect())
225 .collect();
226 polygon_cover(&mut tiles_set, &coords, zoom);
227 }
228 Ok(())
229 }
230 geojson::Value::GeometryCollection(gjcoll) => {
231 for geom in gjcoll {
232 let recurse_res = geom2tiles(geom, zoom)?;
233 tiles_set.extend(recurse_res);
234 }
235 Ok(())
236 }
237 };
238 res?;
239 let tiles_vec = tiles_set.into_iter().collect();
240 Ok(tiles_vec)
241}
242
243pub struct GeojsonCoverOptions {
244 pub zoom: u8,
245 pub minzoom: Option<u8>,
246}
247
248impl From<u8> for GeojsonCoverOptions {
249 fn from(zoom: u8) -> Self {
250 Self {
251 zoom,
252 minzoom: None,
253 }
254 }
255}
256
257impl From<(u8, u8)> for GeojsonCoverOptions {
258 fn from((zoom, minzoom): (u8, u8)) -> Self {
259 Self {
260 zoom,
261 minzoom: Some(minzoom),
262 }
263 }
264}
265
266pub fn geojson2tiles<TOpts>(gj: &GeoJson, opts: TOpts) -> Result<HashSet<Tile>>
273where
274 TOpts: Into<GeojsonCoverOptions>,
275{
276 let opts = opts.into();
277 let mut tiles: HashSet<Tile> = HashSet::new();
278 match gj {
279 GeoJson::FeatureCollection(ctn) => {
280 for feature in &ctn.features {
281 if let Some(ref geom) = feature.geometry {
282 let cov = geom2tiles(geom, opts.zoom)?;
283 tiles.extend(cov);
284 }
285 }
286 }
287 GeoJson::Feature(feature) => {
288 if let Some(ref geom) = feature.geometry {
289 let cov = geom2tiles(geom, opts.zoom)?;
290 tiles.extend(cov);
291 }
292 }
293 GeoJson::Geometry(geom) => {
294 let cov = geom2tiles(geom, opts.zoom)?;
295 tiles.extend(cov);
296 }
297 }
298 match opts.minzoom {
299 Some(z) => {
300 let cov = simplify(&tiles, Some(z));
301 Ok(cov)
302 }
303 None => Ok(tiles),
304 }
305}