geo_buffer/
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_buffer::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_buffer::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_buffer::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_buffer::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.24.1/geo/geometry/struct.Polygon.html
123//! [MultiPolygon module]: https://docs.rs/geo/0.24.1/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;
136mod vertex_queue;
137pub mod util;
138pub mod skeleton;
139
140#[doc(inline)]
141pub use util::{Coordinate, Ray};
142
143// Main functions in this module
144
145use geo_types::{Polygon, MultiPolygon, LineString};
146use skeleton::Skeleton;
147
148/// This function returns the buffered (multi-)polygon of the given polygon. This function creates a miter-joint-like corners around each convex vertex.
149/// 
150/// # Arguments
151/// 
152/// + `input_polygon`: `Polygon` to buffer.
153/// + `distance`: determine how distant from each edge of original polygon to each edge of the result polygon. The sign will be:
154///     - `+` to inflate (to add paddings, make bigger) the given polygon, and,
155///     - `-` to deflate (to add margins, make smaller) the given polygon.
156/// 
157/// # Example
158/// 
159/// ```
160/// use geo_buffer::buffer_polygon;
161/// use geo::{Polygon, MultiPolygon, LineString};
162///
163/// let p1 = Polygon::new(
164///     LineString::from(vec![(0., 0.), (1., 0.), (1., 1.), (0., 1.)]), vec![],
165/// );
166/// let p2: MultiPolygon = buffer_polygon(&p1, -0.2);
167///
168/// 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)]);
169/// assert_eq!(&expected_exterior, p2.0[0].exterior())
170///
171/// ```
172pub fn buffer_polygon(input_polygon: &Polygon, distance: f64) -> MultiPolygon{
173    buffer_multi_polygon(&MultiPolygon::new(vec![input_polygon.clone()]), distance)
174}
175
176/// This function returns the buffered (multi-)polygon of the given polygon, but creates a rounded corners around each convex vertex.
177/// Therefore, distance from each point on border of the buffered polygon to the closest points on the given polygon is (approximately) equal.
178/// Click 'Result' below to see how this function works.
179/// 
180/// # Arguments
181/// 
182/// + `input_polygon`: `Polygon` to buffer.
183/// + `distance`: determine how distant from each edge of original polygon to each edge of the result polygon. The sign will be:
184///     - `+` to inflate (to add paddings, make bigger) the given polygon, and,
185///     - `-` to deflate (to add margins, make smaller) the given polygon.
186/// 
187/// # Example
188/// 
189/// ```
190/// use geo_buffer::buffer_polygon;
191/// use geo::{Polygon, MultiPolygon, LineString};
192///
193/// let p1 = Polygon::new(
194///     LineString::from(vec![(0., 0.), (1., 0.), (1., 1.), (0., 1.)]), vec![],
195/// );
196/// let p2: MultiPolygon = buffer_polygon_rounded(&p1, 0.2);
197/// ```
198/// 
199/// <details>
200/// <summary style="cursor:pointer"> Result </summary>
201/// <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex5.svg" style="padding: 25px 30%;"/>
202/// </details>
203/// 
204pub fn buffer_polygon_rounded(input_polygon: &Polygon, distance: f64) -> MultiPolygon{
205    buffer_multi_polygon_rounded(&MultiPolygon::new(vec![input_polygon.clone()]), distance)
206}
207
208/// This function returns the buffered (multi-)polygon of the given multi-polygon. This function creates a miter-joint-like corners around each convex vertex.
209/// 
210/// # Arguments
211/// 
212/// + `input_multi_polygon`: `MultiPolygon` to buffer.
213/// + `distance`: determine how distant from each edge of original polygon to each edge of the result polygon. The sign will be:
214///     - `+` for to enlarge (to add paddings, make bigger) the given polygon, and,
215///     - `-` for to deflate (to add margins, make smaller) the given polygon
216/// 
217/// # Example
218/// 
219/// ```
220/// use geo_buffer::buffer_multi_polygon;
221/// use geo::{Polygon, MultiPolygon, LineString};
222///
223/// let p1 = Polygon::new(
224///     LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.)]), vec![],
225/// );
226/// let p2 = Polygon::new(
227///     LineString::from(vec![(3., 3.), (5., 3.), (5., 5.), (3., 5.)]), vec![],
228/// );
229/// let mp1 = MultiPolygon::new(vec![p1, p2]);
230/// let mp2 = buffer_multi_polygon(&mp1, 1.);
231/// let expected_exterior = LineString::from(vec![(-1., -1.), (3., -1.), (3., 2.), (6., 2.), (6., 6.), (2., 6.), (2., 3.), (-1., 3.), (-1., -1.)]);
232/// assert_eq!(&expected_exterior, mp2.0[0].exterior())
233///
234/// ```
235pub fn buffer_multi_polygon(input_multi_polygon: &MultiPolygon, distance: f64) -> MultiPolygon{
236    let orientation = if distance < 0. {true} else {false};
237    let offset_distance = f64::abs(distance);
238    let skel = Skeleton::skeleton_of_polygon_vector(&input_multi_polygon.0, orientation);
239    let vq = skel.get_vertex_queue(offset_distance);
240    skel.apply_vertex_queue(&vq, offset_distance)
241}
242
243/// This function returns the buffered (multi-)polygon of the given multi-polygon, but creates a rounded corners around each convex vertex.
244/// Therefore, distance from each point on border of the buffered polygon to the closest points on the given polygon is (approximately) equal.
245/// 
246/// Click 'Result' below to see how this function works.
247/// 
248/// # Arguments
249/// 
250/// + `input_multi_polygon`: `MultiPolygon` to buffer.
251/// + `distance`: determines how distant from each edge of original polygon to each edge of the result polygon. The sign will be:
252///     - `+` to inflate (to add paddings, make bigger) the given polygon, and,
253///     - `-` to deflate (to add margins, make smaller) the given polygon.
254/// 
255/// # Example
256/// 
257/// ```
258/// use geo_buffer::buffer_polygon;
259/// use geo::{Polygon, MultiPolygon, LineString};
260///
261/// let p1 = Polygon::new(
262///     LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.)]), vec![],
263/// );
264/// let p2 = Polygon::new(
265///     LineString::from(vec![(3., 3.), (5., 3.), (5., 5.), (3., 5.)]), vec![],
266/// );
267/// let mp1 = MultiPolygon::new(vec![p1, p2]);
268/// let mp2 = buffer_multi_polygon(&mp1, 1.);
269/// ```
270/// 
271/// <details>
272/// <summary style="cursor:pointer"> Result </summary>
273/// <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex6.svg" style="padding: 25px 30%;"/>
274/// </details>
275/// 
276pub fn buffer_multi_polygon_rounded(input_multi_polygon: &MultiPolygon, distance: f64) -> MultiPolygon{
277    let orientation = if distance < 0. {true} else {false};
278    let offset_distance = f64::abs(distance);
279    let skel = Skeleton::skeleton_of_polygon_vector(&input_multi_polygon.0, orientation);
280    let vq = skel.get_vertex_queue(offset_distance);
281    skel.apply_vertex_queue_rounded(&vq, offset_distance)
282}
283
284// pub fn skeleton_of_polygon(input_polygon: &Polygon, orientation: bool) -> Skeleton{
285//     Skeleton::skeleton_of_polygon(input_polygon, orientation)
286// }
287
288// pub fn skeleton_of_multi_polygon(input_multi_polygon: &MultiPolygon, orientation: bool) -> Skeleton{
289//     Skeleton::skeleton_of_polygon_vector(&input_multi_polygon.0, orientation)
290// }
291
292/// This function returns a set of `LineSting` which represents an instantiated straight skeleton of the given polygon.
293/// Each segment of the straight skeleton is represented as a single `LineString`, and the returned vector is a set of these `LineString`s.
294/// If either endpoints of a `LineString` is infinitely far from the other, then this `LineString` will be clipped to one which has shorter length.
295/// The order of these `LineString`s is arbitrary. (There is no gauranteed order on segments of the straight skeleton.)
296/// 
297/// Click 'Result' below to see how this function works.
298/// 
299/// # Arguments
300/// 
301/// + `input_polygon`: `Polygon` to get the straight skeleton.
302/// + `orientation`: determines the region where the straight skeleton created. The value of this `boolean` variable will be:
303///     * `true` to create the staright skeleton on the inward region of the polygon, and,
304///     * `false` to create on the outward region of the polygon.
305/// 
306/// # Example
307/// 
308/// ```
309/// use geo_buffer::buffer_polygon;
310/// use geo::{Polygon, MultiPolygon, LineString};
311///
312/// let p1 = Polygon::new(
313///     LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.)]), vec![],
314/// );
315/// let ls1: Vec<LineString> = skeleton_of_polygon_to_linestring(&p1, true);
316/// ```
317/// 
318/// <details>
319/// <summary style="cursor:pointer"> Result </summary>
320/// <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex7.svg" style="padding: 25px 30%;"/>
321/// </details>
322/// 
323pub fn skeleton_of_polygon_to_linestring(input_polygon: &Polygon, orientation: bool) -> Vec<LineString>{
324    Skeleton::skeleton_of_polygon(input_polygon, orientation).to_linestring()
325}
326
327/// This function returns a set of `LineSting` which represents an instantiated straight skeleton of the given multi-polygon.
328/// Each segment of the straight skeleton is represented as a single `LineString`, and the returned vector is a set of these `LineString`s.
329/// If either endpoints of a `LineString` is infinitely far from the other, then this `LineString` will be clipped to one which has shorter length.
330/// The order of these `LineString`s is arbitrary. (There is no gauranteed order on segments of the straight skeleton.)
331/// 
332/// Click 'Result' below to see how this function works.
333/// 
334/// # Arguments
335/// 
336/// + `input_multi_polygon`: `MultiPolygon` to get the straight skeleton.
337/// + `orientation`: determines the region where the straight skeleton created. The value of this `boolean` variable will be:
338///     * `true` to create the staright skeleton on the inward region of the polygon, and,
339///     * `false` to create on the outward region of the polygon.
340/// 
341/// # Example
342/// 
343/// ```
344/// use geo_buffer::buffer_polygon;
345/// use geo::{Polygon, MultiPolygon, LineString};
346///
347/// let p1 = Polygon::new(
348///     LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.)]), vec![],
349/// );
350/// let p2 = Polygon::new(
351///     LineString::from(vec![(3., 3.), (5., 3.), (5., 5.), (3., 5.)]), vec![],
352/// );
353/// let mp1 = MultiPolygon::new(vec![p1, p2]);
354/// let ls: Vec<LineString> = skeleton_of_multi_polygon_to_linestring(&mp1, false);
355/// ```
356/// 
357/// <details>
358/// <summary style="cursor:pointer"> Result </summary>
359/// <img src="https://raw.githubusercontent.com/1011-git/geo-buffer/main/assets/ex8.svg" style="padding: 25px 30%;"/>
360/// </details>
361/// 
362pub fn skeleton_of_multi_polygon_to_linestring(input_multi_polygon: &MultiPolygon, orientation: bool) -> Vec<LineString>{
363    Skeleton::skeleton_of_polygon_vector(&input_multi_polygon.0, orientation).to_linestring()
364}
365
366#[cfg(test)]
367mod tests;
368