geo_buf/lib.rs
1//! The `geo-buffer` crate provides methods to buffer (to inflate or deflate) certain
2//! primitive geometric types in the [GeoRust] ecosystem via a straight skeleton.
3//!
4//! This crate can handle simple polygons properly as well as non-convex polygons, (valid) sets of polygons, and polygons with one or more holes.
5//! Note that each method assumes **valid** primitives as a parameter, but [Polygon][Polygon module]/[MultiPolygon][MultiPolygon module] modules
6//! *do not* enforce this validity automatically nor does this crate. (See more details on 'Validity' section in [Polygon][Polygon module]/[MultiPolygon][MultiPolygon module]
7//! and [OGC standards].)
8//!
9//! This crate use a [straight skeleton] to buffer (multi-)polygons. You can also get a straight skeleton separately by proper methods.
10//!
11//! For now, the only viable geometric primitives are [Polygon][Polygon module] and [MultiPolygon][MultiPolygon module] (the rest of the primitives will be added as well).
12//!
13//! # Quick Guide
14//!
15//! The `buffer_polygon()` function (resp. `buffer_multi_polygon()` function) produces a `MultiPolygon` after applying
16//! an offset operation to the given `Polygon` (resp. `MultiPolygon`). The absolute value of the argument passed with
17//! determines the distance between each edge of the result multi-polygon and the original input. The sign determines the direction
18//! where the result expands. Positive values mean it going outward --- that is, it inflates, --- and negative values mean going inward
19//! --- it deflates ---.
20//!
21//! Each code snippets below is a brief guide to use this crate. Click 'Result' to expand the visualized result.
22//! (The red polygon designates the input, and the orange one designates the results.)
23//!
24//! ### Example 1
25//!
26//! You can manipulate a polygon with ease by a single function call.
27//!
28//! ```
29//! use geo_buf::buffer_polygon;
30//! use geo::{Polygon, MultiPolygon, LineString};
31//!
32//! let p1 = Polygon::new(
33//! LineString::from(vec![(0., 0.), (1., 0.), (1., 1.), (0., 1.)]), vec![],
34//! );
35//! let p2: MultiPolygon = buffer_polygon(&p1, -0.2);
36//!
37//! let expected_exterior = LineString::from(vec![(0.2, 0.2), (0.8, 0.2), (0.8, 0.8), (0.2, 0.8), (0.2, 0.2)]);
38//! assert_eq!(&expected_exterior, p2.0[0].exterior())
39//!
40//! ```
41//! <details>
42//! <summary style="cursor:pointer"> Result </summary>
43//! <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex1.svg" style="padding: 25px 30%;"/>
44//! </details>
45//!
46//! ### Example 2
47//!
48//! This example shows the case where the polygon is split while it deflates.
49//!
50//! ```
51//! use geo_buf::buffer_polygon;
52//! use geo::{Polygon, MultiPolygon, LineString};
53//!
54//! let p1 = Polygon::new(
55//! LineString::from(vec![(0., 0.), (4., 0.), (4., 4.), (2., 1.), (0., 4.)]), vec![],
56//! );
57//! let p2: MultiPolygon = buffer_polygon(&p1, -0.45);
58//!
59//! ```
60//! <details>
61//! <summary style="cursor:pointer"> Result </summary>
62//! <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex2.svg" style="padding: 25px 30%;"/>
63//! </details>
64//!
65//! ### Example 3
66//!
67//! You can apply this function to a set of `Polygon`s (i.e. `MultiPolygon`). The constituent polygons may be integrated while they expand.
68//!
69//! ```
70//! use geo_buf::buffer_multi_polygon;
71//! use geo::{Polygon, MultiPolygon, LineString};
72//!
73//! let p1 = Polygon::new(
74//! LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.)]), vec![],
75//! );
76//! let p2 = Polygon::new(
77//! LineString::from(vec![(3., 3.), (5., 3.), (5., 5.), (3., 5.)]), vec![],
78//! );
79//! let mp1 = MultiPolygon::new(vec![p1, p2]);
80//! let mp2 = buffer_multi_polygon(&mp1, 0.9);
81//!
82//! ```
83//! <details>
84//! <summary style="cursor:pointer"> Result </summary>
85//! <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex3.svg" style="padding: 25px 30%;"/>
86//! </details>
87//!
88//! ### Example 4
89//!
90//! If you want to apply this function to each member (and not want to unify them), just traversing over an iterator and collecting them will be fine.
91//! (You can get a vector of `MultiPolygon`s thanks to the 'turbofish' syntax:`::<>`.)
92//!
93//! ```
94//! use geo_buf::buffer_polygon;
95//! use geo::{Polygon, MultiPolygon, LineString};
96//!
97//! let p1 = Polygon::new(
98//! LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.)]), vec![],
99//! );
100//! let p2 = Polygon::new(
101//! LineString::from(vec![(3., 3.), (5., 3.), (5., 5.), (3., 5.)]), vec![],
102//! );
103//! let mp1 = MultiPolygon::new(vec![p1, p2]);
104//! let mp2 = mp1.0.iter().map(|x| buffer_polygon(x, 0.9)).collect::<Vec<_>>();
105//!
106//! ```
107//! <details>
108//! <summary style="cursor:pointer"> Result </summary>
109//! <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex4.svg" style="padding: 25px 30%;"/>
110//! </details>
111//!
112//! # Reference
113//!
114//! This is a Rust implementation of this paper[^note1][^note2]. (See also [Notes](#Notes) below.)
115//!
116//! # Notes
117//!
118//! It has been shown that the algorithm presented in this paper is incorrect.[^note3] Thus we slightly modified the algorithm for some edge cases.
119//!
120//!
121//! [GeoRust]: https://georust.org
122//! [Polygon module]: https://docs.rs/geo/0.29.3/geo/geometry/struct.Polygon.html
123//! [MultiPolygon module]: https://docs.rs/geo/0.29.3/geo/geometry/struct.MultiPolygon.html
124//! [OGC standards]: https://www.ogc.org/standard/sfa/
125//! [straight skeleton]: https://en.wikipedia.org/wiki/Straight_skeleton
126//! [^note1]: Felkel, Petr; Obdržálek, Štěpán (1998), *"Straight skeleton implementation"*, SCCG 98: Proceedings of the 14th Spring Conference on Computer Graphics, pp. 210–218.
127//!
128//! [^note2]: The implementation of the straight skeleton algorithm in CGAL (The Computational Geometry Algorithms Library) is also based on this paper.
129//!
130//! [^note3]: Huber, Stefan (2012), *Computing Straight Skeletons and Motorcycle Graphs: Theory and Practice*, Shaker Verlag.
131//!
132
133// Define submodules and re-exports
134
135mod priority_queue;
136pub mod skeleton;
137pub mod util;
138mod vertex_queue;
139
140use std::f64::consts::TAU;
141
142use geo::Point;
143#[doc(inline)]
144pub use util::{Coordinate, Ray};
145
146// Main functions in this module
147
148use geo_types::{LineString, MultiPolygon, Polygon};
149use skeleton::Skeleton;
150
151/// This function returns the buffered (multi-)polygon of the given polygon. This function creates a miter-joint-like corners around each convex vertex.
152///
153/// # Arguments
154///
155/// + `input_polygon`: `Polygon` to buffer.
156/// + `distance`: determine how distant from each edge of original polygon to each edge of the result polygon. The sign will be:
157/// - `+` to inflate (to add paddings, make bigger) the given polygon, and,
158/// - `-` to deflate (to add margins, make smaller) the given polygon.
159///
160/// # Example
161///
162/// ```
163/// use geo_buf::buffer_polygon;
164/// use geo::{Polygon, MultiPolygon, LineString};
165///
166/// let p1 = Polygon::new(
167/// LineString::from(vec![(0., 0.), (1., 0.), (1., 1.), (0., 1.)]), vec![],
168/// );
169/// let p2: MultiPolygon = buffer_polygon(&p1, -0.2);
170///
171/// let expected_exterior = LineString::from(vec![(0.2, 0.2), (0.8, 0.2), (0.8, 0.8), (0.2, 0.8), (0.2, 0.2)]);
172/// assert_eq!(&expected_exterior, p2.0[0].exterior())
173///
174/// ```
175pub fn buffer_polygon(input_polygon: &Polygon, distance: f64) -> MultiPolygon {
176 buffer_multi_polygon(&MultiPolygon::new(vec![input_polygon.clone()]), distance)
177}
178
179/// This function returns the buffered (multi-)polygon of the given polygon, but creates a rounded corners around each convex vertex.
180/// Therefore, distance from each point on border of the buffered polygon to the closest points on the given polygon is (approximately) equal.
181/// Click 'Result' below to see how this function works.
182///
183/// # Arguments
184///
185/// + `input_polygon`: `Polygon` to buffer.
186/// + `distance`: determine how distant from each edge of original polygon to each edge of the result polygon. The sign will be:
187/// - `+` to inflate (to add paddings, make bigger) the given polygon, and,
188/// - `-` to deflate (to add margins, make smaller) the given polygon.
189///
190/// # Example
191///
192/// ```
193/// use geo_buf::{buffer_polygon, buffer_polygon_rounded};
194/// use geo::{Polygon, MultiPolygon, LineString};
195///
196/// let p1 = Polygon::new(
197/// LineString::from(vec![(0., 0.), (1., 0.), (1., 1.), (0., 1.)]), vec![],
198/// );
199/// let p2: MultiPolygon = buffer_polygon_rounded(&p1, 0.2);
200/// ```
201///
202/// <details>
203/// <summary style="cursor:pointer"> Result </summary>
204/// <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex5.svg" style="padding: 25px 30%;"/>
205/// </details>
206///
207pub fn buffer_polygon_rounded(input_polygon: &Polygon, distance: f64) -> MultiPolygon {
208 buffer_multi_polygon_rounded(&MultiPolygon::new(vec![input_polygon.clone()]), distance)
209}
210
211/// This function returns the buffered (multi-)polygon of the given multi-polygon. This function creates a miter-joint-like corners around each convex vertex.
212///
213/// # Arguments
214///
215/// + `input_multi_polygon`: `MultiPolygon` to buffer.
216/// + `distance`: determine how distant from each edge of original polygon to each edge of the result polygon. The sign will be:
217/// - `+` for to enlarge (to add paddings, make bigger) the given polygon, and,
218/// - `-` for to deflate (to add margins, make smaller) the given polygon
219///
220/// # Example
221///
222/// ```
223/// use geo_buf::buffer_multi_polygon;
224/// use geo::{Polygon, MultiPolygon, LineString};
225///
226/// let p1 = Polygon::new(
227/// LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.)]), vec![],
228/// );
229/// let p2 = Polygon::new(
230/// LineString::from(vec![(3., 3.), (5., 3.), (5., 5.), (3., 5.)]), vec![],
231/// );
232/// let mp1 = MultiPolygon::new(vec![p1, p2]);
233/// let mp2 = buffer_multi_polygon(&mp1, 1.);
234/// let expected_exterior = LineString::from(vec![(-1., -1.), (3., -1.), (3., 2.), (6., 2.), (6., 6.), (2., 6.), (2., 3.), (-1., 3.), (-1., -1.)]);
235/// assert_eq!(&expected_exterior, mp2.0[0].exterior())
236///
237/// ```
238pub fn buffer_multi_polygon(input_multi_polygon: &MultiPolygon, distance: f64) -> MultiPolygon {
239 let orientation = distance < 0.;
240 let offset_distance = f64::abs(distance);
241 let skel = Skeleton::skeleton_of_polygon_vector(&input_multi_polygon.0, orientation);
242 let vq = skel.get_vertex_queue(offset_distance);
243 skel.apply_vertex_queue(&vq, offset_distance)
244}
245
246/// This function returns the buffered (multi-)polygon of the given multi-polygon, but creates a rounded corners around each convex vertex.
247/// Therefore, distance from each point on border of the buffered polygon to the closest points on the given polygon is (approximately) equal.
248///
249/// Click 'Result' below to see how this function works.
250///
251/// # Arguments
252///
253/// + `input_multi_polygon`: `MultiPolygon` to buffer.
254/// + `distance`: determines how distant from each edge of original polygon to each edge of the result polygon. The sign will be:
255/// - `+` to inflate (to add paddings, make bigger) the given polygon, and,
256/// - `-` to deflate (to add margins, make smaller) the given polygon.
257///
258/// # Example
259///
260/// ```
261/// use geo_buf::{buffer_polygon,buffer_multi_polygon};
262/// use geo::{Polygon, MultiPolygon, LineString};
263///
264/// let p1 = Polygon::new(
265/// LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.)]), vec![],
266/// );
267/// let p2 = Polygon::new(
268/// LineString::from(vec![(3., 3.), (5., 3.), (5., 5.), (3., 5.)]), vec![],
269/// );
270/// let mp1 = MultiPolygon::new(vec![p1, p2]);
271/// let mp2 = buffer_multi_polygon(&mp1, 1.);
272/// ```
273///
274/// <details>
275/// <summary style="cursor:pointer"> Result </summary>
276/// <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex6.svg" style="padding: 25px 30%;"/>
277/// </details>
278///
279pub fn buffer_multi_polygon_rounded(
280 input_multi_polygon: &MultiPolygon,
281 distance: f64,
282) -> MultiPolygon {
283 let orientation = distance < 0.;
284 let offset_distance = f64::abs(distance);
285 let skel = Skeleton::skeleton_of_polygon_vector(&input_multi_polygon.0, orientation);
286 let vq = skel.get_vertex_queue(offset_distance);
287 skel.apply_vertex_queue_rounded(&vq, offset_distance)
288}
289
290// pub fn skeleton_of_polygon(input_polygon: &Polygon, orientation: bool) -> Skeleton{
291// Skeleton::skeleton_of_polygon(input_polygon, orientation)
292// }
293
294// pub fn skeleton_of_multi_polygon(input_multi_polygon: &MultiPolygon, orientation: bool) -> Skeleton{
295// Skeleton::skeleton_of_polygon_vector(&input_multi_polygon.0, orientation)
296// }
297
298/// This function returns a set of `LineSting` which represents an instantiated straight skeleton of the given polygon.
299/// Each segment of the straight skeleton is represented as a single `LineString`, and the returned vector is a set of these `LineString`s.
300/// If either endpoints of a `LineString` is infinitely far from the other, then this `LineString` will be clipped to one which has shorter length.
301/// The order of these `LineString`s is arbitrary. (There is no gauranteed order on segments of the straight skeleton.)
302///
303/// Click 'Result' below to see how this function works.
304///
305/// # Arguments
306///
307/// + `input_polygon`: `Polygon` to get the straight skeleton.
308/// + `orientation`: determines the region where the straight skeleton created. The value of this `boolean` variable will be:
309/// * `true` to create the staright skeleton on the inward region of the polygon, and,
310/// * `false` to create on the outward region of the polygon.
311///
312/// # Example
313///
314/// ```
315/// use geo_buf::buffer_polygon;
316/// use geo_buf::skeleton_of_polygon_to_linestring;
317/// use geo::{Polygon, MultiPolygon, LineString};
318///
319/// let p1 = Polygon::new(
320/// LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.)]), vec![],
321/// );
322/// let ls1: Vec<LineString> = skeleton_of_polygon_to_linestring(&p1, true);
323/// ```
324///
325/// <details>
326/// <summary style="cursor:pointer"> Result </summary>
327/// <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex7.svg" style="padding: 25px 30%;"/>
328/// </details>
329///
330pub fn skeleton_of_polygon_to_linestring(
331 input_polygon: &Polygon,
332 orientation: bool,
333) -> Vec<LineString> {
334 Skeleton::skeleton_of_polygon(input_polygon, orientation).to_linestring()
335}
336
337/// This function returns a set of `LineSting` which represents an instantiated straight skeleton of the given multi-polygon.
338/// Each segment of the straight skeleton is represented as a single `LineString`, and the returned vector is a set of these `LineString`s.
339/// If either endpoints of a `LineString` is infinitely far from the other, then this `LineString` will be clipped to one which has shorter length.
340/// The order of these `LineString`s is arbitrary. (There is no gauranteed order on segments of the straight skeleton.)
341///
342/// Click 'Result' below to see how this function works.
343///
344/// # Arguments
345///
346/// + `input_multi_polygon`: `MultiPolygon` to get the straight skeleton.
347/// + `orientation`: determines the region where the straight skeleton created. The value of this `boolean` variable will be:
348/// * `true` to create the staright skeleton on the inward region of the polygon, and,
349/// * `false` to create on the outward region of the polygon.
350///
351/// # Example
352///
353/// ```
354/// use geo_buf::{buffer_polygon, skeleton_of_multi_polygon_to_linestring};
355/// use geo::{Polygon, MultiPolygon, LineString};
356///
357/// let p1 = Polygon::new(
358/// LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.)]), vec![],
359/// );
360/// let p2 = Polygon::new(
361/// LineString::from(vec![(3., 3.), (5., 3.), (5., 5.), (3., 5.)]), vec![],
362/// );
363/// let mp1 = MultiPolygon::new(vec![p1, p2]);
364/// let ls: Vec<LineString> = skeleton_of_multi_polygon_to_linestring(&mp1, false);
365/// ```
366///
367/// <details>
368/// <summary style="cursor:pointer"> Result </summary>
369/// <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex8.svg" style="padding: 25px 30%;"/>
370/// </details>
371///
372pub fn skeleton_of_multi_polygon_to_linestring(
373 input_multi_polygon: &MultiPolygon,
374 orientation: bool,
375) -> Vec<LineString> {
376 Skeleton::skeleton_of_polygon_vector(&input_multi_polygon.0, orientation).to_linestring()
377}
378
379/// This function returns the buffered n-gon of the given point.
380///
381/// # Arguments
382///
383/// + `point`: `Point` to buffer.
384/// + `distance`: determines the distance from the original point to each edge of the resulting n-gon.
385/// + `resolution`: how many sides the resulting polygon will have (n of n-gon).
386///
387/// # Example
388///
389/// ```
390/// use geo_buf::buffer_point;
391/// use geo::{Point, Polygon, HasDimensions};
392///
393/// let p1 = Point::new(0.,0.);
394/// let buffered = buffer_point(&p1, 1., 12);
395///
396/// let neg_empty = buffer_point(&p1, -1., 12);
397/// assert!(neg_empty.is_empty())
398///
399/// ```
400pub fn buffer_point(point: &Point, distance: f64, resolution: usize) -> Polygon {
401 if distance < 0. {
402 return Polygon::new(LineString::new(vec![]), vec![]);
403 }
404 let mut coordinates: Vec<(f64, f64)> = Vec::with_capacity(resolution + 1);
405 for i in 0..=resolution {
406 let theta = i as f64 * TAU / resolution as f64;
407 let (sin, cos) = theta.sin_cos();
408 let dest_x = point.x() + distance * cos;
409 let dest_y = point.y() + distance * sin;
410
411 coordinates.push((dest_x, dest_y))
412 }
413 Polygon::new(LineString::from(coordinates), vec![])
414}