Skip to content

Commit 56f8156

Browse files
committed
Update benchmark code
- allow arbitrary legend kws - import GeoFormatTypes, WellKnownGeometry, CoordinateTransformations, ProgressMeter - make USA benchmarks print better - add benchmarks for coverage union - It turns out JTS (and also GEOS) use a special purpose noder and algorithm for coverage unions. We should also implement that at some stage - but it's a bit tough without the OverlayNG methodology. Maybe we can get an AI to implement that here :D
1 parent ad1e21a commit 56f8156

File tree

4 files changed

+126
-31
lines changed

4 files changed

+126
-31
lines changed

benchmarks/benchmark_plots.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ function plot_trials(
7373
legend_orientation = :horizontal,
7474
legend_halign = 1.0,
7575
legend_valign = -0.25,
76+
legend_kws = (;),
7677
)
7778

7879
xs, ys, labels = [], [], []
@@ -118,7 +119,8 @@ function plot_trials(
118119
tellheight = legend_position isa Union{Tuple, Makie.Automatic} && (legend_position isa Makie.Automatic || length(legend_position) != 3) && legend_orientation == :horizontal,
119120
halign = legend_halign,
120121
valign = legend_valign,
121-
orientation = legend_orientation
122+
orientation = legend_orientation,
123+
legend_kws...,
122124
)
123125
ax.xtickcolor[] = ax.xgridcolor[]
124126
ax

benchmarks/benchmarks.jl

Lines changed: 120 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,23 @@
1010
import GeometryOps as GO,
1111
GeoInterface as GI,
1212
GeometryBasics as GB,
13-
LibGEOS as LG
14-
import GeoJSON, NaturalEarth
13+
LibGEOS as LG,
14+
GeoFormatTypes as GFT
15+
import GeoJSON, NaturalEarth, WellKnownGeometry
16+
using CoordinateTransformations: Translation, LinearMap
1517
# In order to benchmark, we'll actually use the new [Chairmarks.jl](https://github.com/lilithhafner/Chairmarks.jl),
1618
# since it's significantly faster than BenchmarkTools. To keep benchmarks organized, though, we'll still use BenchmarkTools'
1719
# `BenchmarkGroup` structure.
1820
using Chairmarks
1921
import BenchmarkTools: BenchmarkGroup
22+
using ProgressMeter
2023
# We use CairoMakie to visualize our results!
2124
using CairoMakie, MakieThemes, GeoInterfaceMakie
2225
# Finally, we import some general utility packages:
2326
using Statistics, CoordinateTransformations
2427

28+
include("benchmark_plots.jl")
29+
2530
# We also set up some utility functions for later on.
2631
"""
2732
Returns LibGEOS and GeometryOps' native geometries,
@@ -155,7 +160,7 @@ circle_difference_suite = circle_suite["difference"] = BenchmarkGroup(["title:Ci
155160
circle_intersection_suite = circle_suite["intersection"] = BenchmarkGroup(["title:Circle intersection", "subtitle:Tested on a regular circle"])
156161
circle_union_suite = circle_suite["union"] = BenchmarkGroup(["title:Circle union", "subtitle:Tested on a regular circle"])
157162

158-
n_points_values = round.(Int, exp10.(LinRange(1, 4, 10)))
163+
n_points_values = round.(Int, exp10.(LinRange(0.7, 6, 15)))
159164
@time for n_points in n_points_values
160165
circle = GI.Wrappers.Polygon([tuple.((cos(θ) for θ in LinRange(0, 2π, n_points)), (sin(θ) for θ in LinRange(0, 2π, n_points)))])
161166
closed_circle = GO.ClosedRing()(circle)
@@ -197,29 +202,117 @@ f
197202
# Now, we get to benchmarking:
198203

199204

200-
usa_o_lg, usa_o_go = lg_and_go(usa_poly)
201-
usa_r_lg, usa_r_go = lg_and_go(usa_reflected)
205+
usa_o_lg, usa_o_go = lg_and_go(usa_poly);
206+
usa_r_lg, usa_r_go = lg_and_go(usa_reflected);
202207

203208
# First, we'll test union:
204-
printstyled("LibGEOS"; color = :red, bold = true)
205-
println()
206-
@be LG.union($usa_o_lg, $usa_r_lg) seconds=5
207-
printstyled("GeometryOps"; color = :blue, bold = true)
208-
println()
209-
@be GO.union($usa_o_go, $usa_r_go; target = GI.PolygonTrait) seconds=5
210-
211-
# Next, intersection:
212-
printstyled("LibGEOS"; color = :red, bold = true)
213-
println()
214-
@be LG.intersection($usa_o_lg, $usa_r_lg) seconds=5
215-
printstyled("GeometryOps"; color = :blue, bold = true)
216-
println()
217-
@be GO.intersection($usa_o_go, $usa_r_go; target = GI.PolygonTrait) seconds=5
218-
219-
# and finally the difference:
220-
printstyled("LibGEOS"; color = :red, bold = true)
221-
println()
222-
@be LG.difference(usa_o_lg, usa_r_lg) seconds=5
223-
printstyled("GeometryOps"; color = :blue, bold = true)
224-
println()
225-
@be go_diff = GO.difference(usa_o_go, usa_r_go; target = GI.PolygonTrait) seconds=5
209+
begin
210+
printstyled("Union"; color = :green, bold = true)
211+
println()
212+
printstyled("LibGEOS"; color = :red, bold = true)
213+
println()
214+
display(@be LG.union($usa_o_lg, $usa_r_lg) seconds=5)
215+
printstyled("GeometryOps"; color = :blue, bold = true)
216+
println()
217+
display(@be GO.union($usa_o_go, $usa_r_go; target = GI.PolygonTrait) seconds=5)
218+
println()
219+
# Next, intersection:
220+
printstyled("Intersection"; color = :green, bold = true)
221+
println()
222+
printstyled("LibGEOS"; color = :red, bold = true)
223+
println()
224+
display(@be LG.intersection($usa_o_lg, $usa_r_lg) seconds=5)
225+
printstyled("GeometryOps"; color = :blue, bold = true)
226+
println()
227+
display(@be GO.intersection($usa_o_go, $usa_r_go; target = GI.PolygonTrait) seconds=5)
228+
# and finally the difference:
229+
printstyled("Difference"; color = :green, bold = true)
230+
println()
231+
printstyled("LibGEOS"; color = :red, bold = true)
232+
println()
233+
display(@be LG.difference(usa_o_lg, usa_r_lg) seconds=5)
234+
printstyled("GeometryOps"; color = :blue, bold = true)
235+
println()
236+
display(@be GO.difference(usa_o_go, usa_r_go; target = GI.PolygonTrait) seconds=5)
237+
end
238+
239+
240+
241+
242+
# ## Vancouver watershed benchmarks
243+
#=
244+
245+
Vancouver Island has ~1,300 watersheds. LibGEOS uses this exact data
246+
in their tests, so we'll use it in ours as well!
247+
248+
We'll start by loading the data, and then we'll use it to benchmark various operations.
249+
250+
=#
251+
252+
# The CRS for this file is EPSG:3005, or as a PROJ string,
253+
# `"+proj=aea +lat_1=50 +lat_2=58.5 +lat_0=45 +lon_0=-126 +x_0=1000000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"`
254+
# TODO: this doesn't work with WellKnownGeometry. Why?
255+
256+
watersheds = mktempdir() do dir
257+
cd(dir) do
258+
wkt_gz = download("https://github.com/pramsey/geos-performance/raw/refs/heads/master/data/watersheds.wkt.gz", "watersheds.wkt.gz")
259+
run(`gunzip watersheds.wkt.gz`)
260+
return [
261+
GO.tuples(GFT.WellKnownText(GFT.Geom(), line))
262+
for line in eachline("watersheds.wkt")
263+
]
264+
end
265+
end
266+
267+
watershed_polygons = only.(GI.getgeom.(watersheds))
268+
269+
import SortTileRecursiveTree as STR
270+
tree = STR.STRtree(watershed_polygons)
271+
query_result = STR.query(tree, GI.extent(watershed_polygons[1]))
272+
273+
GO.intersects.((watershed_polygons[1],), watershed_polygons[query_result])
274+
275+
@be GO.union($(watershed_polygons[1]), $(watershed_polygons[2]); target = $GI.PolygonTrait())
276+
@be LG.union($(watershed_polygons[1] |> GI.convert(LG)), $(watershed_polygons[2] |> GI.convert(LG)))
277+
278+
function union_coverage(intersection_f::IF, union_f::UF, polygons::Vector{T}; showplot = true, showprogress = true) where {T, IF, UF}
279+
tree = STR.STRtree(polygons)
280+
all_intersected = falses(length(polygons))
281+
accumulator = polygons[1]
282+
all_intersected[1] = true
283+
i = 1
284+
285+
(showprogress && (prog = Progress(length(all_intersected))))
286+
287+
for i in 1:length(polygons)
288+
query_result = STR.query(tree, GI.extent(accumulator))
289+
for idx in query_result
290+
if !(all_intersected[idx] || !(intersection_f(polygons[idx], accumulator)))
291+
result = union_f(polygons[idx], accumulator)
292+
accumulator = result
293+
all_intersected[idx] = true
294+
(showprogress && next!(prog))
295+
end
296+
end
297+
showplot && display(poly(view(polygons, all_intersected); color = rand(RGBf, sum(all_intersected))), axis = (; title = "$(GI.trait(accumulator) isa GI.PolygonTrait ? "Polygon" : "MultiPolygon with $(GI.ngeom(accumulator)) individual polys")"))
298+
all(all_intersected) && break # if done then finish
299+
end
300+
301+
return accumulator
302+
end
303+
304+
@time union_coverage(LG.intersects, LG.union, watershed_polygons .|> GI.convert(LG); showplot = false, showprogress = true)
305+
306+
@time union_coverage(GO.intersects, (x, y) -> (GO.union(x, y; target = GI.MultiPolygonTrait())), watershed_polygons; showplot = false, showprogress = true)
307+
308+
309+
using GADM
310+
311+
# austria is landlocked and will form a coverage
312+
# something like India will not -- because it has islands
313+
ind_fc = GADM.get("AUT"; depth = 1)
314+
ind_polys = GI.geometry.(GI.getfeature(ind_fc)) |> x -> GO.tuples(x; calc_extent = true)
315+
316+
317+
318+
@time union_coverage(GO.intersects, (x, y) -> (GO.union(x, y; target = GI.MultiPolygonTrait())), ind_polys; showplot = true, showprogress = true)

benchmarks/geometry_providers.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ end
139139

140140
function Makie.convert_arguments(::Makie.SampleBased, labels::AbstractVector{<: AbstractString}, bs::AbstractVector{<: Chairmarks.Benchmark})
141141
ts = map(b -> getproperty.(b.samples, :time), bs)
142-
labels =
143-
return flatten
142+
labels = reduce(vcat, (fill(l, length(t)) for (l, t) in zip(labels, ts)))
143+
return flatten(labels), flatten(ts)
144144
end
145145

146146
function Makie.convert_arguments(::Type{Makie.Errorbars}, xs, bs::AbstractVector{<: Chairmarks.Benchmark})

benchmarks/polygon_scaling.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ p26 = GI.Polygon([[(3.0, 1.0), (8.0, 1.0), (8.0, 7.0), (3.0, 7.0), (3.0, 5.0), (
77

88
suite = BenchmarkGroup(["title:Polygon intersection timing","subtitle:Single polygon, densified"])
99

10-
for max_distance in exp10.(LinRange(-1, 1.5, 10))
10+
for max_distance in exp10.(LinRange(-2, 1.5, 10))
1111
p25s = GO.segmentize(p25; max_distance)
1212
p26s = GO.segmentize(p26; max_distance)
1313
n_verts = GI.npoint(p25s)

0 commit comments

Comments
 (0)