Skip to content

Commit 414d228

Browse files
authored
Merge pull request #80 from JuliaGeometry/sd/polygons
make polygon plotting work
2 parents e8ae135 + 394d6d9 commit 414d228

File tree

8 files changed

+128
-39
lines changed

8 files changed

+128
-39
lines changed

Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ authors = ["SimonDanisch <[email protected]>"]
44
version = "0.2.15"
55

66
[deps]
7+
EarCut_jll = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
78
IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
89
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
910
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"

src/GeometryBasics.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module GeometryBasics
22

33
using StaticArrays, Tables, StructArrays, IterTools, LinearAlgebra
4+
using EarCut_jll
45

56
using Base: @propagate_inbounds
67

@@ -12,15 +13,15 @@ module GeometryBasics
1213
include("viewtypes.jl")
1314
include("geometry_primitives.jl")
1415
include("rectangles.jl")
15-
include("triangulation.jl")
1616
include("meshes.jl")
17+
include("triangulation.jl")
1718
include("lines.jl")
1819
include("boundingboxes.jl")
1920

2021
export AbstractGeometry, GeometryPrimitive
2122
export Mat, Point, Vec
2223
export LineFace, Polytope, Line, NgonFace, convert_simplex
23-
export LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon
24+
export LineString, AbstractPolygon, Polygon, MultiPoint, MultiLineString, MultiPolygon
2425
export Simplex, connect, Triangle, NSimplex, Tetrahedron
2526
export QuadFace, metafree, coordinates, TetrahedronFace
2627
export TupleView, SimplexFace, Mesh, meta
@@ -36,7 +37,7 @@ module GeometryBasics
3637
export AbstractMesh, Mesh, TriangleMesh
3738
export GLNormalMesh2D, PlainTriangleMesh
3839
export MetaT, meta_table
39-
40+
4041
# all the different predefined mesh types
4142
# Note: meshes can contain arbitrary meta information,
4243
export AbstractMesh, TriangleMesh, PlainMesh, GLPlainMesh, GLPlainMesh2D, GLPlainMesh3D

src/basic_types.jl

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ function Polytope(::Type{<: NNgon{N}}, P::Type{<: AbstractPoint{NDim, T}}) where
9292
return Ngon{NDim, T, N, P}
9393
end
9494

95-
9695
const LineP{Dim, T, P <: AbstractPoint{Dim, T}} = Ngon{Dim, T, 2, P}
9796
const Line{Dim, T} = LineP{Dim, T, Point{Dim, T}}
9897

@@ -111,6 +110,17 @@ const Quadrilateral{Dim, T} = Ngon{Dim, T, 4, P} where P <: AbstractPoint{Dim, T
111110
Base.show(io::IO, x::Quadrilateral) = print(io, "Quad(", join(x, ", "), ")")
112111
Base.summary(io::IO, x::Type{<: Quadrilateral}) = print(io, "Quad")
113112

113+
function coordinates(lines::AbstractArray{LineP{Dim, T, PointType}}) where {Dim, T, PointType}
114+
if lines isa Base.ReinterpretArray
115+
return coordinates(lines.parent)
116+
else
117+
result = PointType[]
118+
for line in lines
119+
append!(result, coordinates(line))
120+
end
121+
return result
122+
end
123+
end
114124

115125
"""
116126
A `Simplex` is a generalization of an N-dimensional tetrahedra and can be thought
@@ -181,11 +191,11 @@ struct LineString{
181191
points::V
182192
end
183193

184-
coordinates(x::LineString) = x.points
194+
coordinates(x::LineString) = coordinates(x.points)
185195

186196
Base.copy(x::LineString) = LineString(copy(x.points))
187-
Base.size(x::LineString) = size(coordinates(x))
188-
Base.getindex(x::LineString, i) = getindex(coordinates(x), i)
197+
Base.size(x::LineString) = size(getfield(x, :points))
198+
Base.getindex(x::LineString, i) = getindex(getfield(x, :points), i)
189199

190200
function LineString(points::AbstractVector{LineP{Dim, T, P}}) where {Dim, T, P}
191201
return LineString{Dim, T, P, typeof(points)}(points)
@@ -276,6 +286,27 @@ function Polygon(exterior::AbstractVector{P}, faces::AbstractVector{<: LineFace}
276286
return Polygon(LineString(exterior, faces))
277287
end
278288

289+
function Polygon(exterior::AbstractVector{P}, interior::AbstractVector{<:AbstractVector{P}}) where P <: AbstractPoint{Dim, T} where {Dim, T}
290+
ext = LineString(exterior)
291+
# We need to take extra care for empty interiors, since
292+
# if we just map over it, it won't infer the element type correctly!
293+
int = typeof(ext)[]
294+
foreach(x-> push!(int, LineString(x)), interior)
295+
return Polygon(ext, int)
296+
end
297+
298+
function coordinates(polygon::Polygon{N, T, PointType}) where {N, T, PointType}
299+
exterior = coordinates(polygon.exterior)
300+
if isempty(polygon.interiors)
301+
return exterior
302+
else
303+
result = PointType[]
304+
append!(result, exterior)
305+
foreach(x-> append!(result, coordinates(x)), polygon.interiors)
306+
return result
307+
end
308+
end
309+
279310
"""
280311
MultiPolygon(polygons::AbstractPolygon)
281312
"""

src/interfaces.jl

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,12 @@ faces(tesselation::Tesselation) = faces(tesselation.primitive, nvertices(tessela
7575
normals(tesselation::Tesselation) = normals(tesselation.primitive, nvertices(tesselation))
7676
texturecoordinates(tesselation::Tesselation) = texturecoordinates(tesselation.primitive, nvertices(tesselation))
7777

78-
7978
## Decompose methods
8079
# Dispatch type to make `decompose(UV{Vec2f0}, primitive)` work
8180
# and to pass through tesselation information
8281

8382
# Types that can be converted to a mesh via the functions below
84-
const Meshable{Dim, T} = Union{Tesselation{Dim, T}, Mesh{Dim, T},
83+
const Meshable{Dim, T} = Union{Tesselation{Dim, T}, Mesh{Dim, T}, AbstractPolygon{Dim, T},
8584
GeometryPrimitive{Dim, T}, AbstractVector{<: AbstractPoint{Dim, T}}}
8685

8786
struct UV{T} end
@@ -108,23 +107,14 @@ function decompose(::Type{Point}, primitive::Meshable{Dim, T}) where {Dim, T}
108107
return collect_with_eltype(Point{Dim, T}, metafree(coordinates(primitive)))
109108
end
110109

111-
function decompose(::Type{T}, primitive) where {T}
112-
return collect_with_eltype(T, primitive)
110+
function decompose(::Type{Point}, primitive::LineString{Dim, T}) where {Dim, T}
111+
return collect_with_eltype(Point{Dim, T}, metafree(coordinates(primitive)))
113112
end
114113

115-
function decompose(::Type{P}, pol::Polygon) where {P<:AbstractPoint}
116-
if isempty(pol.interiors)
117-
return decompose(P, pol.exterior)
118-
else
119-
arr = copy(decompose(P, pol.exterior))
120-
for i in pol.interiors
121-
append!(arr, decompose(P, i))
122-
end
123-
return arr
124-
end
114+
function decompose(::Type{T}, primitive) where {T}
115+
return collect_with_eltype(T, primitive)
125116
end
126117

127-
decompose(::Type{P}, ls::LineString) where {P<:AbstractPoint} = ls.points.parent.data
128118
decompose_uv(primitive) = decompose(UV(), primitive)
129119
decompose_uvw(primitive) = decompose(UVW(), primitive)
130120
decompose_normals(primitive) = decompose(Normal(), primitive)

src/meshes.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ function normals(mesh::AbstractMesh)
1414
return nothing
1515
end
1616

17-
1817
const GLTriangleElement = Triangle{3, Float32}
1918
const GLTriangleFace = TriangleFace{GLIndex}
2019
const PointWithUV{Dim, T} = PointMeta{Dim, T, Point{Dim, T}, (:uv,), Tuple{Vec{2, T}}}
@@ -144,14 +143,19 @@ Polygon triangluation!
144143
function mesh(polygon::AbstractVector{P}; pointtype=P, facetype=GLTriangleFace,
145144
normaltype=nothing) where {P<:AbstractPoint{2}}
146145

146+
return mesh(Polygon(polygon); pointtype, facetype, normaltype)
147+
end
148+
149+
function mesh(polygon::AbstractPolygon{Dim, T}; pointtype=Point{Dim, T}, facetype=GLTriangleFace,
150+
normaltype=nothing) where {Dim, T}
151+
147152
faces = decompose(facetype, polygon)
148153
positions = decompose(pointtype, polygon)
149154

150155
if normaltype !== nothing
151156
n = normals(positions, faces; normaltype=normaltype)
152157
positions = meta(positions; normals=n)
153158
end
154-
155159
return Mesh(positions, faces)
156160
end
157161

src/triangulation.jl

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,66 @@ function decompose(::Type{FaceType}, points::AbstractArray{P}) where {P<:Abstrac
169169

170170
return result
171171
end
172+
173+
174+
function earcut_triangulate(polygon::Vector{Vector{Point{2, Float64}}})
175+
lengths = map(x-> UInt32(length(x)), polygon)
176+
len = UInt32(length(lengths))
177+
array = ccall(
178+
(:u32_triangulate_f64, libearcut),
179+
Tuple{Ptr{GLTriangleFace}, Cint},
180+
(Ptr{Ptr{Float64}}, Ptr{UInt32}, UInt32),
181+
polygon, lengths, len
182+
)
183+
return unsafe_wrap(Vector{GLTriangleFace}, array[1], array[2])
184+
end
185+
186+
function earcut_triangulate(polygon::Vector{Vector{Point{2, Float32}}})
187+
lengths = map(x-> UInt32(length(x)), polygon)
188+
len = UInt32(length(lengths))
189+
array = ccall(
190+
(:u32_triangulate_f32, libearcut),
191+
Tuple{Ptr{GLTriangleFace}, Cint},
192+
(Ptr{Ptr{Float32}}, Ptr{UInt32}, UInt32),
193+
polygon, lengths, len
194+
)
195+
return unsafe_wrap(Vector{GLTriangleFace}, array[1], array[2])
196+
end
197+
198+
function earcut_triangulate(polygon::Vector{Vector{Point{2, Int64}}})
199+
lengths = map(x-> UInt32(length(x)), polygon)
200+
len = UInt32(length(lengths))
201+
array = ccall(
202+
(:u32_triangulate_i64, libearcut),
203+
Tuple{Ptr{GLTriangleFace}, Cint},
204+
(Ptr{Ptr{Int64}}, Ptr{UInt32}, UInt32),
205+
polygon, lengths, len
206+
)
207+
return unsafe_wrap(Vector{GLTriangleFace}, array[1], array[2])
208+
end
209+
210+
function earcut_triangulate(polygon::Vector{Vector{Point{2, Int32}}})
211+
lengths = map(x-> UInt32(length(x)), polygon)
212+
len = UInt32(length(lengths))
213+
array = ccall(
214+
(:u32_triangulate_i32, libearcut),
215+
Tuple{Ptr{GLTriangleFace}, Cint},
216+
(Ptr{Ptr{Int32}}, Ptr{UInt32}, UInt32),
217+
polygon, lengths, len
218+
)
219+
return unsafe_wrap(Vector{GLTriangleFace}, array[1], array[2])
220+
end
221+
222+
best_earcut_eltype(x) = Float64
223+
best_earcut_eltype(::Type{Float64}) = Float64
224+
best_earcut_eltype(::Type{<:AbstractFloat}) = Float32
225+
best_earcut_eltype(::Type{Int64}) = Int64
226+
best_earcut_eltype(::Type{Int32}) = Int32
227+
best_earcut_eltype(::Type{<:Integer}) = Int64
228+
229+
function faces(polygon::Polygon{Dim, T}) where {Dim, T}
230+
PT = Point{Dim, best_earcut_eltype(T)}
231+
points = [decompose(PT, polygon.exterior)]
232+
foreach(x-> push!(points, decompose(PT, x)), polygon.interiors)
233+
return earcut_triangulate(points)
234+
end

src/viewtypes.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ struct TupleView{T, N, Skip, A} <: AbstractVector{T}
3636
connect::Bool
3737
end
3838

39+
coordinates(tw::TupleView) = coordinates(tw.data)
40+
3941
function Base.size(x::TupleView{T, N, M}) where {T, N, M}
4042
nitems = length(x.data) ÷ (N - (N - M))
4143
nitems = nitems - max(N - M, 0)

test/runtests.jl

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ using GeometryBasics: attributes
5656
end
5757

5858
end
59-
59+
6060
@testset "polygon with metadata" begin
6161
polys = [Polygon(rand(Point{2, Float32}, 20)) for i in 1:10]
6262
pnames = [randstring(4) for i in 1:10]
@@ -68,7 +68,7 @@ using GeometryBasics: attributes
6868
multipoly = MultiPolygonMeta(polys, name = pnames, value = numbers, category = bin)
6969
@test multipoly isa AbstractVector
7070
@test poly isa GeometryBasics.AbstractPolygon
71-
71+
7272
@test GeometryBasics.getcolumn(poly, :name) == pnames[1]
7373
@test GeometryBasics.MetaFree(PolygonMeta) == Polygon
7474

@@ -92,7 +92,7 @@ using GeometryBasics: attributes
9292
@test metafree(pm) === p
9393
@test propertynames(pm) == (:position, :a, :b)
9494
end
95-
95+
9696
@testset "MultiPoint with metadata" begin
9797
p = collect(Point{2, Float64}(x, x+1) for x in 1:5)
9898
@test p isa AbstractVector
@@ -144,18 +144,18 @@ end
144144
multipoly = MetaT(multipol, name = pnames, value = numbers, category = bin)
145145
@test multipoly isa MetaT
146146
@test poly isa MetaT
147-
147+
148148
@test GeometryBasics.getcolumn(poly, :name) == pnames[1]
149149
@test GeometryBasics.getcolumn(multipoly, :name) == pnames
150-
150+
151151
meta_p = MetaT(polys[1], boundingbox=Rect(0, 0, 2, 2))
152152
@test meta_p.boundingbox === Rect(0, 0, 2, 2)
153153
@test GeometryBasics.metafree(meta_p) == polys[1]
154154
@test GeometryBasics.metafree(poly) == polys[1]
155155
@test GeometryBasics.metafree(multipoly) == multipol
156156
@test GeometryBasics.meta(meta_p) == (boundingbox = GeometryBasics.HyperRectangle{2,Int64}([0, 0], [2, 2]),)
157157
@test GeometryBasics.meta(poly) == (name = pnames[1], value = 0.0, category = bin[1])
158-
@test GeometryBasics.meta(multipoly) == (name = pnames, value = numbers, category = bin)
158+
@test GeometryBasics.meta(multipoly) == (name = pnames, value = numbers, category = bin)
159159
end
160160

161161
@testset "MetaT{Point}" begin
@@ -171,7 +171,7 @@ end
171171
@test GeometryBasics.metafree(pm) == p
172172
@test GeometryBasics.meta(pm) == (a = 1, b = 2)
173173
end
174-
174+
175175
@testset "MetaT{MultiPoint}" begin
176176
p = collect(Point{2, Float64}(x, x+1) for x in 1:5)
177177
@test p isa AbstractVector
@@ -623,7 +623,7 @@ end
623623

624624
@testset "MetaT and heterogeneous data" begin
625625
ls = [LineString([Point(i, (i+1)^2/6), Point(i*0.86,i+5), Point(i/3, i/7)]) for i in 1:10]
626-
mls = MultiLineString([LineString([Point(i+1, (i)^2/6), Point(i*0.75,i+8), Point(i/2.5, i/6.79)]) for i in 5:10])
626+
mls = MultiLineString([LineString([Point(i+1, (i)^2/6), Point(i*0.75,i+8), Point(i/2.5, i/6.79)]) for i in 5:10])
627627
poly = Polygon(Point{2, Int}[(40, 40), (20, 45), (45, 30), (40, 40)])
628628
geom = [ls..., mls, poly]
629629
prop = Any[(country_states = "India$(i)", rainfall = (i*9)/2) for i in 1:11]
@@ -637,17 +637,14 @@ end
637637
@test propertynames(sa) === (:main, :country_states, :rainfall)
638638
@test getproperty(sa, :country_states) isa Array{Any}
639639
@test getproperty(sa, :main) == geom
640-
641-
@test GeometryBasics.getnamestypes(typeof(feat[1])) ==
640+
641+
@test GeometryBasics.getnamestypes(typeof(feat[1])) ==
642642
(LineString{2,Float64,Point{2,Float64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Float64,2,Point{2,Float64}},1,Tuple{Point{2,Float64},Point{2,Float64}},TupleView{Tuple{Point{2,Float64},Point{2,Float64}},2,1,Array{Point{2,Float64},1}}}},
643643
(:country_states, :rainfall), Tuple{String,Float64})
644-
645-
@test StructArrays.staticschema(typeof(feat[1])) ==
646-
NamedTuple{(:main, :country_states, :rainfall),Tuple{LineString{2,Float64,Point{2,Float64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Float64,2,Point{2,Float64}},1,Tuple{Point{2,Float64},Point{2,Float64}},TupleView{Tuple{Point{2,Float64},Point{2,Float64}},2,1,Array{Point{2,Float64},1}}}},
647-
String,Float64}}
644+
648645

649646
@test StructArrays.createinstance(typeof(feat[1]), LineString([Point(1, (2)^2/6), Point(1*0.86,6), Point(1/3, 1/7)]), "Mumbai", 100) isa typeof(feat[1])
650-
647+
651648
@test Base.getindex(feat[1], 1) isa Line
652649
@test Base.size(feat[1]) == (2,)
653650
end

0 commit comments

Comments
 (0)