Smooth
Geometry smoothing is meant to make shapes more aesthetically pleasing, usually by rounding out rough edges and corners.
You can do this by the smooth
function, which uses the Chaikin
algorithm by default.
Example
@example
using CairoMakie
import GeoInterface as GI, GeometryOps as GO
line = GI.LineString([(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)])
smoothed = GO.smooth(line)
smoothed_2 = GO.smooth(line; iterations=2)
f, a, p = lines(line; label = "Original")
lines!(a, smoothed; label = "1 iteration")
lines!(a, smoothed_2; label = "2 iterations")
axislegend(a)
fig
Smoothing also works on the Spherical
manifold, similarly to the planar manifold (default):
@example
using CairoMakie
import GeoInterface as GI, GeometryOps as GO
line = GI.LineString([(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)])
smoothed = GO.smooth(GO.Spherical(), line) |> x -> GO.transform(GO.UnitSpherical.GeographicFromUnitSpherical(), x)
smoothed_2 = GO.smooth(GO.Spherical(), line; iterations=2) |> x -> GO.transform(GO.UnitSpherical.GeographicFromUnitSpherical(), x)
f, a, p = lines(line; label = "Original", axis = (; title = "Spherical smoothing"))
lines!(a, smoothed; label = "1 iteration")
lines!(a, smoothed_2; label = "2 iterations")
axislegend(a)
fig
julia
"""
Chaikin(; iterations=1, manifold=Planar())
Smooths geometries using Chaikin's corner-cutting algorithm [^1].
This algorithm "slices" off every corner of the geometry to smooth it out,
equivalent to a sequence of quadratic Bezier curves.
# Keywords
- `iterations`: the number of times to apply the algorithm.
- `manifold`: the `Manifold` to smooth the geometry on. Currently, `Planar` and `Spherical` are supported.
Extended help
julia
The algorithm is very simple; for each corner of the line (a -> b -> c),
insert two new points and remove b, such that `a -> b -> c` becomes
`a -> q -> r -> c`, where `q` and `r` are the new points such that:
```math
q = 3/4 * b + 1/4 * a
r = 3/4 * b + 1/4 * c
```
In practice the replacement happens on the level of each edge.
# References
[^1]: Chaikin, G. An algorithm for high speed curve generation. Computer Graphics and Image Processing 3 (1974), 346-349
"""
@kwdef struct Chaikin{M} <: Algorithm{M}
manifold::M = Planar()
iterations::Int = 1
end
"""
smooth(alg::Algorithm, geom)
smooth(geom; kw...)
Smooths a geometry using the provided algorithm.
The default algorithm is `Chaikin()`, which can be used on the spherical or planar manifolds.
"""
smooth(geom; kw...) = smooth(Chaikin(; kw...), geom)
smooth(m::Manifold, geom; kw...) = smooth(Chaikin(; manifold=m, kw...), geom)
function smooth(alg::Algorithm, geom; kw...)
_smooth_function(trait, geom) = _smooth(alg, trait, geom)
return apply(
WithTrait(_smooth_function),
TraitTarget{Union{GI.AbstractCurveTrait,GI.MultiPointTrait,GI.PointTrait}}(),
geom;
kw...
)
end
_smooth(alg, ::GI.PointTrait, geom) = geom
_smooth(alg, ::GI.MultiPointTrait, geom) = geom
function _smooth(alg::Chaikin{<: Planar}, trait::Trait, geom) where {M, Trait <: Union{GI.LineStringTrait,GI.LinearRingTrait}}
isring = Trait <: GI.LinearRingTrait
points = tuple_points(geom)
if isring && first(points) != last(points)
push!(points, first(points))
end
smoothed_points = _chaikin_smooth(alg.manifold, points, alg.iterations, isring)
return rebuild(geom, smoothed_points)
end
function _smooth(alg::Chaikin{<: M}, trait::Trait, geom) where {M <: Spherical, Trait <: Union{GI.LineStringTrait,GI.LinearRingTrait}}
isring = Trait <: GI.LinearRingTrait
points = apply(UnitSphereFromGeographic(), GI.PointTrait(), geom).geom
if isring && first(points) != last(points)
push!(points, first(points))
end
smoothed_points = _chaikin_smooth(alg.manifold, points, alg.iterations, isring)
return rebuild(geom, smoothed_points)
end
function _chaikin_smooth(manifold::M, points::Vector{P}, iterations::Int, isring::Bool) where {M <: Manifold, P}
points is expected to be a vector of points
julia
smoothed_points = points
for itr in 1:iterations
num_points = length(smoothed_points)
if isring
n = 1
new_points = Vector{P}(undef, num_points * 2 - 1)
else
n = 2
Need to add the first point
julia
new_points = Vector{P}(undef, num_points * 2)
new_points[begin] = smoothed_points[begin]
new_points[end] = smoothed_points[end]
end
fill!(new_points, (P <: NTuple{2, Float64} ? (-9999.0, -9999.0) : UnitSphericalPoint(-9999.0, -9999.0, -9999.0)))
julia
for i in eachindex(smoothed_points)[begin:end-1]
p1 = smoothed_points[i]
p2 = smoothed_points[i+1]
_add_smoothed_points!(manifold, new_points, p1, p2, n)
n += 2
end
if isring # Close it
new_points[end] = new_points[begin]
end
smoothed_points = new_points
end
return smoothed_points
end
function _add_smoothed_points!(::Planar, new_points, p1, p2, n)
q_x = 0.75 * GI.x(p1) + 0.25 * GI.x(p2)
q_y = 0.75 * GI.y(p1) + 0.25 * GI.y(p2)
r_x = 0.25 * GI.x(p1) + 0.75 * GI.x(p2)
r_y = 0.25 * GI.y(p1) + 0.75 * GI.y(p2)
new_points[n] = (q_x, q_y)
new_points[n+1] = (r_x, r_y)
end
For spherical points, we can simply slerp.
julia
function _add_smoothed_points!(::Spherical, new_points, p1, p2, n)
q = slerp(p1, p2, 0.25)
r = slerp(p1, p2, 0.75)
new_points[n] = q
new_points[n+1] = r
end
This page was generated using Literate.jl.