|
10 | 10 | import GeometryOps as GO,
|
11 | 11 | GeoInterface as GI,
|
12 | 12 | 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 |
15 | 17 | # In order to benchmark, we'll actually use the new [Chairmarks.jl](https://github.com/lilithhafner/Chairmarks.jl),
|
16 | 18 | # since it's significantly faster than BenchmarkTools. To keep benchmarks organized, though, we'll still use BenchmarkTools'
|
17 | 19 | # `BenchmarkGroup` structure.
|
18 | 20 | using Chairmarks
|
19 | 21 | import BenchmarkTools: BenchmarkGroup
|
| 22 | +using ProgressMeter |
20 | 23 | # We use CairoMakie to visualize our results!
|
21 | 24 | using CairoMakie, MakieThemes, GeoInterfaceMakie
|
22 | 25 | # Finally, we import some general utility packages:
|
23 | 26 | using Statistics, CoordinateTransformations
|
24 | 27 |
|
| 28 | +include("benchmark_plots.jl") |
| 29 | + |
25 | 30 | # We also set up some utility functions for later on.
|
26 | 31 | """
|
27 | 32 | Returns LibGEOS and GeometryOps' native geometries,
|
@@ -155,7 +160,7 @@ circle_difference_suite = circle_suite["difference"] = BenchmarkGroup(["title:Ci
|
155 | 160 | circle_intersection_suite = circle_suite["intersection"] = BenchmarkGroup(["title:Circle intersection", "subtitle:Tested on a regular circle"])
|
156 | 161 | circle_union_suite = circle_suite["union"] = BenchmarkGroup(["title:Circle union", "subtitle:Tested on a regular circle"])
|
157 | 162 |
|
158 |
| -n_points_values = round.(Int, exp10.(LinRange(1, 4, 10))) |
| 163 | +n_points_values = round.(Int, exp10.(LinRange(0.7, 6, 15))) |
159 | 164 | @time for n_points in n_points_values
|
160 | 165 | circle = GI.Wrappers.Polygon([tuple.((cos(θ) for θ in LinRange(0, 2π, n_points)), (sin(θ) for θ in LinRange(0, 2π, n_points)))])
|
161 | 166 | closed_circle = GO.ClosedRing()(circle)
|
|
197 | 202 | # Now, we get to benchmarking:
|
198 | 203 |
|
199 | 204 |
|
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); |
202 | 207 |
|
203 | 208 | # 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) |
0 commit comments