geo_offset/
offset.rs

1use super::*;
2use geo_clipper::Clipper;
3use geo_types::CoordFloat;
4
5/// If offset computing fails this error is returned.
6#[derive(Debug, Copy, Clone, PartialEq)]
7pub enum OffsetError {
8    /// This error can be produced when manipulating edges.
9    EdgeError(EdgeError),
10}
11
12/// Arcs around corners are made of 5 segments by default.
13pub const DEFAULT_ARC_SEGMENTS: u32 = 5;
14
15pub trait Offset<F: CoordFloat> {
16    fn offset(&self, distance: F) -> Result<geo_types::MultiPolygon<F>, OffsetError> {
17        self.offset_with_arc_segments(distance, DEFAULT_ARC_SEGMENTS)
18    }
19
20    fn offset_with_arc_segments(
21        &self,
22        distance: F,
23        arc_segments: u32,
24    ) -> Result<geo_types::MultiPolygon<F>, OffsetError>;
25}
26
27impl<F: CoordFloat> Offset<F> for geo_types::GeometryCollection<F> {
28    fn offset_with_arc_segments(
29        &self,
30        distance: F,
31        arc_segments: u32,
32    ) -> Result<geo_types::MultiPolygon<F>, OffsetError> {
33        let mut geometry_collection_with_offset = geo_types::MultiPolygon::<F>(Vec::new());
34        for geometry in self.0.iter() {
35            let geometry_with_offset = geometry.offset_with_arc_segments(distance, arc_segments)?;
36            geometry_collection_with_offset = geometry_collection_with_offset
37                .union(&geometry_with_offset, F::from(1000.0).unwrap());
38        }
39        Ok(geometry_collection_with_offset)
40    }
41}
42
43impl<F: CoordFloat> Offset<F> for geo_types::Geometry<F> {
44    fn offset_with_arc_segments(
45        &self,
46        distance: F,
47        arc_segments: u32,
48    ) -> Result<geo_types::MultiPolygon<F>, OffsetError> {
49        match self {
50            geo_types::Geometry::Point(point) => {
51                point.offset_with_arc_segments(distance, arc_segments)
52            }
53            geo_types::Geometry::Line(line) => {
54                line.offset_with_arc_segments(distance, arc_segments)
55            }
56            geo_types::Geometry::LineString(line_tring) => {
57                line_tring.offset_with_arc_segments(distance, arc_segments)
58            }
59            geo_types::Geometry::Triangle(triangle) => triangle
60                .to_polygon()
61                .offset_with_arc_segments(distance, arc_segments),
62            geo_types::Geometry::Rect(rect) => rect
63                .to_polygon()
64                .offset_with_arc_segments(distance, arc_segments),
65            geo_types::Geometry::Polygon(polygon) => {
66                polygon.offset_with_arc_segments(distance, arc_segments)
67            }
68            geo_types::Geometry::MultiPoint(multi_point) => {
69                multi_point.offset_with_arc_segments(distance, arc_segments)
70            }
71            geo_types::Geometry::MultiLineString(multi_line_string) => {
72                multi_line_string.offset_with_arc_segments(distance, arc_segments)
73            }
74            geo_types::Geometry::MultiPolygon(multi_polygon) => {
75                multi_polygon.offset_with_arc_segments(distance, arc_segments)
76            }
77            geo_types::Geometry::GeometryCollection(geometry_collection) => {
78                geometry_collection.offset_with_arc_segments(distance, arc_segments)
79            }
80        }
81    }
82}
83
84impl<F: CoordFloat> Offset<F> for geo_types::MultiPolygon<F> {
85    fn offset_with_arc_segments(
86        &self,
87        distance: F,
88        arc_segments: u32,
89    ) -> Result<geo_types::MultiPolygon<F>, OffsetError> {
90        let mut polygons = geo_types::MultiPolygon::<F>(Vec::new());
91        for polygon in self.0.iter() {
92            let polygon_with_offset = polygon.offset_with_arc_segments(distance, arc_segments)?;
93            polygons = polygons.union(&polygon_with_offset, F::from(1000.0).unwrap());
94        }
95        Ok(polygons)
96    }
97}
98
99impl<F: CoordFloat> Offset<F> for geo_types::Polygon<F> {
100    fn offset_with_arc_segments(
101        &self,
102        distance: F,
103        arc_segments: u32,
104    ) -> Result<geo_types::MultiPolygon<F>, OffsetError> {
105        let exterior_with_offset = self
106            .exterior()
107            .offset_with_arc_segments(distance.abs(), arc_segments)?;
108        let interiors_with_offset = geo_types::MultiLineString::<F>(self.interiors().to_vec())
109            .offset_with_arc_segments(distance.abs(), arc_segments)?;
110
111        Ok(if distance.is_sign_positive() {
112            self.union(&exterior_with_offset, F::from(1000.0).unwrap())
113                .union(&interiors_with_offset, F::from(1000.0).unwrap())
114        } else {
115            self.difference(&exterior_with_offset, F::from(1000.0).unwrap())
116                .difference(&interiors_with_offset, F::from(1000.0).unwrap())
117        })
118    }
119}
120
121impl<F: CoordFloat> Offset<F> for geo_types::MultiLineString<F> {
122    fn offset_with_arc_segments(
123        &self,
124        distance: F,
125        arc_segments: u32,
126    ) -> Result<geo_types::MultiPolygon<F>, OffsetError> {
127        if distance < F::zero() {
128            return Ok(geo_types::MultiPolygon(Vec::new()));
129        }
130
131        let mut multi_line_string_with_offset = geo_types::MultiPolygon::<F>(Vec::new());
132        for line_string in self.0.iter() {
133            let line_string_with_offset =
134                line_string.offset_with_arc_segments(distance, arc_segments)?;
135            multi_line_string_with_offset = multi_line_string_with_offset
136                .union(&line_string_with_offset, F::from(1000.0).unwrap());
137        }
138        Ok(multi_line_string_with_offset)
139    }
140}
141
142impl<F: CoordFloat> Offset<F> for geo_types::LineString<F> {
143    fn offset_with_arc_segments(
144        &self,
145        distance: F,
146        arc_segments: u32,
147    ) -> Result<geo_types::MultiPolygon<F>, OffsetError> {
148        if distance < F::zero() {
149            return Ok(geo_types::MultiPolygon(Vec::new()));
150        }
151
152        let mut line_string_with_offset = geo_types::MultiPolygon::<F>(Vec::new());
153        for line in self.lines() {
154            let line_with_offset = line.offset_with_arc_segments(distance, arc_segments)?;
155            line_string_with_offset =
156                line_string_with_offset.union(&line_with_offset, F::from(1000.0).unwrap());
157        }
158
159        let line_string_with_offset = line_string_with_offset.0.iter().skip(1).fold(
160            geo_types::MultiPolygon::<F>(
161                line_string_with_offset
162                    .0
163                    .get(0)
164                    .map(|polygon| vec![polygon.clone()])
165                    .unwrap_or_default(),
166            ),
167            |result, hole| result.difference(hole, F::from(1000.0).unwrap()),
168        );
169
170        Ok(line_string_with_offset)
171    }
172}
173
174impl<F: CoordFloat> Offset<F> for geo_types::Line<F> {
175    fn offset_with_arc_segments(
176        &self,
177        distance: F,
178        arc_segments: u32,
179    ) -> Result<geo_types::MultiPolygon<F>, OffsetError> {
180        if distance < F::zero() {
181            return Ok(geo_types::MultiPolygon(Vec::new()));
182        }
183
184        let v1 = &self.start;
185        let v2 = &self.end;
186        let e1 = Edge::new(v1, v2);
187
188        if let (Ok(in_normal), Ok(out_normal)) = (e1.inwards_normal(), e1.outwards_normal()) {
189            let offsets = [
190                e1.with_offset(in_normal.x * distance, in_normal.y * distance),
191                e1.inverse_with_offset(out_normal.x * distance, out_normal.y * distance),
192            ];
193
194            let len = 2;
195            let mut vertices = Vec::new();
196
197            for i in 0..len {
198                let current_edge = offsets.get(i).unwrap();
199                let prev_edge = offsets.get((i + len + 1) % len).unwrap();
200                create_arc(
201                    &mut vertices,
202                    if i == 0 { v1 } else { v2 },
203                    distance,
204                    &prev_edge.next,
205                    &current_edge.current,
206                    arc_segments,
207                    true,
208                );
209            }
210
211            Ok(geo_types::MultiPolygon(vec![geo_types::Polygon::new(
212                geo_types::LineString(vertices),
213                vec![],
214            )]))
215        } else {
216            geo_types::Point::from(self.start).offset_with_arc_segments(distance, arc_segments)
217        }
218    }
219}
220
221impl<F: CoordFloat> Offset<F> for geo_types::MultiPoint<F> {
222    fn offset_with_arc_segments(
223        &self,
224        distance: F,
225        arc_segments: u32,
226    ) -> Result<geo_types::MultiPolygon<F>, OffsetError> {
227        if distance < F::zero() {
228            return Ok(geo_types::MultiPolygon(Vec::new()));
229        }
230
231        let mut multi_point_with_offset = geo_types::MultiPolygon::<F>(Vec::new());
232        for point in self.0.iter() {
233            let point_with_offset = point.offset_with_arc_segments(distance, arc_segments)?;
234            multi_point_with_offset =
235                multi_point_with_offset.union(&point_with_offset, F::from(1000.0).unwrap());
236        }
237        Ok(multi_point_with_offset)
238    }
239}
240
241impl<F: CoordFloat> Offset<F> for geo_types::Point<F> {
242    fn offset_with_arc_segments(
243        &self,
244        distance: F,
245        arc_segments: u32,
246    ) -> Result<geo_types::MultiPolygon<F>, OffsetError> {
247        if distance < F::zero() {
248            return Ok(geo_types::MultiPolygon(Vec::new()));
249        }
250
251        let mut angle = F::zero();
252
253        let vertice_count = match arc_segments * 2 {
254            count if count % 2 == 0 => count + 1,
255            count => count,
256        };
257
258        let contour = (0..vertice_count)
259            .map(|_| {
260                angle =
261                    angle + F::from(2.0 * std::f64::consts::PI / f64::from(vertice_count)).unwrap(); // counter-clockwise
262
263                geo_types::Coord::from((
264                    self.x() + (distance * angle.cos()),
265                    self.y() + (distance * angle.sin()),
266                ))
267            })
268            .collect();
269
270        Ok(geo_types::MultiPolygon(vec![geo_types::Polygon::new(
271            contour,
272            Vec::new(),
273        )]))
274    }
275}
276
277fn create_arc<F: CoordFloat>(
278    vertices: &mut Vec<geo_types::Coord<F>>,
279    center: &geo_types::Coord<F>,
280    radius: F,
281    start_vertex: &geo_types::Coord<F>,
282    end_vertex: &geo_types::Coord<F>,
283    segment_count: u32,
284    outwards: bool,
285) {
286    let pi2 = F::from(std::f64::consts::PI * 2.0).unwrap();
287
288    let start_angle = (start_vertex.y - center.y).atan2(start_vertex.x - center.x);
289    let start_angle = if start_angle.is_sign_negative() {
290        start_angle + pi2
291    } else {
292        start_angle
293    };
294
295    let end_angle = (end_vertex.y - center.y).atan2(end_vertex.x - center.x);
296    let end_angle = if end_angle.is_sign_negative() {
297        end_angle + pi2
298    } else {
299        end_angle
300    };
301
302    let segment_count = if segment_count % 2 == 0 {
303        segment_count - 1
304    } else {
305        segment_count
306    };
307
308    let angle = if start_angle > end_angle {
309        start_angle - end_angle
310    } else {
311        start_angle + pi2 - end_angle
312    };
313
314    let segment_angle =
315        if outwards { -angle } else { pi2 - angle } / F::from(segment_count).unwrap();
316
317    vertices.push(*start_vertex);
318    for i in 1..segment_count {
319        let angle = start_angle + segment_angle * F::from(i).unwrap();
320        vertices.push(geo_types::Coord::from((
321            center.x + angle.cos() * radius,
322            center.y + angle.sin() * radius,
323        )));
324    }
325    vertices.push(*end_vertex);
326}