-
Notifications
You must be signed in to change notification settings - Fork 79
/
polyarea.jl
119 lines (92 loc) · 3.47 KB
/
polyarea.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------
"""
PolyArea(outer; fix=true)
PolyArea([outer, inner₁, inner₂, ..., innerₖ]; fix=true)
A polygonal area with `outer` ring, and optional inner
rings `inner₁`, `inner₂`, ..., `innerₖ`.
Rings can be a vector of [`Point`](@ref) or a
vector of tuples with coordinates for convenience,
in which case the first point should *not* be repeated
at the end of the vector.
The option `fix` tries to correct issues with polygons
in the real world, including issues with:
* `orientation` - Most algorithms assume that the
outer ring is oriented counter-clockwise (CCW) and
that all inner rings are oriented clockwise (CW).
* `degeneracy` - Sometimes data is shared with
degenerate rings (e.g. only 2 vertices).
"""
struct PolyArea{Dim,T,R<:Ring{Dim,T}} <: Polygon{Dim,T}
rings::Vector{R}
function PolyArea{Dim,T,R}(rings; fix=true) where {Dim,T,R<:Ring{Dim,T}}
if isempty(rings)
throw(ArgumentError("cannot create PolyArea without rings"))
end
if fix
outer = rings[begin]
inners = length(rings) > 1 ? rings[(begin + 1):end] : R[]
# fix orientation
ofix(r, o) = orientation(r) == o ? r : reverse(r)
outer = ofix(outer, CCW)
inners = ofix.(inners, CW)
# fix degeneracy
if nvertices(outer) == 2
v = vertices(outer)
A, B = v[1], v[2]
M = center(Segment(A, B))
outer = Ring(A, M, B)
end
inners = filter(r -> nvertices(r) > 2, inners)
rings = [outer; inners]
end
new(rings)
end
end
PolyArea(rings::AbstractVector{R}; fix=true) where {Dim,T,R<:Ring{Dim,T}} = PolyArea{Dim,T,R}(rings; fix)
PolyArea(vertices::AbstractVector{<:AbstractVector}; fix=true) = PolyArea([Ring(v) for v in vertices]; fix)
PolyArea(outer::Ring; fix=true) = PolyArea([outer]; fix)
PolyArea(outer::AbstractVector; fix=true) = PolyArea(Ring(outer); fix)
PolyArea(outer...; fix=true) = PolyArea(collect(outer); fix)
==(p₁::PolyArea, p₂::PolyArea) = p₁.rings == p₂.rings
function Base.isapprox(p₁::PolyArea, p₂::PolyArea; kwargs...)
length(p₁.rings) ≠ length(p₂.rings) && return false
all(isapprox(r₁, r₂; kwargs...) for (r₁, r₂) in zip(p₁.rings, p₂.rings))
end
vertices(p::PolyArea) = mapreduce(vertices, vcat, p.rings)
nvertices(p::PolyArea) = mapreduce(nvertices, +, p.rings)
centroid(p::PolyArea) = centroid(first(p.rings))
rings(p::PolyArea) = p.rings
function Base.unique!(p::PolyArea)
foreach(unique!, p.rings)
inds = findall(r -> nvertices(r) ≤ 2, p.rings)
setdiff!(inds, 1) # don't remove outer ring
isempty(inds) || deleteat!(p.rings, inds)
p
end
function Base.show(io::IO, p::PolyArea)
rings = p.rings
print(io, "PolyArea(")
if length(rings) == 1
r = first(rings)
printverts(io, vertices(r))
else
nverts = nvertices.(rings)
join(io, ("$n-Ring" for n in nverts), ", ")
end
print(io, ")")
end
function Base.show(io::IO, ::MIME"text/plain", p::PolyArea{Dim,T}) where {Dim,T}
rings = p.rings
println(io, "PolyArea{$Dim,$T}")
println(io, " outer")
print(io, " └─ $(rings[1])")
if length(rings) > 1
println(io)
println(io, " inner")
printelms(io, @view(rings[2:end]), " ")
end
end
Random.rand(rng::Random.AbstractRNG, ::Random.SamplerType{<:PolyArea{Dim,T}}) where {Dim,T} =
PolyArea(rand(rng, Ring{Dim,T}))