Skip to content

Commit

Permalink
Geometry corrections, segmentization, type stability, benchmarks (Jul…
Browse files Browse the repository at this point in the history
…iaGeo#69)

# Change log:
## New features
- Add a geometry correction framework with `fix` and one method, `ClosedRing()`, for it.  Others can be added in future.
- Add a `segmentize` method that corresponds to LibGEOS's density, that upsamples any geometry more complex than a line.
- Add a benchmark framework which runs quickly and well, and can plot on CI.
	- Implement proof of concept benchmarks for some functions (`simplify` and `segmentize`).
## Code changes
- Factor `reproject`'s implementation out to an extension on Proj
	- Provide a nice error hint if Proj isn't loaded and a user tries to use `reproject`.
- Since we're getting rid of Proj, also factor out the GeodesicSegments implementation for `segementize`.
- Various comment changes and documentation improvements.
  • Loading branch information
asinghvi17 authored Mar 27, 2024
2 parents 4af2e75 + 1cf273c commit 1e506f0
Show file tree
Hide file tree
Showing 29 changed files with 1,132 additions and 107 deletions.
16 changes: 14 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,40 @@ authors = ["Anshul Singhvi <[email protected]> and contributors"]
version = "0.0.1"

[deps]
CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298"
ExactPredicates = "429591f6-91af-11e9-00e2-59fbe8cec110"
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Proj = "c94c279d-25a6-4763-9509-64d165bea63e"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[weakdeps]
Proj = "c94c279d-25a6-4763-9509-64d165bea63e"

[extensions]
GeometryOpsProjExt = "Proj"

[compat]
CoordinateTransformations = "0.5, 0.6"
ExactPredicates = "2"
GeoInterface = "1.2"
GeometryBasics = "0.4.7"
LinearAlgebra = "1"
Proj = "1"
Statistics = "1"
julia = "1.9"

[extras]
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
GeoFormatTypes = "68eda718-8dee-11e9-39e7-89f7f65f511f"
GeoJSON = "61d90e0f-e114-555e-ac52-39dfb47a3ef9"
Proj = "c94c279d-25a6-4763-9509-64d165bea63e"
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
LibGEOS = "a90b1aa1-3769-5649-ba7e-abc5a9d163eb"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["ArchGDAL", "Distributions", "GeoFormatTypes", "GeoJSON", "JLD2", "LibGEOS", "Random", "Test"]
test = ["ArchGDAL", "CoordinateTransformations", "Distributions", "GeoFormatTypes", "GeoJSON", "Proj", "JLD2", "LibGEOS", "Random", "Test"]
149 changes: 149 additions & 0 deletions benchmarks/benchmark_plots.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using Printf, Statistics
using Makie, MakieThemes
using BenchmarkTools, Chairmarks

function compute_gridsize(numplts::Int, nr::Int, nc::Int)
# figure out how many rows/columns we need
if nr < 1
if nc < 1
nr = round(Int, sqrt(numplts))
nc = ceil(Int, numplts / nr)
else
nr = ceil(Int, numplts / nc)
end
else
nc = ceil(Int, numplts / nr)
end
nr, nc
end

function _prettytime(t::Real)
if t < 1e-6
value, units = t * 1e9, "ns"
elseif t < 1e-3
value, units = t * 1e6, "μs"
elseif t < 1
value, units = t * 1e3, "ms"
else
value, units = t * 1, "s"
end
return string(@sprintf("%.1f", value), " ", units)
end

_prettytime(ts::AbstractArray{<: Real}) = _prettytime.(ts)


function results_to_numbers(result::BenchmarkGroup, postprocess_times = Statistics.median, postprocess_numbers = identity)
# First, we extract the keys from the result.
# It's assumed that there is only one key per result, and that it's a number.
numbers = identity.(collect(keys(result)))
@assert numbers isa AbstractVector{<: Number} """
Extra keys involved in the benchmark group!
Provide a pure group with only numerical keys.
Got key types:
$(unique(typeof.(numbers)))
"""
# We sort the numbers, and then we use them to get the results
sort!(numbers)
result_objects = getindex.((result,), numbers)
# Now, we get the times, and return their medians.
postprocessed_objects = postprocess_times.(result_objects)
return postprocess_numbers.(numbers), getproperty.(postprocessed_objects, :time)
end

# function plot_trials(results; title = )



"""
plot_trials(result, title_str; theme = MakieThemes.bbc())::Figure
This function takes `result::BenchmarkTools.BenchmarkGroup` and plots the trials. It returns the figure.
"""
plot_trials(results; kwargs...) = begin
fig = Figure()
plot_trials(fig[1, 1], results; kwargs...)
fig
end

function plot_trials(
gp::Makie.GridPosition,
results;
theme = merge(deepcopy(Makie.CURRENT_DEFAULT_THEME), MakieThemes.bbc()),
legend_position = Makie.automatic, #(1, 1, TopRight()),
legend_orientation = :horizontal,
legend_halign = 1.0,
legend_valign = -0.25,
)

xs, ys, labels = [], [], []
for label in keys(results)
current_result = results[label]
if isempty(current_result)
@warn "ResultSet with key $label is empty, skipping."
continue
end
x, y = results_to_numbers(current_result)
push!(xs, x)
push!(ys, y)
push!(labels, label)
end

tag_attrs = capture_tag_attrs(results.tags)
tag_theme = merge(theme, tag_attrs)

lp = if legend_position isa Makie.Automatic
gp.layout[gp.span.rows, gp.span.cols, TopRight()]
elseif legend_position isa Tuple
gp.layout[legend_position...]
elseif legend_position isa Union{Makie.GridPosition, Makie.GridSubposition}
legend_position
else
error()
end

return Makie.with_theme(tag_theme) do
ax = Axis(
gp;
tag_attrs.Axis...,
xlabel = "Number of points", ylabel = "Time to calculate",
xscale = log10, yscale = log10, ytickformat = _prettytime,
)
plots = [scatterlines!(ax, x, y; label = label) for (x, y, label) in zip(xs, ys, labels)]
setproperty!.(getindex.(getproperty.(plots, :plots), 1), :alpha, 0.1)
leg = Legend(
lp, ax;
tellwidth = legend_position isa Union{Tuple, Makie.Automatic} && (legend_position isa Makie.Automatic || length(legend_position) != 3) && legend_orientation == :vertical,
tellheight = legend_position isa Union{Tuple, Makie.Automatic} && (legend_position isa Makie.Automatic || length(legend_position) != 3) && legend_orientation == :horizontal,
halign = legend_halign,
valign = legend_valign,
orientation = legend_orientation
)
ax.xticksvisible[] = true
ax.xtickcolor[] = ax.xgridcolor[]
ax.xticklabelsvisible[] = true
ax.yticks[] = Makie.LogTicks(Makie.WilkinsonTicks(7; k_min = 4))
ax.ygridwidth[] = 0.75
return ax
end
end

const _tag_includelist = ["title", "subtitle"]

function capture_tag_attrs(tags)
attr_dict = Attributes()
axis = attr_dict.Axis = Attributes()
for tag in tags
for possibility in sort(_tag_includelist; by = length)
if startswith(tag, possibility)
axis[Symbol(possibility)] = tag[(length(possibility) + 2):end]
break
end
end
end
return attr_dict
end

function decompose_benchmarksuite_to_2ndlevel(result)
# here, `result` is a BenchmarkGroup.

end
Loading

0 comments on commit 1e506f0

Please sign in to comment.