1#![expect(
3 clippy::cast_possible_truncation,
4 clippy::cast_possible_wrap,
5 clippy::cast_sign_loss
6)]
7use crate::Result;
8use std::collections::{BTreeMap, HashSet};
9use utiles_core::{Tile, lnglat2tile_frac, simplify, tile, utile};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct GeoTypesCoverOptions {
13 pub zoom: u8,
14 pub minzoom: Option<u8>,
15}
16
17impl From<u8> for GeoTypesCoverOptions {
18 fn from(zoom: u8) -> Self {
19 Self {
20 zoom,
21 minzoom: None,
22 }
23 }
24}
25
26#[expect(clippy::cast_precision_loss)]
27fn line_string_cover(
28 tiles_set: &mut HashSet<Tile>,
29 ls: &geo_types::LineString<f64>,
30 maxzoom: u8,
31 mut ring: Option<&mut Vec<(u32, u32)>>,
32) {
33 if ls.0.len() < 2 {
34 return;
35 }
36 let mut prev_x: Option<i64> = None;
37 let mut prev_y: Option<i64> = None;
38 let mut y_value: Option<i64> = None;
39 let minxy = (1u32 << maxzoom) - 1; for segment in ls.lines() {
43 let start_coord = segment.start;
45 let stop_coord = segment.end;
46
47 let (x0f, y0f, _) = lnglat2tile_frac(start_coord.x, start_coord.y, maxzoom);
48 let (x1f, y1f, _) = lnglat2tile_frac(stop_coord.x, stop_coord.y, maxzoom);
49
50 let dx = x1f - x0f;
51 let dy = y1f - y0f;
52
53 if dx == 0.0 && dy == 0.0 {
55 continue;
56 }
57 let sx = dx.signum() as i64;
58 let sy = dy.signum() as i64;
59
60 let mut x = x0f.floor() as i64;
61 let mut y = y0f.floor() as i64;
62 y_value = Some(y);
63
64 let tdx = if dx == 0.0 {
65 f64::INFINITY
66 } else {
67 (sx as f64 / dx).abs()
68 };
69 let tdy = if dy == 0.0 {
70 f64::INFINITY
71 } else {
72 (sy as f64 / dy).abs()
73 };
74
75 let mut t_max_x = if dx == 0.0 {
76 f64::INFINITY
77 } else {
78 ((if dx > 0.0 { 1.0 } else { 0.0 } + x as f64 - x0f) / dx).abs()
79 };
80 let mut t_max_y = if dy == 0.0 {
81 f64::INFINITY
82 } else {
83 ((if dy > 0.0 { 1.0 } else { 0.0 } + y as f64 - y0f) / dy).abs()
84 };
85
86 if prev_x != Some(x) || prev_y != Some(y) {
88 let tile = utile!(x as u32, y as u32, maxzoom);
89 tiles_set.insert(tile);
90 if let Some(ring) = &mut ring
91 && prev_y != Some(y)
92 {
93 ring.push((x as u32, y as u32));
94 }
95 prev_x = Some(x);
96 prev_y = Some(y);
97 }
98
99 let mut max_it = (dx.abs() + dy.abs()) as i64;
101 while (t_max_x < 1.0 || t_max_y < 1.0) && max_it >= 0 {
102 if t_max_x < t_max_y {
103 t_max_x += tdx;
104 x += sx;
105 } else {
106 t_max_y += tdy;
107 y += sy;
108 }
109
110 if x > i64::from(minxy) + 1 || y > i64::from(minxy) + 1 {
112 break;
113 }
114
115 if prev_x != Some(x) || prev_y != Some(y) {
116 let tile = utile!(x as u32, y as u32, maxzoom);
117 tiles_set.insert(tile);
118 if let Some(ring) = &mut ring
119 && prev_y != Some(y)
120 {
121 ring.push((x as u32, y as u32));
122 }
123 prev_x = Some(x);
124 prev_y = Some(y);
125 }
126
127 max_it -= 1; }
129 }
130
131 if let Some(ring) = &mut ring
133 && let (Some(first_ring), Some(y_value)) = (ring.first(), y_value)
134 && y_value == i64::from(first_ring.1)
135 {
136 ring.pop();
137 }
138}
139
140fn polygon_cover(
141 tiles_set: &mut HashSet<Tile>,
142 geom: &geo_types::Polygon<f64>,
143 zoom: u8,
144) {
145 let mut scanlines: BTreeMap<u32, Vec<u32>> = BTreeMap::new();
147
148 let rings = vec![geom.exterior()] .into_iter()
150 .chain(geom.interiors().iter()); for ring_pts in rings {
153 let mut boundary: Vec<(u32, u32)> = Vec::new();
155 line_string_cover(tiles_set, ring_pts, zoom, Some(&mut boundary));
156 if boundary.is_empty() {
157 continue;
158 }
159
160 let iter = boundary
162 .windows(2)
163 .map(|w| (w[0], w[1]))
164 .chain(std::iter::once((boundary[boundary.len() - 1], boundary[0])));
165
166 for ((x0f, y0f), (x1f, y1f)) in iter {
167 let x0 = x0f as i32;
168 let y0 = y0f as i32;
169 let x1 = x1f as i32;
170 let y1 = y1f as i32;
171
172 if y0 == y1 {
174 continue;
175 }
176
177 let (ymin, ymax) = (y0.min(y1), y0.max(y1));
179 let dx = x1 - x0;
180 let dy = y1 - y0;
181
182 for y in ymin..ymax {
183 let t = f64::from(y - y0) / f64::from(dy);
185 let x = t.mul_add(f64::from(dx), f64::from(x0)).floor() as u32;
186 scanlines.entry(y as u32).or_default().push(x);
187 }
188 }
189 }
190
191 for (y, mut xs) in scanlines {
193 xs.sort_unstable();
194 for pair in xs.chunks(2).filter(|c| c.len() == 2) {
196 let x_start = pair[0];
197 let x_end = pair[1];
198 tiles_set.extend((x_start..=x_end).map(|x| Tile::new(x, y, zoom)));
199 }
200 }
201}
202
203fn gt_geometry2tiles(
204 geom: &geo_types::Geometry,
205 opts: GeoTypesCoverOptions,
206) -> Result<HashSet<Tile>> {
207 let mut tilescoverage: HashSet<Tile> = HashSet::new();
208
209 match geom {
210 geo_types::Geometry::Point(pt) => {
211 let tile = tile(pt.x(), pt.y(), opts.zoom, None)?;
212 tilescoverage.insert(tile);
213 }
214 geo_types::Geometry::MultiPoint(pts) => {
215 let it = pts
216 .iter()
217 .filter_map(|pt| tile(pt.x(), pt.y(), opts.zoom, None).ok());
218 tilescoverage.extend(it);
219 }
220 geo_types::Geometry::Line(ln) => {
221 let ls = geo_types::LineString::from(ln);
222 line_string_cover(&mut tilescoverage, &ls, opts.zoom, None);
223 }
224 geo_types::Geometry::LineString(ls) => {
225 line_string_cover(&mut tilescoverage, ls, opts.zoom, None);
226 }
227 geo_types::Geometry::MultiLineString(mls) => {
228 mls.iter().for_each(|ls| {
229 line_string_cover(&mut tilescoverage, ls, opts.zoom, None);
230 });
231 }
232 geo_types::Geometry::Polygon(poly) => {
233 polygon_cover(&mut tilescoverage, poly, opts.zoom);
234 }
235 geo_types::Geometry::MultiPolygon(mpoly) => {
236 for poly in mpoly.iter() {
237 polygon_cover(&mut tilescoverage, poly, opts.zoom);
238 }
239 }
240 geo_types::Geometry::GeometryCollection(gjcoll) => {
241 for g in gjcoll {
242 tilescoverage.extend(gt_geometry2tiles(g, opts)?);
243 }
244 }
245 geo_types::Geometry::Rect(r) => {
246 let poly = geo_types::Polygon::from(*r);
247 polygon_cover(&mut tilescoverage, &poly, opts.zoom);
248 }
249 geo_types::Geometry::Triangle(t) => {
250 let poly = geo_types::Polygon::from(*t);
251 polygon_cover(&mut tilescoverage, &poly, opts.zoom);
252 }
253 }
254
255 match opts.minzoom {
256 Some(z) => {
257 let cov = simplify(&tilescoverage, Some(z));
258 Ok(cov)
259 }
260 None => Ok(tilescoverage),
261 }
262}
263
264pub fn geometry2tiles<T>(geom: &geo_types::Geometry, opts: T) -> Result<HashSet<Tile>>
271where
272 T: Into<GeoTypesCoverOptions>,
273{
274 let opts = opts.into();
275 gt_geometry2tiles(geom, opts)
276}