gistools/geometry/wm/convert/
s2.rs

1use crate::geometry::{ClipLineResultWithBBox, LonLat, S2Point, clip_line};
2use alloc::{collections::BTreeSet, vec, vec::Vec};
3use s2json::{
4    BBox3D, Face, MValue, STPoint, VectorGeometry, VectorGeometryType, VectorLineString,
5    VectorLineStringGeometry, VectorMultiLineStringGeometry, VectorMultiPointGeometry,
6    VectorMultiPolygonGeometry, VectorPoint, VectorPointGeometry, VectorPolygon,
7    VectorPolygonGeometry,
8};
9
10/// The resultant geometry after conversion
11#[derive(Debug)]
12pub struct ConvertedGeometry<M: Clone + Default = MValue> {
13    /// The converted geometry
14    pub geometry: VectorGeometry<M>,
15    /// The face of the geometry
16    pub face: Face,
17}
18/// A list of converted geometries
19pub type ConvertedGeometryList<M> = Vec<ConvertedGeometry<M>>;
20
21// TODO: We may be able to optimize clones. Do we just take ownership with mutables?
22
23/// Underlying conversion mechanic to move GeoJSON Geometry to S2Geometry
24pub fn convert_geometry_wm_to_s2<M: Clone + Default>(
25    geometry: &VectorGeometry<M>,
26) -> ConvertedGeometryList<M> {
27    let mut res: ConvertedGeometryList<M> = vec![];
28
29    match geometry {
30        VectorGeometry::Point(geo) => {
31            res.extend(convert_geometry_point(geo));
32        }
33        VectorGeometry::MultiPoint(geo) => {
34            res.extend(convert_geometry_multipoint(geo));
35        }
36        VectorGeometry::LineString(geo) => {
37            res.extend(convert_geometry_linestring(geo));
38        }
39        VectorGeometry::MultiLineString(geo) => {
40            res.extend(convert_geometry_multilinestring(geo));
41        }
42        VectorGeometry::Polygon(geo) => {
43            res.extend(convert_geometry_polygon(geo));
44        }
45        VectorGeometry::MultiPolygon(geo) => {
46            res.extend(convert_geometry_multipolygon(geo));
47        }
48    }
49
50    res
51}
52
53/// Convert a GeoJSON PointGeometry to a S2 PointGeometry
54fn convert_geometry_point<M: Clone + Default>(
55    geometry: &VectorPointGeometry<M>,
56) -> ConvertedGeometryList<M> {
57    let VectorPointGeometry::<M> { _type, is_3d, coordinates, bbox, .. } = geometry;
58    let mut new_point = coordinates.clone();
59    let ll: S2Point = (&LonLat::<M>::new(new_point.x, new_point.y, None)).into();
60    let (face, s, t) = ll.to_face_st();
61    new_point.x = s;
62    new_point.y = t;
63    let vec_bbox = Some(BBox3D::from_point(&new_point));
64    vec![ConvertedGeometry {
65        face: face.into(),
66        geometry: VectorGeometry::Point(VectorPointGeometry {
67            _type: VectorGeometryType::Point,
68            coordinates: new_point,
69            is_3d: *is_3d,
70            bbox: *bbox,
71            vec_bbox,
72            ..Default::default()
73        }),
74    }]
75}
76
77/// Convert a GeoJSON MultiPointGeometry to S2 MultiPointGeometry
78fn convert_geometry_multipoint<M: Clone + Default>(
79    geometry: &VectorMultiPointGeometry<M>,
80) -> ConvertedGeometryList<M> {
81    let VectorMultiPointGeometry { is_3d, coordinates, bbox, .. } = geometry;
82    coordinates
83        .iter()
84        .flat_map(|coordinates| {
85            convert_geometry_point(&VectorPointGeometry {
86                _type: VectorGeometryType::Point,
87                is_3d: *is_3d,
88                coordinates: coordinates.clone(),
89                bbox: *bbox,
90                ..Default::default()
91            })
92        })
93        .collect()
94}
95
96/// Convert a GeoJSON LineStringGeometry to S2 LineStringGeometry
97fn convert_geometry_linestring<M: Clone + Default>(
98    geometry: &VectorLineStringGeometry<M>,
99) -> ConvertedGeometryList<M> {
100    let VectorLineStringGeometry { _type, is_3d, coordinates, bbox, .. } = geometry;
101
102    convert_line_string(coordinates, false)
103        .into_iter()
104        .map(|cline| {
105            let ConvertedLineString { face, mut line, offset, vec_bbox } = cline;
106            ConvertedGeometry {
107                face,
108                geometry: VectorGeometry::LineString(VectorLineStringGeometry {
109                    _type: VectorGeometryType::LineString,
110                    is_3d: *is_3d,
111                    coordinates: core::mem::take(&mut line),
112                    bbox: *bbox,
113                    offset: Some(offset),
114                    vec_bbox: Some(vec_bbox),
115                    ..Default::default()
116                }),
117            }
118        })
119        .collect()
120}
121
122/// Convert a GeoJSON MultiLineStringGeometry to S2 MultiLineStringGeometry
123fn convert_geometry_multilinestring<M: Clone + Default>(
124    geometry: &VectorMultiLineStringGeometry<M>,
125) -> ConvertedGeometryList<M> {
126    let VectorMultiLineStringGeometry { is_3d, coordinates, bbox, .. } = geometry;
127
128    coordinates
129        .iter()
130        .flat_map(|line| convert_line_string(line, false))
131        .map(|ConvertedLineString { face, line, offset, vec_bbox }| ConvertedGeometry {
132            face,
133            geometry: VectorGeometry::LineString(VectorLineStringGeometry {
134                _type: VectorGeometryType::LineString,
135                is_3d: *is_3d,
136                coordinates: line,
137                bbox: *bbox,
138                offset: Some(offset),
139                vec_bbox: Some(vec_bbox),
140                ..Default::default()
141            }),
142        })
143        .collect()
144}
145
146/// Convert a GeoJSON PolygonGeometry to S2 PolygonGeometry
147fn convert_geometry_polygon<M: Clone + Default>(
148    geometry: &VectorPolygonGeometry<M>,
149) -> ConvertedGeometryList<M> {
150    let VectorPolygonGeometry { _type, is_3d, coordinates, bbox, .. } = geometry;
151    let mut res: ConvertedGeometryList<M> = vec![];
152
153    // conver all lines
154    let mut outer_ring = convert_line_string(&coordinates[0], true);
155    let mut inner_rings = coordinates[1..].iter().flat_map(|line| convert_line_string(line, true));
156
157    // for each face, build a new polygon
158    for ConvertedLineString { face, line, offset, vec_bbox: poly_bbox } in &mut outer_ring {
159        let mut polygon: VectorPolygon<M> = vec![core::mem::take(line)];
160        let mut polygon_offsets = vec![*offset];
161        let mut poly_bbox = *poly_bbox;
162        for ConvertedLineString {
163            face: inner_face,
164            line: inner_line,
165            offset: inner_offset,
166            vec_bbox,
167        } in &mut inner_rings
168        {
169            if inner_face == *face {
170                polygon.push(inner_line);
171                polygon_offsets.push(inner_offset);
172                poly_bbox.merge_in_place(&vec_bbox);
173            }
174        }
175
176        res.push(ConvertedGeometry {
177            face: *face,
178            geometry: VectorGeometry::Polygon(VectorPolygonGeometry {
179                _type: VectorGeometryType::Polygon,
180                is_3d: *is_3d,
181                coordinates: polygon,
182                bbox: *bbox,
183                offset: Some(polygon_offsets),
184                vec_bbox: Some(poly_bbox),
185                ..Default::default()
186            }),
187        });
188    }
189
190    res
191}
192
193/// Convert a GeoJSON MultiPolygonGeometry to S2 MultiPolygonGeometry
194fn convert_geometry_multipolygon<M: Clone + Default>(
195    geometry: &VectorMultiPolygonGeometry<M>,
196) -> ConvertedGeometryList<M> {
197    let VectorMultiPolygonGeometry { is_3d, coordinates, bbox, offset, .. } = geometry;
198    coordinates
199        .iter()
200        .enumerate()
201        .flat_map(|(i, polygon)| {
202            let offset: Option<Vec<f64>> = offset.as_ref().map(|offset| offset[i].clone());
203            convert_geometry_polygon(&VectorPolygonGeometry {
204                _type: VectorGeometryType::Polygon,
205                is_3d: *is_3d,
206                coordinates: polygon.to_vec(),
207                bbox: *bbox,
208                offset,
209                ..Default::default()
210            })
211        })
212        .collect()
213}
214
215/// LineString converted from WM to S2
216pub struct ConvertedLineString<M: Clone + Default = MValue> {
217    face: Face,
218    line: VectorLineString<M>,
219    offset: f64,
220    vec_bbox: BBox3D,
221}
222
223/// Convert WM LineString to S2
224fn convert_line_string<M: Clone + Default>(
225    line: &VectorLineString<M>,
226    is_polygon: bool,
227) -> Vec<ConvertedLineString<M>> {
228    let mut res: Vec<ConvertedLineString<M>> = vec![];
229    // find all the faces that exist in the line while we re-project
230    let mut faces = BTreeSet::<Face>::new();
231    // first re-project all the coordinates to S2
232    let mut new_geometry: Vec<STPoint<M>> = vec![];
233    for VectorPoint { x: lon, y: lat, z, m, .. } in line {
234        let ll: S2Point = (&LonLat::<M>::new(*lon, *lat, None)).into();
235        let (face, s, t) = ll.to_face_st();
236        let stpoint = STPoint { face: face.into(), s, t, z: *z, m: m.clone() };
237        faces.insert(stpoint.face);
238        new_geometry.push(stpoint);
239    }
240    // for each face, build a line
241    for face in faces {
242        let mut line: VectorLineString<M> = vec![];
243        for st_point in &mut new_geometry {
244            line.push(st_point_to_face(face, st_point));
245        }
246        let clipped_lines =
247            clip_line(&line, BBox3D::new(0., 0., 1., 1., 0., 1.), is_polygon, None, None);
248        for ClipLineResultWithBBox { line, offset, vec_bbox } in clipped_lines {
249            res.push(ConvertedLineString { face, line, offset, vec_bbox });
250        }
251    }
252
253    res
254}
255
256/// Given a face, rotate the point into it's 0->1 coordinate system
257fn st_point_to_face<M: Clone + Default>(target_face: Face, stp: &mut STPoint<M>) -> VectorPoint<M> {
258    let cur_face = stp.face;
259    if target_face == cur_face {
260        return VectorPoint {
261            x: stp.s,
262            y: stp.t,
263            z: stp.z,
264            m: core::mem::take(&mut stp.m),
265            t: None,
266        };
267    }
268
269    let (rot, x, y) = &FACE_RULE_SET[target_face as usize][cur_face as usize];
270    let (new_s, new_t) = rotate(*rot, stp.s, stp.t);
271
272    VectorPoint {
273        x: new_s + *x as f64,
274        y: new_t + *y as f64,
275        z: stp.z,
276        m: core::mem::take(&mut stp.m),
277        t: None,
278    }
279}
280
281/// Rotate a point
282///
283/// ## Parameters
284/// - `rot`: rotation
285/// - `s`: input s
286/// - `t`: input t
287///
288/// ## Returns
289/// new `(s, t)` after rotating
290fn rotate(rot: Rotation, s: f64, t: f64) -> (f64, f64) {
291    match rot {
292        Rotation::_0 => (s, t),
293        Rotation::_90 => (t, 1. - s),
294        Rotation::_Neg90 => (1. - t, s),
295    }
296}
297
298#[derive(Debug, PartialEq, Copy, Clone)]
299/// Track the rotation of a face
300pub enum Rotation {
301    /// No rotation
302    _0,
303    /// Rotate 90 degrees
304    _90,
305    /// Rotate -90 degrees
306    _Neg90,
307}
308
309/// Ruleset for converting an S2Point from a face to another.
310/// While this this set includes opposite side faces, without axis mirroring,
311/// it is not technically accurate and shouldn't be used. Instead, data should let two points travel
312/// further than a full face width.
313/// `FACE_RULE_SET[target_face][currentFace] = [rot, x, y]`
314pub const FACE_RULE_SET: [[(Rotation, i8, i8); 6]; 6] = [
315    // Target Face 0
316    [
317        (Rotation::_0, 0, 0),      // Current Face 0
318        (Rotation::_0, 1, 0),      // Current Face 1
319        (Rotation::_90, 0, 1),     // Current Face 2
320        (Rotation::_Neg90, 2, 0),  // Current Face 3
321        (Rotation::_Neg90, -1, 0), //  Current Face 4
322        (Rotation::_0, 0, -1),     //  Current Face 5
323    ],
324    // Target Face 1
325    [
326        (Rotation::_0, -1, 0),    // Current Face 0
327        (Rotation::_0, 0, 0),     // Current Face 1
328        (Rotation::_0, 0, 1),     // Current Face 2
329        (Rotation::_Neg90, 1, 0), // Current Face 3
330        (Rotation::_Neg90, 2, 0), // Current Face 4
331        (Rotation::_90, 0, -1),   // Current Face 5
332    ],
333    // Target Face 2
334    [
335        (Rotation::_Neg90, -1, 0), // Current Face 0
336        (Rotation::_0, 0, -1),     // Current Face 1
337        (Rotation::_0, 0, 0),      // Current Face 2
338        (Rotation::_0, 1, 0),      // Current Face 3
339        (Rotation::_90, 0, 1),     // Current Face 4
340        (Rotation::_Neg90, 2, 0),  // Current Face 5
341    ],
342    // Target Face 3
343    [
344        (Rotation::_Neg90, 2, 0), // Current Face 0
345        (Rotation::_90, 0, -1),   // Current Face 1
346        (Rotation::_0, -1, 0),    // Current Face 2
347        (Rotation::_0, 0, 0),     // Current Face 3
348        (Rotation::_0, 0, 1),     // Current Face 4
349        (Rotation::_Neg90, 1, 0), // Current Face 5
350    ],
351    // Target Face 4
352    [
353        (Rotation::_90, 0, 1),     // Current Face 0
354        (Rotation::_Neg90, 2, 0),  // Current Face 1
355        (Rotation::_Neg90, -1, 0), // Current Face 2
356        (Rotation::_0, 0, -1),     // Current Face 3
357        (Rotation::_0, 0, 0),      // Current Face 4
358        (Rotation::_0, 1, 0),      // Current Face 5
359    ],
360    // Target Face 5
361    [
362        (Rotation::_0, 0, 1),     // Current Face 0
363        (Rotation::_Neg90, 1, 0), // Current Face 1
364        (Rotation::_Neg90, 2, 0), // Current Face 2
365        (Rotation::_90, 0, -1),   // Current Face 3
366        (Rotation::_0, -1, 0),    // Current Face 4
367        (Rotation::_0, 0, 0),     // Current Face 5
368    ],
369];