Skip to content

Commit 4ec151a

Browse files
authored
Merge branch 'master' into sd/simple-mesh
2 parents b1f6567 + 89b857e commit 4ec151a

File tree

9 files changed

+149
-34
lines changed

9 files changed

+149
-34
lines changed

.github/workflows/TagBot.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ on:
44
types:
55
- created
66
workflow_dispatch:
7+
inputs:
8+
lookback:
9+
default: 3
10+
permissions:
11+
actions: read
12+
checks: read
13+
contents: write
14+
deployments: read
15+
issues: read
16+
discussions: read
17+
packages: read
18+
pages: read
19+
pull-requests: read
20+
repository-projects: read
21+
security-events: read
22+
statuses: read
723
jobs:
824
TagBot:
925
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'

Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
name = "GeometryBasics"
22
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
33
authors = ["SimonDanisch <[email protected]>"]
4-
version = "0.4.5"
4+
version = "0.4.9"
55

66
[deps]
77
EarCut_jll = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
8+
Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
89
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
910
IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
1011
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1112
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1213

1314
[compat]
1415
EarCut_jll = "2"
16+
Extents = "0.1"
1517
GeoInterface = "1.0.1"
1618
IterTools = "1.3.0"
1719
julia = "1.6"

src/GeometryBasics.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module GeometryBasics
22

33
using IterTools, LinearAlgebra
44
using GeoInterface
5+
import Extents
56
using EarCut_jll
67
import Base: *
78

src/geointerface.jl

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
GeoInterface.isgeometry(::Type{<:AbstractGeometry}) = true
44
GeoInterface.isgeometry(::Type{<:AbstractFace}) = true
5+
56
GeoInterface.isgeometry(::Type{<:Point}) = true
6-
GeoInterface.isgeometry(::Type{<:AbstractVector{<:AbstractGeometry}}) = true
7-
GeoInterface.isgeometry(::Type{<:AbstractVector{<:Point}}) = true
8-
GeoInterface.isgeometry(::Type{<:AbstractVector{<:LineString}}) = true
9-
GeoInterface.isgeometry(::Type{<:AbstractVector{<:AbstractPolygon}}) = true
10-
GeoInterface.isgeometry(::Type{<:AbstractVector{<:AbstractFace}}) = true
7+
GeoInterface.isgeometry(::Type{<:AbstractMesh}) = true
8+
GeoInterface.isgeometry(::Type{<:AbstractPolygon}) = true
9+
GeoInterface.isgeometry(::Type{<:LineString}) = true
10+
GeoInterface.isgeometry(::Type{<:MultiPoint}) = true
11+
GeoInterface.isgeometry(::Type{<:MultiLineString}) = true
12+
GeoInterface.isgeometry(::Type{<:MultiPolygon}) = true
1113
GeoInterface.isgeometry(::Type{<:Mesh}) = true
1214

1315
GeoInterface.geomtrait(::Point) = PointTrait()
@@ -23,6 +25,7 @@ GeoInterface.geomtrait(::AbstractMesh) = PolyhedralSurfaceTrait()
2325
# GeoInterface calls this method in `GeoInterface.convert(GeometryBasics, ...)`
2426
geointerface_geomtype(::GeoInterface.PointTrait) = Point
2527
geointerface_geomtype(::GeoInterface.MultiPointTrait) = MultiPoint
28+
geointerface_geomtype(::GeoInterface.LineTrait) = Line
2629
geointerface_geomtype(::GeoInterface.LineStringTrait) = LineString
2730
geointerface_geomtype(::GeoInterface.MultiLineStringTrait) = MultiLineString
2831
geointerface_geomtype(::GeoInterface.PolygonTrait) = Polygon
@@ -59,8 +62,7 @@ GeoInterface.getgeom(::MultiPointTrait, g::MultiPoint, i::Int) = g[i]
5962
function GeoInterface.ngeom(::MultiLineStringTrait, g::MultiLineString)
6063
return length(g)
6164
end
62-
function GeoInterface.getgeom(::MultiLineStringTrait, g::MultiLineString,
63-
i::Int)
65+
function GeoInterface.getgeom(::MultiLineStringTrait, g::MultiLineString, i::Int)
6466
return g[i]
6567
end
6668
GeoInterface.ncoord(::MultiLineStringTrait, g::MultiLineString{Dim}) where {Dim} = Dim
@@ -91,13 +93,29 @@ GeoInterface.ngeom(::PolyhedralSurfaceTrait, g::AbstractMesh) = length(g)
9193
GeoInterface.getgeom(::PolyhedralSurfaceTrait, g::AbstractMesh, i) = g[i]
9294

9395
function GeoInterface.convert(::Type{Point}, type::PointTrait, geom)
94-
dim = Int(ncoord(geom))
95-
return Point{dim, Float64}(GeoInterface.coordinates(geom))
96+
x, y = GeoInterface.x(geom), GeoInterface.y(geom)
97+
if GeoInterface.is3d(geom)
98+
z = GeoInterface.z(geom)
99+
T = promote_type(typeof(x), typeof(y), typeof(z))
100+
return Point{3,T}(x, y, z)
101+
else
102+
GeoInterface.x(geom), GeoInterface.y(geom)
103+
T = promote_type(typeof(x), typeof(y))
104+
return Point{2,T}(x, y)
105+
end
96106
end
97107

98108
function GeoInterface.convert(::Type{LineString}, type::LineStringTrait, geom)
99-
dim = Int(ncoord(geom))
100-
return LineString([Point{dim, Float64}(GeoInterface.coordinates(p)) for p in getgeom(geom)])
109+
g1 = getgeom(geom, 1)
110+
x, y = GeoInterface.x(g1), GeoInterface.y(g1)
111+
if GeoInterface.is3d(geom)
112+
z = GeoInterface.z(g1)
113+
T = promote_type(typeof(x), typeof(y), typeof(z))
114+
return LineString([Point{3,T}(GeoInterface.x(p), GeoInterface.y(p), GeoInterface.z(p)) for p in getgeom(geom)])
115+
else
116+
T = promote_type(typeof(x), typeof(y))
117+
return LineString([Point{2,T}(GeoInterface.x(p), GeoInterface.y(p)) for p in getgeom(geom)])
118+
end
101119
end
102120

103121
function GeoInterface.convert(::Type{Polygon}, type::PolygonTrait, geom)
@@ -106,22 +124,35 @@ function GeoInterface.convert(::Type{Polygon}, type::PolygonTrait, geom)
106124
if GeoInterface.nhole(geom) == 0
107125
return Polygon(exterior)
108126
else
109-
interiors = GeoInterface.convert.(LineString, Ref(t), GeoInterface.gethole(geom))
127+
interiors = map(h -> GeoInterface.convert(LineString, t, h), GeoInterface.gethole(geom))
110128
return Polygon(exterior, interiors)
111129
end
112130
end
113131

114132
function GeoInterface.convert(::Type{MultiPoint}, type::MultiPointTrait, geom)
115-
dim = Int(ncoord(geom))
116-
return MultiPoint([Point{dim, Float64}(GeoInterface.coordinates(p)) for p in getgeom(geom)])
133+
g1 = getgeom(geom, 1)
134+
x, y = GeoInterface.x(g1), GeoInterface.y(g1)
135+
if GeoInterface.is3d(geom)
136+
z = GeoInterface.z(g1)
137+
T = promote_type(typeof(x), typeof(y), typeof(z))
138+
return MultiPoint([Point{3,T}(GeoInterface.x(p), GeoInterface.y(p), GeoInterface.z(p)) for p in getgeom(geom)])
139+
else
140+
T = promote_type(typeof(x), typeof(y))
141+
return MultiPoint([Point{2,T}(GeoInterface.x(p), GeoInterface.y(p)) for p in getgeom(geom)])
142+
end
117143
end
118144

119145
function GeoInterface.convert(::Type{MultiLineString}, type::MultiLineStringTrait, geom)
120146
t = LineStringTrait()
121-
return MultiLineString([GeoInterface.convert(LineString, t, l) for l in getgeom(geom)])
147+
return MultiLineString(map(l -> GeoInterface.convert(LineString, t, l), getgeom(geom)))
122148
end
123149

124150
function GeoInterface.convert(::Type{MultiPolygon}, type::MultiPolygonTrait, geom)
125151
t = PolygonTrait()
126-
return MultiPolygon([GeoInterface.convert(Polygon, t, poly) for poly in getgeom(geom)])
152+
return MultiPolygon(map(poly -> GeoInterface.convert(Polygon, t, poly), getgeom(geom)))
127153
end
154+
155+
function Extents.extent(rect::Rect2)
156+
(xmin, ymin), (xmax, ymax) = extrema(rect)
157+
return Extents.Extent(X=(xmin, xmax), Y=(ymin, ymax))
158+
end

src/geometry_primitives.jl

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,33 @@ collect_with_eltype(::Type{T}, vec::Vector{T}) where {T} = vec
7575
collect_with_eltype(::Type{T}, vec::AbstractVector{T}) where {T} = collect(vec)
7676

7777
function collect_with_eltype(::Type{T}, iter) where {T}
78-
# TODO we could be super smart about allocating the right length
79-
# but its kinda annoying, since e.g. T == Triangle and first(iter) isa Quad
80-
# will need double the length etc - but could all be figured out ;)
81-
result = T[]
78+
isempty(iter) && return T[]
79+
# We need to get `eltype` information from `iter`, it seems to be `Any`
80+
# most of the time so the eltype checks here don't actually work
81+
l = if Base.IteratorSize(iter) isa Union{Base.HasShape,Base.HasLength}
82+
if Base.IteratorEltype(iter) isa Base.HasEltype && isconcretetype(eltype(iter))
83+
# Work out the exact length
84+
length(convert_simplex(T, first(iter))) * length(iter)
85+
else
86+
# We know it is at least the length of iter,
87+
# after that we will `push!` if we have to
88+
length(iter)
89+
end
90+
else
91+
0
92+
end
93+
n = 0
94+
result = Vector{T}(undef, l)
8295
for element in iter
8396
# convert_simplex always returns a tuple,
8497
# so that e.g. convert(Triangle, quad) can return 2 elements
8598
for telement in convert_simplex(T, element)
86-
push!(result, telement)
99+
n += 1
100+
if n > l
101+
push!(result, telement)
102+
else
103+
result[n] = telement
104+
end
87105
end
88106
end
89107
return result

src/meshes.jl

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,25 @@ function triangle_mesh(primitive::AbstractGeometry{N})::TriangleMesh{N} where {N
4545
return mesh(primitive; pointtype=Point{N, Float32})
4646
end
4747

48-
function triangle_mesh(primitive::AbstractVector{<: Point2})::TriangleMesh{2}
49-
return mesh(Polygon(primitive))
48+
49+
pointtype(x::Mesh) = eltype(decompose(Point, x))
50+
facetype(x::Mesh) = eltype(faces(x))
51+
52+
function triangle_mesh(primitive::Mesh{N}) where {N}
53+
# already target type:
54+
if pointtype(primitive) === Point{N,Float32} && GLTriangleFace === facetype(primitive)
55+
return primitive
56+
else
57+
return mesh(primitive; pointtype=Point{N,Float32}, facetype=GLTriangleFace)
58+
end
59+
end
60+
61+
function triangle_mesh(primitive::Meshable{N}; nvertices=nothing) where {N}
62+
if nvertices !== nothing
63+
@warn("nvertices argument deprecated. Wrap primitive in `Tesselation(primitive, nvertices)`")
64+
primitive = Tesselation(primitive, nvertices)
65+
end
66+
return mesh(primitive; pointtype=Point{N,Float32}, facetype=GLTriangleFace)
5067
end
5168

5269
function uv_mesh(primitive::AbstractGeometry{N,T}) where {N,T}
@@ -99,12 +116,17 @@ function Base.merge(meshes::AbstractVector{<:Mesh})
99116
elseif length(meshes) == 1
100117
return meshes[1]
101118
else
102-
m1 = meshes[1]
103-
ps = copy(coordinates(m1))
104-
fs = copy(faces(m1))
119+
ps = reduce(vcat, coordinates.(meshes))
120+
fs = reduce(vcat, faces.(meshes))
121+
idx = length(faces(meshes[1]))
122+
offset = length(coordinates(meshes[1]))
105123
for mesh in Iterators.drop(meshes, 1)
106-
append!(fs, map(f -> f .+ length(ps), faces(mesh)))
107-
append!(ps, coordinates(mesh))
124+
N = length(faces(mesh))
125+
for i = idx .+ (1:N)
126+
fs[i] = fs[i] .+ offset
127+
end
128+
idx += N
129+
offset += length(coordinates(mesh))
108130
end
109131
return Mesh(ps, fs)
110132
end

test/geointerface.jl

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
@testset "Basic types" begin
22
point = Point(2, 3)
3+
@test geomtrait(point) isa PointTrait
34
@test testgeometry(point)
45
@test ncoord(point) == 2
56
@test getcoord(point, 2) == 3
67
@test GeoInterface.coordinates(point) == [2, 3]
78

9+
line = Line(Point(2, 3), Point(4, 5))
10+
@test geomtrait(line) isa LineTrait
11+
@test testgeometry(line)
12+
@test ngeom(line) == 2
13+
@test getgeom(line, 2) == Point(4, 5)
14+
@test GeoInterface.coordinates(line) == [[2, 3], [4, 5]]
15+
816
mp = MultiPoint([point, point])
17+
@test geomtrait(mp) isa MultiPointTrait
918
@test testgeometry(mp)
1019
@test ngeom(mp) == 2
1120
@test getgeom(mp, 2) == point
1221
@test GeoInterface.coordinates(mp) == [[2, 3], [2, 3]]
1322

1423
linestring = LineString(Point{2,Int}[(10, 10), (20, 20), (10, 40)])
24+
@test geomtrait(linestring) isa LineStringTrait
1525
@test testgeometry(linestring)
1626
@test ngeom(linestring) == 3
1727
@test ncoord(linestring) == 2
@@ -21,22 +31,26 @@
2131
@test GeoInterface.coordinates(linestring) == [[10, 10], [20, 20], [10, 40]]
2232

2333
multilinestring = MultiLineString([linestring, linestring])
34+
@test geomtrait(multilinestring) isa MultiLineStringTrait
2435
@test testgeometry(multilinestring)
2536
@test GeoInterface.coordinates(multilinestring) ==
2637
[[[10, 10], [20, 20], [10, 40]], [[10, 10], [20, 20], [10, 40]]]
2738
@test ncoord(multilinestring) == 2
2839

2940
poly = Polygon(rand(Point{2,Float32}, 5), [rand(Point{2,Float32}, 5)])
41+
@test geomtrait(poly) isa PolygonTrait
3042
@test testgeometry(poly)
3143
@test length(GeoInterface.coordinates(poly)) == 2
3244
@test length(GeoInterface.coordinates(poly)[1]) == 5
3345

3446
triangle = Triangle(point, point, point)
47+
@test geomtrait(triangle) isa PolygonTrait # ?? should it be a Triangle trait
3548
@test testgeometry(triangle)
3649
@test length(GeoInterface.coordinates(triangle)) == 1
3750
@test length(GeoInterface.coordinates(triangle)[1]) == 3
3851

3952
polys = MultiPolygon([poly, poly])
53+
@test geomtrait(polys) isa MultiPolygonTrait
4054
@test testgeometry(polys)
4155
@test length(GeoInterface.coordinates(polys)) == 2
4256
@test length(GeoInterface.coordinates(polys)[1]) == 2
@@ -79,20 +93,21 @@ end
7993
multilinestring_gb = GeoInterface.convert(GeometryBasics, multilinestring_json)
8094
multipolygon_gb = GeoInterface.convert(GeometryBasics, multipolygon_json)
8195
multipolygon_hole_gb = GeoInterface.convert(GeometryBasics, multipolygon_hole_json)
82-
83-
@test point_gb === Point{2, Float64}(30.1, 10.1)
84-
@test point_3d_gb === Point{3, Float64}(30.1, 10.1, 5.1)
96+
97+
@test point_gb === Point{2,Float32}(30.1, 10.1)
98+
@test point_3d_gb === Point{3,Float32}(30.1, 10.1, 5.1)
8599
@test linestring_gb isa LineString
86100
# TODO, what should we do exactly with linestrings?
87101
# @test length(linestring_gb) == 2
88102
# @test eltype(linestring_gb) == Line{2, Float64}
103+
89104
@test polygon_gb isa Polygon
90105
@test isempty(polygon_gb.interiors)
91106
@test polygon_hole_gb isa Polygon
92107
@test length(polygon_hole_gb.interiors) == 1
93108
@test multipoint_gb isa MultiPoint
94109
@test length(multipoint_gb) == 4
95-
@test multipoint_gb[4] === Point{2, Float64}(30.1, 10.1)
110+
@test multipoint_gb[4] === Point{2,Float32}(30.1, 10.1)
96111
@test multilinestring_gb isa MultiLineString
97112
@test length(multilinestring_gb) == 2
98113
@test multipolygon_gb isa MultiPolygon
@@ -102,3 +117,10 @@ end
102117
@test length(multipolygon_hole_gb[1].interiors) == 0
103118
@test length(multipolygon_hole_gb[2].interiors) == 1
104119
end
120+
121+
@testset "Extent" begin
122+
rect = Rect2f(Vec2f(0), Vec2f(1.0))
123+
ext = extent(rect)
124+
@test ext.X == (0.0f0, 1.0f0)
125+
@test ext.Y == (0.0f0, 1.0f0)
126+
end

test/geometrytypes.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ using Test, GeometryBasics
7373
@test faces == decompose(TriangleFace{Int}, Tesselation(s, 8))
7474

7575
m = triangle_mesh(Tesselation(s, 8))
76-
76+
@test m === triangle_mesh(m)
7777
@test GeometryBasics.faces(m) == faces
7878
@test GeometryBasics.coordinates(m) positions
7979
m = normal_mesh(s)# just test that it works without explicit resolution parameter

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ using LinearAlgebra
44
using GeometryBasics: MetaMesh, add_meta, pop_meta
55
using GeoInterface
66
using GeoJSON
7+
using Extents
78

89
@testset "GeometryBasics" begin
910
@testset "algorithms" begin
@@ -147,6 +148,8 @@ end
147148
end
148149

149150
@testset "decompose/triangulation" begin
151+
@test isempty(decompose(Vec3f, []))
152+
@test decompose(Vec3f, []) isa Vector{Vec3f}
150153
primitive = Sphere(Point3f(0), 1)
151154
@test ndims(primitive) === 3
152155
mesh = triangle_mesh(primitive)

0 commit comments

Comments
 (0)