From 5e41a15e2ffa8924ed7bd5621dba1efbe15a9d96 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sun, 4 Jun 2023 19:28:50 -0400 Subject: [PATCH] Barycentric coordinates (#10) * Make `contains` more geointerfacey * Mean value method for barycentric coordinates On arbitrary polygons * Clean up + improve documentation * Fix chatgpt numbering bug (this is some pretty bad stuff) * Add a method for true polys * Update make.jl * Hormann method works well * Reorder some code and comments * Add tests * Reorder, remove cruft, get this working * Include and export * Add README and logo * Fix tests * Remove MakieThemes * Fix the doctest setup x * Don't use GB and Rasters in the bary example * Don't test on nightly * Fix examples * More doc fixes * Display figure to reset limits in docs * Move doctests to tests file * Test on v1.6 too * Fix syntax for v1.6 in simplify.jl * Literateify * Remove codecov files * Fix the plotted barycentric example * Set the default method to mean value --- .github/workflows/CI.yml | 3 +- Project.toml | 2 + README.md | 27 ++ docs/make.jl | 34 ++- docs/src/assets/logo.png | Bin 0 -> 87252 bytes src/GeometryOps.jl | 6 + src/methods/barycentric.jl | 454 +++++++++++++++++++++++++++++++ src/methods/centroid.jl | 2 + src/methods/contains.jl | 44 +-- src/methods/signed_area.jl | 6 +- src/methods/signed_distance.jl | 2 + src/primitives.jl | 4 + src/transformations/flip.jl | 5 + src/transformations/reproject.jl | 8 + src/transformations/simplify.jl | 58 ++-- src/utils.jl | 1 + test/methods/barycentric.jl | 26 ++ test/runtests.jl | 8 +- 18 files changed, 640 insertions(+), 50 deletions(-) create mode 100644 README.md create mode 100644 docs/src/assets/logo.png create mode 100644 src/methods/barycentric.jl create mode 100644 test/methods/barycentric.jl diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 85b1594e5..eaefcae5f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -18,8 +18,9 @@ jobs: fail-fast: false matrix: version: + - '1.6' - '1' - - 'nightly' + # - 'nightly' os: - ubuntu-latest arch: diff --git a/Project.toml b/Project.toml index 887eb4b81..fb0713aa3 100644 --- a/Project.toml +++ b/Project.toml @@ -6,7 +6,9 @@ version = "0.0.1-DEV" [deps] 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" [compat] GeometryBasics = "0.4.7" diff --git a/README.md b/README.md new file mode 100644 index 000000000..a18bd67a0 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +## GeometryOps.jl + + +[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://asinghvi17.github.io/GeometryOps.jl/stable/) +[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://asinghvi17.github.io/GeometryOps.jl/dev/) +[![Build Status](https://github.com/asinghvi17/GeometryOps.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/asinghvi17/GeometryOps.jl/actions/workflows/CI.yml?query=branch%3Amain) + +GeometryOps logo + +GeometryOps.jl is a package for geometric calculations on (primarily 2D) geometries. + +The driving idea behind this package is to unify all the disparate packages for geometric calculations in Julia, and make them GeoInterface.jl-compatible. We seem to be focusing primarily on 2/2.5D geometries for now. + +Most of the usecases are driven by GIS and similar Earth data workflows, so this might be a bit specialized towards that, but methods should always be general to any coordinate space. + +## Methods + +- Signed area, centroid, distance, etc +- Iteration into geometries (`apply`) +- Line and polygon simplification +- Generalized barycentric coordinates in polygons + +### Planned additions + +- OGC methods (crosses, contains, intersects, etc) +- Polygon union, intersection and clipping +- Arclength interpolation (absolute and relative) diff --git a/docs/make.jl b/docs/make.jl index fc76d61db..d1ad2ed52 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -2,9 +2,20 @@ using GeometryOps using Documenter using Literate using Makie, CairoMakie -CairoMakie.activate!(px_per_unit = 2, type = "png") # TODO: make this svg +CairoMakie.activate!(px_per_unit = 2, type = "png", inline = true) # TODO: make this svg -DocMeta.setdocmeta!(GeometryOps, :DocTestSetup, :(using GeometryOps); recursive=true) +DocMeta.setdocmeta!(GeometryOps, :DocTestSetup, :(using GeometryOps; using GeometryOps.GeometryBasics); recursive=true) + +# First, remove any codecov files that may have been generated by the CI run +for (root, dirs, files) in walkdir(dirname(@__DIR__)) # walk through `GeometryOps/*` + # Iterate through all files in the current directory + for file in files + # If the file is a codecov file, remove it + if splitext(file)[2] == ".cov" + rm(joinpath(root, file)) + end + end +end source_path = joinpath(dirname(@__DIR__), "src") output_path = joinpath(@__DIR__, "src", "source") @@ -12,13 +23,15 @@ mkpath(output_path) literate_pages = String[] -for (root_path, dirs, files) in walkdir(source_path) - output_dir = joinpath(output_path, relpath(root_path, source_path)) - for file in files - # convert the source file to Markdown - Literate.markdown(joinpath(root_path, file), output_dir; documenter = false) - # TODO: make this respect nesting somehow! - push!(literate_pages, joinpath("source", relpath(root_path, source_path), splitext(file)[1] * ".md")) +withenv("JULIA_DEBUG" => "Literate") do # allow Literate debug output to escape to the terminal! + for (root_path, dirs, files) in walkdir(source_path) + output_dir = joinpath(output_path, relpath(root_path, source_path)) + for file in files + # convert the source file to Markdown + Literate.markdown(joinpath(root_path, file), output_dir; documenter = false) + # TODO: make this respect nesting somehow! + push!(literate_pages, joinpath("source", relpath(root_path, source_path), splitext(file)[1] * ".md")) + end end end @@ -36,7 +49,8 @@ makedocs(; pages=[ "Home" => "index.md", "Source code" => literate_pages - ] + ], + strict=false, ) deploydocs(; diff --git a/docs/src/assets/logo.png b/docs/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8a3120119f5fb9fc512cf7c86c6adb6396b1c663 GIT binary patch literal 87252 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 G%zps%(o>rN literal 0 HcmV?d00001 diff --git a/src/GeometryOps.jl b/src/GeometryOps.jl index 4031c384d..983b6a99e 100644 --- a/src/GeometryOps.jl +++ b/src/GeometryOps.jl @@ -1,18 +1,24 @@ +# # GeometryOps.jl + module GeometryOps using GeoInterface using GeometryBasics import Proj +using LinearAlgebra const GI = GeoInterface const GB = GeometryBasics include("primitives.jl") include("utils.jl") + include("methods/signed_distance.jl") include("methods/signed_area.jl") include("methods/centroid.jl") include("methods/contains.jl") +include("methods/barycentric.jl") + include("transformations/flip.jl") include("transformations/simplify.jl") include("transformations/reproject.jl") diff --git a/src/methods/barycentric.jl b/src/methods/barycentric.jl new file mode 100644 index 000000000..8a91a701c --- /dev/null +++ b/src/methods/barycentric.jl @@ -0,0 +1,454 @@ +# # Barycentric coordinates + +export barycentric_coordinates, barycentric_coordinates!, barycentric_interpolate +export MeanValue + +# Generalized barycentric coordinates are a generalization of barycentric coordinates, +# which are typically used in triangles, to arbitrary polygons. + +# They provide a way to express a point within a polygon as a weighted average +# of the polygon's vertices. + +# In the case of a triangle, barycentric coordinates are a set of three numbers +# $(λ_1, λ_2, λ_3)$, each associated with a vertex of the triangle. Any point within +# the triangle can be expressed as a weighted average of the vertices, where the +# weights are the barycentric coordinates. The weights sum to 1, and each is non-negative. + +# For a polygon with $n$ vertices, generalized barycentric coordinates are a set of +# $n$ numbers $(λ_1, λ_2, ..., λ_n)$, each associated with a vertex of the polygon. +# Any point within the polygon can be expressed as a weighted average of the vertices, +# where the weights are the generalized barycentric coordinates. + +# As with the triangle case, the weights sum to 1, and each is non-negative. + + +# ## Example +# This example was taken from [this page of CGAL's documentation](https://doc.cgal.org/latest/Barycentric_coordinates_2/index.html). + +# ```@example barycentric +# using GeometryOps, Makie +# using GeometryOps.GeometryBasics +# # Define a polygon +# polygon_points = Point3f[ +# (0.03, 0.05, 0.00), (0.07, 0.04, 0.02), (0.10, 0.04, 0.04), +# (0.14, 0.04, 0.06), (0.17, 0.07, 0.08), (0.20, 0.09, 0.10), +# (0.22, 0.11, 0.12), (0.25, 0.11, 0.14), (0.27, 0.10, 0.16), +# (0.30, 0.07, 0.18), (0.31, 0.04, 0.20), (0.34, 0.03, 0.22), +# (0.37, 0.02, 0.24), (0.40, 0.03, 0.26), (0.42, 0.04, 0.28), +# (0.44, 0.07, 0.30), (0.45, 0.10, 0.32), (0.46, 0.13, 0.34), +# (0.46, 0.19, 0.36), (0.47, 0.26, 0.38), (0.47, 0.31, 0.40), +# (0.47, 0.35, 0.42), (0.45, 0.37, 0.44), (0.41, 0.38, 0.46), +# (0.38, 0.37, 0.48), (0.35, 0.36, 0.50), (0.32, 0.35, 0.52), +# (0.30, 0.37, 0.54), (0.28, 0.39, 0.56), (0.25, 0.40, 0.58), +# (0.23, 0.39, 0.60), (0.21, 0.37, 0.62), (0.21, 0.34, 0.64), +# (0.23, 0.32, 0.66), (0.24, 0.29, 0.68), (0.27, 0.24, 0.70), +# (0.29, 0.21, 0.72), (0.29, 0.18, 0.74), (0.26, 0.16, 0.76), +# (0.24, 0.17, 0.78), (0.23, 0.19, 0.80), (0.24, 0.22, 0.82), +# (0.24, 0.25, 0.84), (0.21, 0.26, 0.86), (0.17, 0.26, 0.88), +# (0.12, 0.24, 0.90), (0.07, 0.20, 0.92), (0.03, 0.15, 0.94), +# (0.01, 0.10, 0.97), (0.02, 0.07, 1.00)] +# # Plot it! +# # First, we'll plot the polygon using Makie's rendering: +# f, a1, p1 = poly( +# polygon_points; +# color = last.(polygon_points), colormap = cgrad(:jet, 18; categorical = true), +# axis = (; +# aspect = DataAspect(), title = "Makie mesh based polygon rendering", subtitle = "CairoMakie" +# ), +# figure = (; resolution = (800, 400),) +# ) +# +# Makie.update_state_before_display!(f) # We have to call this explicitly, to get the axis limits correct +# # Now that we've plotted the first polygon, +# # we can render it using barycentric coordinates. +# a1_bbox = a1.finallimits[] # First we get the extent of the axis +# ext = GeometryOps.GI.Extent(NamedTuple{(:X, :Y)}(zip(minimum(a1_bbox), maximum(a1_bbox)))) +# +# a2, p2box = poly( # Now, we plot a cropping rectangle around the axis so we only show the polygon +# f[1, 2], +# GeometryOps.GeometryBasics.Polygon( # This is a rectangle with an internal hole shaped like the polygon. +# Point2f[(ext.X[1], ext.Y[1]), (ext.X[2], ext.Y[1]), (ext.X[2], ext.Y[2]), (ext.X[1], ext.Y[2]), (ext.X[1], ext.Y[1])], +# [reverse(Point2f.(polygon_points))] +# ); +# color = :white, xautolimits = false, yautolimits = false, +# axis = (; +# aspect = DataAspect(), title = "Barycentric coordinate based polygon rendering", subtitle = "GeometryOps", +# limits = (ext.X, ext.Y), +# ) +# ) +# hidedecorations!(a1) +# hidedecorations!(a2) +# cb = Colorbar(f[2, :], p1.plots[1]; vertical = false, flipaxis = true) +# # Finally, we perform barycentric interpolation on a grid, +# xrange = LinRange(ext.X..., widths(a2.scene.px_area[])[1] * 4) # 2 rendered pixels per "physical" pixel +# yrange = LinRange(ext.Y..., widths(a2.scene.px_area[])[2] * 4) # 2 rendered pixels per "physical" pixel +# @time mean_values = barycentric_interpolate.( +# (MeanValue(),), # The barycentric coordinate algorithm (MeanValue is the only one for now) +# (Point2f.(polygon_points),), # The polygon points as `Point2f` +# (last.(polygon_points,),), # The values per polygon point - can be anything which supports addition and division +# Point2f.(xrange, yrange') # The points at which to interpolate +# ) +# # and render! +# hm = heatmap!( +# a2, xrange, yrange, mean_values; +# colormap = p1.colormap, # Use the same colormap as the original polygon plot +# colorrange = p1.plots[1].colorrange[], # Access the rendered mesh plot's colorrange directly +# transformation = (; translation = Vec3f(0,0,-1)), # This gets the heatmap to render "behind" the previously plotted polygon +# xautolimits = false, yautolimits = false +# ) +# f +# ``` + +# ## Barycentric-coordinate API +# In some cases, we actually want barycentric interpolation, and have no interest +# in the coordinates themselves. +# +# However, the coordinates can be useful for debugging, and when performing 3D rendering, +# multiple barycentric values (depth, uv) are needed for depth buffering. +# + +const _VecTypes = Union{Tuple{Vararg{T, N}}, GeometryBasics.StaticArraysCore.StaticArray{Tuple{N}, T, 1}} where {N, T} + +""" + abstract type AbstractBarycentricCoordinateMethod + +Abstract supertype for barycentric coordinate methods. +The subtypes may serve as dispatch types, or may cache +some information about the target polygon. + +## API +The following methods must be implemented for all subtypes: +- `barycentric_coordinates!(λs::Vector{<: Real}, method::AbstractBarycentricCoordinateMethod, exterior::Vector{<: Point{2, T1}}, point::Point{2, T2})` +- `barycentric_interpolate(method::AbstractBarycentricCoordinateMethod, exterior::Vector{<: Point{2, T1}}, values::Vector{V}, point::Point{2, T2})::V` +- `barycentric_interpolate(method::AbstractBarycentricCoordinateMethod, exterior::Vector{<: Point{2, T1}}, interiors::Vector{<: Vector{<: Point{2, T1}}} values::Vector{V}, point::Point{2, T2})::V` +The rest of the methods will be implemented in terms of these, and have efficient dispatches for broadcasting. +""" +abstract type AbstractBarycentricCoordinateMethod end + + +Base.@propagate_inbounds function barycentric_coordinates!(λs::Vector{<: Real}, method::AbstractBarycentricCoordinateMethod, polypoints::AbstractVector{<: Point{N1, T1}}, point::Point{N2, T2}) where {N1, N2, T1 <: Real, T2 <: Real} + @boundscheck @assert length(λs) == length(polypoints) + @boundscheck @assert length(polypoints) >= 3 + + @error("Not implemented yet for method $(method).") +end +Base.@propagate_inbounds barycentric_coordinates!(λs::Vector{<: Real}, polypoints::AbstractVector{<: Point{N1, T1}}, point::Point{N2, T2}) where {N1, N2, T1 <: Real, T2 <: Real} = barycentric_coordinates!(λs, MeanValue(), polypoints, point) + +Base.@propagate_inbounds function barycentric_coordinates(method::AbstractBarycentricCoordinateMethod, polypoints::AbstractVector{<: Point{N1, T1}}, point::Point{N2, T2}) where {N1, N2, T1 <: Real, T2 <: Real} + λs = zeros(promote_type(T1, T2), length(polypoints)) + barycentric_coordinates!(λs, method, polypoints, point) + return λs +end +Base.@propagate_inbounds barycentric_coordinates(polypoints::AbstractVector{<: Point{N1, T1}}, point::Point{N2, T2}) where {N1, N2, T1 <: Real, T2 <: Real} = barycentric_coordinates(MeanValue(), polypoints, point) + +Base.@propagate_inbounds function barycentric_interpolate(method::AbstractBarycentricCoordinateMethod, polypoints::AbstractVector{<: Point{N, T1}}, values::AbstractVector{V}, point::Point{N, T2}) where {N, T1 <: Real, T2 <: Real, V} + @boundscheck @assert length(values) == length(polypoints) + @boundscheck @assert length(polypoints) >= 3 + λs = barycentric_coordinates(method, polypoints, point) + return sum(λs .* values) +end +Base.@propagate_inbounds barycentric_interpolate(polypoints::AbstractVector{<: Point{N, T1}}, values::AbstractVector{V}, point::Point{N, T2}) where {N, T1 <: Real, T2 <: Real, V} = barycentric_interpolate(MeanValue(), polypoints, values, point) + +Base.@propagate_inbounds function barycentric_interpolate(method::AbstractBarycentricCoordinateMethod, exterior::AbstractVector{<: Point{N, T1}}, interiors::AbstractVector{<: Point{N, T1}}, values::AbstractVector{V}, point::Point{N, T2}) where {N, T1 <: Real, T2 <: Real, V} + @boundscheck @assert length(values) == length(exterior) + isempty(interiors) ? 0 : sum(length.(interiors)) + @boundscheck @assert length(exterior) >= 3 + λs = barycentric_coordinates(method, exterior, interiors, point) + return sum(λs .* values) +end +Base.@propagate_inbounds barycentric_interpolate(exterior::AbstractVector{<: Point{N, T1}}, interiors::AbstractVector{<: Point{N, T1}}, values::AbstractVector{V}, point::Point{N, T2}) where {N, T1 <: Real, T2 <: Real, V} = barycentric_interpolate(MeanValue(), exterior, interiors, values, point) + +Base.@propagate_inbounds function barycentric_interpolate(method::AbstractBarycentricCoordinateMethod, polygon::Polygon{2, T1}, values::AbstractVector{V}, point::Point{2, T2}) where {T1 <: Real, T2 <: Real, V} + exterior = decompose(Point{2, promote_type(T1, T2)}, polygon.exterior) + if isempty(polygon.interiors) + @boundscheck @assert length(values) == length(exterior) + return barycentric_interpolate(method, exterior, values, point) + else # the poly has interiors + interiors = reverse.(decompose.((Point{2, promote_type(T1, T2)},), polygon.interiors)) + @boundscheck @assert length(values) == length(exterior) + sum(length.(interiors)) + return barycentric_interpolate(method, exterior, interiors, values, point) + end +end +Base.@propagate_inbounds barycentric_interpolate(polygon::Polygon{2, T1}, values::AbstractVector{V}, point::Point{2, T2}) where {T1 <: Real, T2 <: Real, V} = barycentric_interpolate(MeanValue(), polygon, values, point) + +# 3D polygons are considered to have their vertices in the XY plane, +# and the Z coordinate must represent some value. This is to say that +# the Z coordinate is interpreted as an M coordinate. +Base.@propagate_inbounds function barycentric_interpolate(method::AbstractBarycentricCoordinateMethod, polygon::Polygon{3, T1}, point::Point{2, T2}) where {T1 <: Real, T2 <: Real} + exterior_point3s = decompose(Point{3, promote_type(T1, T2)}, polygon.exterior) + exterior_values = getindex.(exterior_point3s, 3) + exterior_points = Point2f.(exterior_point3s) + if isempty(polygon.interiors) + return barycentric_interpolate(method, exterior_points, exterior_values, point) + else # the poly has interiors + interior_point3s = decompose.((Point{3, promote_type(T1, T2)},), polygon.interiors) + interior_values = collect(Iterators.flatten((getindex.(point3s, 3) for point3s in interior_point3s))) + interior_points = map(point3s -> Point2f.(point3s), interior_point3s) + return barycentric_interpolate(method, exterior_points, interior_points, vcat(exterior_values, interior_values), point) + end +end +Base.@propagate_inbounds barycentric_interpolate(polygon::Polygon{3, T1}, point::Point{2, T2}) where {T1 <: Real, T2 <: Real} = barycentric_interpolate(MeanValue(), polygon, point) + +# This method is the one which supports GeoInterface. +Base.@propagate_inbounds function barycentric_interpolate(method::AbstractBarycentricCoordinateMethod, polygon, values::AbstractVector{V}, point) where V + @assert GeoInterface.trait(polygon) isa GeoInterface.PolygonTrait + @assert GeoInterface.trait(point) isa GeoInterface.PointTrait + passable_polygon = GeoInterface.convert(GeometryBasics, polygon) + @assert passable_polygon isa GeometryBasics.Polygon "The polygon was converted to a $(typeof(passable_polygon)), which is not a `GeometryBasics.Polygon`." + ## first_poly_point = GeoInterface.getpoint(GeoInterface.getexterior(polygon)) + passable_point = GeoInterface.convert(GeometryBasics, point) + return barycentric_interpolate(method, passable_polygon, Point2(passable_point)) +end +Base.@propagate_inbounds barycentric_interpolate(polygon, values::AbstractVector{V}, point) where V = barycentric_interpolate(MeanValue(), polygon, values, point) + +""" + weighted_mean(weight::Real, x1, x2) + +Returns the weighted mean of `x1` and `x2`, where `weight` is the weight of `x1`. + +Specifically, calculates `x1 * weight + x2 * (1 - weight)`. + +!!! note + The idea for this method is that you can override this for custom types, like Color types, in extension modules. +""" +function weighted_mean(weight::WT, x1, x2) where {WT <: Real} + return muladd(x1, weight, x2 * (oneunit(WT) - weight)) +end + + +""" + MeanValue() <: AbstractBarycentricCoordinateMethod + +This method calculates barycentric coordinates using the mean value method. + +## References + +""" +struct MeanValue <: AbstractBarycentricCoordinateMethod +end + +# Before we go to the actual implementation, there are some quick and simple utility functions +# that we need to implement. These are mainly for convenience and code brevity. + +""" + _det(s1::Point2{T1}, s2::Point2{T2}) where {T1 <: Real, T2 <: Real} + +Returns the determinant of the matrix formed by `hcat`'ing two points `s1` and `s2`. + +Specifically, this is: +```julia +s1[1] * s2[2] - s1[2] * s2[1] +``` +""" +function _det(s1::_VecTypes{2, T1}, s2::_VecTypes{2, T2}) where {T1 <: Real, T2 <: Real} + return s1[1] * s2[2] - s1[2] * s2[1] +end + +""" + t_value(sᵢ, sᵢ₊₁, rᵢ, rᵢ₊₁) + +Returns the "T-value" as described in Hormann's presentation [^HormannPresentation] on how to calculate +the mean-value coordinate. + +Here, `sᵢ` is the vector from vertex `vᵢ` to the point, and `rᵢ` is the norm (length) of `sᵢ`. +`s` must be `Point` and `r` must be real numbers. + +```math +tᵢ = \\frac{\\mathrm{det}\\left(sᵢ, sᵢ₊₁\\right)}{rᵢ * rᵢ₊₁ + sᵢ ⋅ sᵢ₊₁} +``` + +[^HormannPresentation]: K. Hormann and N. Sukumar. Generalized Barycentric Coordinates in Computer Graphics and Computational Mechanics. Taylor & Fancis, CRC Press, 2017. +``` + +""" +function t_value(sᵢ::_VecTypes{N, T1}, sᵢ₊₁::_VecTypes{N, T1}, rᵢ::T2, rᵢ₊₁::T2) where {N, T1 <: Real, T2 <: Real} + return _det(sᵢ, sᵢ₊₁) / muladd(rᵢ, rᵢ₊₁, dot(sᵢ, sᵢ₊₁)) +end + + +function barycentric_coordinates!(λs::Vector{<: Real}, ::MeanValue, polypoints::AbstractVector{<: Point{2, T1}}, point::Point{2, T2}) where {T1 <: Real, T2 <: Real} + @boundscheck @assert length(λs) == length(polypoints) + @boundscheck @assert length(polypoints) >= 3 + n_points = length(polypoints) + ## Initialize counters and register variables + ## Points - these are actually vectors from point to vertices + ## polypoints[i-1], polypoints[i], polypoints[i+1] + sᵢ₋₁ = polypoints[end] - point + sᵢ = polypoints[begin] - point + sᵢ₊₁ = polypoints[begin+1] - point + ## radius / Euclidean distance between points. + rᵢ₋₁ = norm(sᵢ₋₁) + rᵢ = norm(sᵢ ) + rᵢ₊₁ = norm(sᵢ₊₁) + ## Perform the first computation explicitly, so we can cut down on + ## a mod in the loop. + λs[1] = (t_value(sᵢ₋₁, sᵢ, rᵢ₋₁, rᵢ) + t_value(sᵢ, sᵢ₊₁, rᵢ, rᵢ₊₁)) / rᵢ + ## Loop through the rest of the vertices, compute, store in λs + for i in 2:n_points + ## Increment counters + set variables + sᵢ₋₁ = sᵢ + sᵢ = sᵢ₊₁ + sᵢ₊₁ = polypoints[mod1(i+1, n_points)] - point + rᵢ₋₁ = rᵢ + rᵢ = rᵢ₊₁ + rᵢ₊₁ = norm(sᵢ₊₁) # radius / Euclidean distance between points. + λs[i] = (t_value(sᵢ₋₁, sᵢ, rᵢ₋₁, rᵢ) + t_value(sᵢ, sᵢ₊₁, rᵢ, rᵢ₊₁)) / rᵢ + end + ## Normalize λs to the 1-norm (sum=1) + λs ./= sum(λs) + return λs +end + +# ```julia +# function barycentric_coordinates(::MeanValue, polypoints::NTuple{N, Point{2, T2}}, point::Point{2, T1},) where {N, T1, T2} +# ## Initialize counters and register variables +# ## Points - these are actually vectors from point to vertices +# ## polypoints[i-1], polypoints[i], polypoints[i+1] +# sᵢ₋₁ = polypoints[end] - point +# sᵢ = polypoints[begin] - point +# sᵢ₊₁ = polypoints[begin+1] - point +# ## radius / Euclidean distance between points. +# rᵢ₋₁ = norm(sᵢ₋₁) +# rᵢ = norm(sᵢ ) +# rᵢ₊₁ = norm(sᵢ₊₁) +# λ₁ = (t_value(sᵢ₋₁, sᵢ, rᵢ₋₁, rᵢ) + t_value(sᵢ, sᵢ₊₁, rᵢ, rᵢ₊₁)) / rᵢ +# λs = ntuple(N) do i +# if i == 1 +# return λ₁ +# end +# ## Increment counters + set variables +# sᵢ₋₁ = sᵢ +# sᵢ = sᵢ₊₁ +# sᵢ₊₁ = polypoints[mod1(i+1, N)] - point +# rᵢ₋₁ = rᵢ +# rᵢ = rᵢ₊₁ +# rᵢ₊₁ = norm(sᵢ₊₁) # radius / Euclidean distance between points. +# return (t_value(sᵢ₋₁, sᵢ, rᵢ₋₁, rᵢ) + t_value(sᵢ, sᵢ₊₁, rᵢ, rᵢ₊₁)) / rᵢ +# end +# +# ∑λ = sum(λs) +# +# return ntuple(N) do i +# λs[i] / ∑λ +# end +# end +# ``` + +# This performs an inplace accumulation, using less memory and is faster. +# That's particularly good if you are using a polygon with a large number of points... +function barycentric_interpolate(::MeanValue, polypoints::AbstractVector{<: Point{2, T1}}, values::AbstractVector{V}, point::Point{2, T2}) where {T1 <: Real, T2 <: Real, V} + @boundscheck @assert length(values) == length(polypoints) + @boundscheck @assert length(polypoints) >= 3 + + n_points = length(polypoints) + ## Initialize counters and register variables + ## Points - these are actually vectors from point to vertices + ## polypoints[i-1], polypoints[i], polypoints[i+1] + sᵢ₋₁ = polypoints[end] - point + sᵢ = polypoints[begin] - point + sᵢ₊₁ = polypoints[begin+1] - point + ## radius / Euclidean distance between points. + rᵢ₋₁ = norm(sᵢ₋₁) + rᵢ = norm(sᵢ ) + rᵢ₊₁ = norm(sᵢ₊₁) + ## Now, we set the interpolated value to the first point's value, multiplied + ## by the weight computed relative to the first point in the polygon. + wᵢ = (t_value(sᵢ₋₁, sᵢ, rᵢ₋₁, rᵢ) + t_value(sᵢ, sᵢ₊₁, rᵢ, rᵢ₊₁)) / rᵢ + wₜₒₜ = wᵢ + interpolated_value = values[begin] * wᵢ + for i in 2:n_points + ## Increment counters + set variables + sᵢ₋₁ = sᵢ + sᵢ = sᵢ₊₁ + sᵢ₊₁ = polypoints[mod1(i+1, n_points)] - point + rᵢ₋₁ = rᵢ + rᵢ = rᵢ₊₁ + rᵢ₊₁ = norm(sᵢ₊₁) + ## Now, we calculate the weight: + wᵢ = (t_value(sᵢ₋₁, sᵢ, rᵢ₋₁, rᵢ) + t_value(sᵢ, sᵢ₊₁, rᵢ, rᵢ₊₁)) / rᵢ + ## perform a weighted sum with the interpolated value: + interpolated_value += values[i] * wᵢ + ## and add the weight to the total weight accumulator. + wₜₒₜ += wᵢ + end + ## Return the normalized interpolated value. + return interpolated_value / wₜₒₜ +end + +# When you have holes, then you have to be careful +# about the order you iterate around points. + +# Specifically, you have to iterate around each linear ring separately +# and ensure there are no degenerate/repeated points at the start and end! + +function barycentric_interpolate(::MeanValue, exterior::AbstractVector{<: Point{N, T1}}, interiors::AbstractVector{<: AbstractVector{<: Point{N, T1}}}, values::AbstractVector{V}, point::Point{N, T2}) where {N, T1 <: Real, T2 <: Real, V} + ## @boundscheck @assert length(values) == (length(exterior) + isempty(interiors) ? 0 : sum(length.(interiors))) + ## @boundscheck @assert length(exterior) >= 3 + + current_index = 1 + l_exterior = length(exterior) + + sᵢ₋₁ = exterior[end] - point + sᵢ = exterior[begin] - point + sᵢ₊₁ = exterior[begin+1] - point + rᵢ₋₁ = norm(sᵢ₋₁) # radius / Euclidean distance between points. + rᵢ = norm(sᵢ ) # radius / Euclidean distance between points. + rᵢ₊₁ = norm(sᵢ₊₁) # radius / Euclidean distance between points. + # Now, we set the interpolated value to the first point's value, multiplied + # by the weight computed relative to the first point in the polygon. + wᵢ = (t_value(sᵢ₋₁, sᵢ, rᵢ₋₁, rᵢ) + t_value(sᵢ, sᵢ₊₁, rᵢ, rᵢ₊₁)) / rᵢ + wₜₒₜ = wᵢ + interpolated_value = values[begin] * wᵢ + + for i in 2:l_exterior + # Increment counters + set variables + sᵢ₋₁ = sᵢ + sᵢ = sᵢ₊₁ + sᵢ₊₁ = exterior[mod1(i+1, l_exterior)] - point + rᵢ₋₁ = rᵢ + rᵢ = rᵢ₊₁ + rᵢ₊₁ = norm(sᵢ₊₁) # radius / Euclidean distance between points. + wᵢ = (t_value(sᵢ₋₁, sᵢ, rᵢ₋₁, rᵢ) + t_value(sᵢ, sᵢ₊₁, rᵢ, rᵢ₊₁)) / rᵢ + # Updates - first the interpolated value, + interpolated_value += values[current_index] * wᵢ + # then the accumulators for total weight and current index. + wₜₒₜ += wᵢ + current_index += 1 + + end + for hole in interiors + l_hole = length(hole) + sᵢ₋₁ = hole[end] - point + sᵢ = hole[begin] - point + sᵢ₊₁ = hole[begin+1] - point + rᵢ₋₁ = norm(sᵢ₋₁) # radius / Euclidean distance between points. + rᵢ = norm(sᵢ ) # radius / Euclidean distance between points. + rᵢ₊₁ = norm(sᵢ₊₁) # radius / Euclidean distance between points. + ## Now, we set the interpolated value to the first point's value, multiplied + ## by the weight computed relative to the first point in the polygon. + wᵢ = (t_value(sᵢ₋₁, sᵢ, rᵢ₋₁, rᵢ) + t_value(sᵢ, sᵢ₊₁, rᵢ, rᵢ₊₁)) / rᵢ + + interpolated_value += values[current_index] * wᵢ + + wₜₒₜ += wᵢ + current_index += 1 + + for i in 2:l_hole + ## Increment counters + set variables + sᵢ₋₁ = sᵢ + sᵢ = sᵢ₊₁ + sᵢ₊₁ = hole[mod1(i+1, l_hole)] - point + rᵢ₋₁ = rᵢ + rᵢ = rᵢ₊₁ + rᵢ₊₁ = norm(sᵢ₊₁) ## radius / Euclidean distance between points. + wᵢ = (t_value(sᵢ₋₁, sᵢ, rᵢ₋₁, rᵢ) + t_value(sᵢ, sᵢ₊₁, rᵢ, rᵢ₊₁)) / rᵢ + interpolated_value += values[current_index] * wᵢ + wₜₒₜ += wᵢ + current_index += 1 + end + end + return interpolated_value / wₜₒₜ + +end + +struct Wachspress <: AbstractBarycentricCoordinateMethod +end diff --git a/src/methods/centroid.jl b/src/methods/centroid.jl index df78f8360..855c46af0 100644 --- a/src/methods/centroid.jl +++ b/src/methods/centroid.jl @@ -1,3 +1,5 @@ +# # Centroid + export centroid # These are all GeometryBasics.jl methods so far. diff --git a/src/methods/contains.jl b/src/methods/contains.jl index 2fe1f929b..5d978e89e 100644 --- a/src/methods/contains.jl +++ b/src/methods/contains.jl @@ -1,44 +1,52 @@ +# # Containment + +# This currently works for point-in-linestring or point-in-polygon. + # More GeometryBasics code -_cross(p1, p2, p3) = (p1[1] - p3[1]) * (p2[2] - p3[2]) - (p2[1] - p3[1]) * (p1[2] - p3[2]) +_cross(p1, p2, p3) = (GI.x(p1) - GI.x(p3)) * (GI.y(p2) - GI.y(p3)) - (GI.x(p2) - GI.x(p3)) * (GI.y(p1) - GI.y(p3)) + +contains(pointlist, point) = contains(GI.trait(pointlist), GI.trait(point), pointlist, point) # Implementation of a point-in-polygon algorithm # from Luxor.jl. This is the Hormann-Agathos (2001) algorithm. # For the source, see https://github.com/JuliaGraphics/Luxor.jl/blob/66d60fb51f6b1bb38690fe8dcc6c0084eeb80710/src/polygons.jl#L190-L229. -function contains(ls::GeometryBasics.LineString{2, T1}, point::Point{2, T2}) where {T1, T2} - pointlist = decompose(Point{2, promote_type(T1, T2)}, ls) +function contains(::GI.LineStringTrait, ::GI.PointTrait, pointlist, point) + n = GI.npoint(pointlist) c = false - @inbounds for counter in eachindex(pointlist) - q1 = pointlist[counter] + q1 = GI.getpoint(pointlist, 1) + q2 = GI.getpoint(pointlist, 1) + @inbounds for (counter, current_point) in enumerate(Iterators.drop(GI.getpoint(pointlist), 1)) + q1 = q2 # if reached last point, set "next point" to first point - if counter == length(pointlist) - q2 = pointlist[1] + if counter == (n-1) + q2 = GI.getpoint(pointlist, 1) else - q2 = pointlist[counter + 1] + q2 = current_point end - if q1 == point + if GI.x(q1) == GI.x(point) && GI.x(q1) == GI.y(point) # allowonedge || error("isinside(): VertexException a") continue end - if q2[2] == point[2] - if q2[1] == point[1] + if GI.y(q2) == GI.y(point) + if GI.x(q2) == GI.x(point) # allowonedge || error("isinside(): VertexException b") continue - elseif (q1[2] == point[2]) && ((q2[1] > point[1]) == (q1[1] < point[1])) + elseif (GI.y(q1) == GI.y(point)) && ((GI.x(q2) > GI.x(point)) == (GI.x(q1) < GI.x(point))) # allowonedge || error("isinside(): EdgeException") continue end end - if (q1[2] < point[2]) != (q2[2] < point[2]) # crossing - if q1[1] >= point[1] - if q2[1] > point[1] + if (GI.y(q1) < GI.y(point)) != (GI.y(q2) < GI.y(point)) # crossing + if GI.x(q1) >= GI.x(point) + if GI.x(q2) > GI.x(point) c = !c - elseif ((_cross(q1, q2, point) > 0) == (q2[2] > q1[2])) + elseif ((_cross(q1, q2, point) > 0) == (GI.y(q2) > GI.y(q1))) c = !c end - elseif q2[1] > point[1] - if ((_cross(q1, q2, point) > 0) == (q2[2] > q1[2])) + elseif GI.x(q2) > GI.x(point) + if ((_cross(q1, q2, point) > 0) == (GI.y(q2) > GI.y(q1))) c = !c end end diff --git a/src/methods/signed_area.jl b/src/methods/signed_area.jl index 0113e362c..7e202701f 100644 --- a/src/methods/signed_area.jl +++ b/src/methods/signed_area.jl @@ -1,4 +1,5 @@ # # Signed area + export signed_area # ## What is signed area? @@ -11,7 +12,10 @@ export signed_area # To provide an example, consider this rectangle: # ```@example rect -# using GeometryBasics +# using GeometryOps +# using GeometryOps.GeometryBasics +# using Makie +# # rect = Polygon([Point(0,0), Point(0,1), Point(1,1), Point(1,0), Point(0, 0)]) # f, a, p = poly(rect; axis = (; aspect = DataAspect())) # ``` diff --git a/src/methods/signed_distance.jl b/src/methods/signed_distance.jl index 52db4e6fd..c0ccb0e20 100644 --- a/src/methods/signed_distance.jl +++ b/src/methods/signed_distance.jl @@ -1,5 +1,7 @@ # # Signed distance + export signed_distance + # TODO: clean this up. It already supports GeoInterface. Base.@propagate_inbounds euclid_distance(p1, p2) = sqrt((GeoInterface.x(p2)-GeoInterface.x(p1))^2 + (GeoInterface.y(p2)-GeoInterface.y(p1))^2) diff --git a/src/primitives.jl b/src/primitives.jl index 634f509bb..431e7fa76 100644 --- a/src/primitives.jl +++ b/src/primitives.jl @@ -1,3 +1,7 @@ +# # Primitive functions + +# This file mainly defines the [`apply`](@ref) function. + """ apply(f, target::Type{<:AbstractTrait}, obj; crs) diff --git a/src/transformations/flip.jl b/src/transformations/flip.jl index e8b3e6cdd..194119095 100644 --- a/src/transformations/flip.jl +++ b/src/transformations/flip.jl @@ -1,3 +1,8 @@ +# # Coordinate flipping + +# This is a simple example of how to use the `apply` functionality in a function, +# by flipping the x and y coordinates of a geometry. + """ flip(obj) diff --git a/src/transformations/reproject.jl b/src/transformations/reproject.jl index 5d8e9bb1f..5e73df925 100644 --- a/src/transformations/reproject.jl +++ b/src/transformations/reproject.jl @@ -1,4 +1,12 @@ +# # Geometry reprojection +export reproject + +# This file is pretty simple - it simply reprojects a geometry pointwise from one CRS +# to another. It uses the `Proj` package for the transformation, but this could be +# moved to an extension if needed. + +# This works using the [`apply`](@ref) functionality. """ reproject(geometry; source_crs, target_crs, transform, always_xy, time) diff --git a/src/transformations/simplify.jl b/src/transformations/simplify.jl index 0ffbdb36a..d9b4b4b11 100644 --- a/src/transformations/simplify.jl +++ b/src/transformations/simplify.jl @@ -1,3 +1,23 @@ +# # Geometry simplification + +# This file holds implementations for the Douglas-Peucker and Visvalingam-Whyatt +# algorithms for simplifying geometries (specifically polygons and lines). + +export simplify, VisvalingamWhyatt, DouglasPeucker + + +""" + abstract type SimplifyAlg + +Abstract type for simplification algorithms. + +## API + +For now, the algorithm must hold the `number`, `ratio` and `tol` properties. + +Simplification algorithm types can hook into the interface by implementing +the `_simplify(trait, alg, geom)` methods for whichever traits are necessary. +""" abstract type SimplifyAlg end const SIMPLIFY_ALG_KEYWORDS = """ @@ -83,27 +103,27 @@ simplify(data; kw...) = _simplify(DouglasPeucker(; kw...), data) simplify(alg::SimplifyAlg, data) = _simplify(alg, data) function _simplify(alg::SimplifyAlg, data) - # Apply simplication to all curves, multipoints, and points, - # reconstructing everything else around them. + ## Apply simplication to all curves, multipoints, and points, + ## reconstructing everything else around them. simplifier(geom) = _simplify(trait(geom), alg, geom) apply(simplifier, Union{PolygonTrait,AbstractCurveTrait,MultiPoint,PointTrait}, data) end -# For Point and MultiPoint traits we do nothing +## For Point and MultiPoint traits we do nothing _simplify(::PointTrait, alg, geom) = geom _simplify(::MultiPointTrait, alg, geom) = geom function _simplify(::PolygonTrait, alg, geom) - # Force treating children as LinearRing + ## Force treating children as LinearRing rebuilder(g) = rebuild(g, _simplify(LinearRingTrait(), alg, g)) lrs = map(rebuilder, GI.getgeom(geom)) return rebuild(geom, lrs) end -# For curves and rings we simplify +## For curves and rings we simplify _simplify(::AbstractCurveTrait, alg, geom) = rebuild(geom, simplify(alg, tuple_points(geom))) function _simplify(::LinearRingTrait, alg, geom) - # Make a vector of points + ## Make a vector of points points = tuple_points(geom) - # Simplify it once + ## Simplify it once simple = _simplify(alg, points) return rebuild(geom, simple) @@ -138,9 +158,9 @@ function _simplify(alg::RadialDistance, points::Vector) distances[i] = _squared_dist(point, previous) previous = point end - # Never remove the end points + ## Never remove the end points distances[begin] = distances[end] = Inf - # This avoids taking the square root of each distance above + ## This avoids taking the square root of each distance above if !isnothing(alg.tol) alg = settol(alg, (alg.tol::Float64)^2) end @@ -180,8 +200,8 @@ settol(alg::DouglasPeucker, tol) = DouglasPeucker(alg.number, alg.ratio, tol, al function _simplify(alg::DouglasPeucker, points::Vector) length(points) <= MIN_POINTS && return points - # TODO do we need this? - # points = alg.prefilter ? simplify(RadialDistance(alg.tol), points) : points + ## TODO do we need this? + ## points = alg.prefilter ? simplify(RadialDistance(alg.tol), points) : points distances = _build_tolerances(_squared_segdist, points) return _get_points(alg, points, distances) @@ -239,19 +259,19 @@ function _simplify(alg::VisvalingamWhyatt, points::Vector) length(points) <= MIN_POINTS && return points areas = _build_tolerances(_triangle_double_area, points) - # This avoids diving everything by two + ## This avoids diving everything by two if !isnothing(alg.tol) alg = settol(alg, (alg.tol::Float64)*2) end return _get_points(alg, points, areas) end -# calculates the area of a triangle given its vertices +## calculates the area of a triangle given its vertices _triangle_double_area(p1, p2, p3) = abs(p1[1] * (p2[2] - p3[2]) + p2[1] * (p3[2] - p1[2]) + p3[1] * (p1[2] - p2[2])) -# Shared utils +# ### Shared utils function _build_tolerances(f, points) nmax = length(points) @@ -317,7 +337,11 @@ function tuple_points(geom) end function _get_points(alg, points, tolerances) - (; tol, number, ratio) = alg + ## This assumes that `alg` has the properties + ## `tol`, `number`, and `ratio` available... + tol = alg.tol + number = alg.number + ratio = alg.ratio bit_indices = if !isnothing(tol) _tol_indices(alg.tol::Float64, points, tolerances) elseif !isnothing(number) @@ -336,8 +360,8 @@ function _number_indices(n, points, tolerances) tol = partialsort(tolerances, length(points) - n + 1) bit_indices = _tol_indices(tol, points, tolerances) nselected = sum(bit_indices) - # If there are multiple values exactly at `tol` we will get - # the wrong output length. So we need to remove some. + ## If there are multiple values exactly at `tol` we will get + ## the wrong output length. So we need to remove some. while nselected > n min_tol = Inf min_i = 0 diff --git a/src/utils.jl b/src/utils.jl index 0fdaa8872..b59940a78 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,3 +1,4 @@ +# # Utility functions _is3d(geom) = _is3d(GI.trait(geom), geom) _is3d(::GI.AbstractGeometryTrait, geom) = GI.is3d(geom) diff --git a/test/methods/barycentric.jl b/test/methods/barycentric.jl new file mode 100644 index 000000000..27f378d93 --- /dev/null +++ b/test/methods/barycentric.jl @@ -0,0 +1,26 @@ +@testset "Mean value coordinates" begin + @testset "Preserving return type" begin + @test barycentric_coordinates(MeanValue(), Point2{BigFloat}[(0,0), (1,0), (0,1)], Point2{BigFloat}(1,1)) isa Vector{BigFloat} + @test barycentric_coordinates(MeanValue(), Point2{BigFloat}[(0,0), (1,0), (0,1)], Point2f(1,1)) isa Vector{BigFloat} # keep the most precise type + @test barycentric_coordinates(MeanValue(), Point2{Float64}[(0,0), (1,0), (0,1)], Point2{Float64}(1,1)) isa Vector{Float64} + @test barycentric_coordinates(MeanValue(), Point2{Float32}[(0,0), (1,0), (0,1)], Point2{Float32}(1,1)) isa Vector{Float32} + end + @testset "Triangle coordinates" begin + # Test that the barycentric coordinates for (0,0), (1,0), (0,1), and (0.5,0.5) are (0.5,0.25,0.25) + @test all(barycentric_coordinates(MeanValue(), Point2f[(0,0), (1,0), (0,1)], Point2f(0.5,0.5)) .== (0.5,0.25,0.25)) + # Test that the barycentric coordinates for (0,0), (1,0), (0,1), and (1,1) are (-1,1,1) + @test all(barycentric_coordinates(MeanValue(), Point2f[(0,0), (1,0), (0,1)], Point2f(1,1)) .≈ (-1,1,1)) + # Test that calculations with different number types (in this case Float64) are also accurate + @test all(barycentric_coordinates(MeanValue(), Point2{Float64}[(0,0), (1,0), (0,1)], Point2{Float64}(1,1)) .≈ (-1,1,1)) + end + @testset "Tests for helper methods" begin + @testset "`t_value`" begin + @test GeometryOps.t_value(Point2f(0,0), Point2f(1,0), 1, 1) == 0 + @test GeometryOps.t_value(Point2f(0, 1), Point2f(1, 0), 1, 2) == -0.5f0 + end + @testset "`_det`" begin + @test GeometryOps._det((1,0), (0,1)) == 1f0 + @test GeometryOps._det(Point2f(1, 2), Point2f(3, 4)) == -2.0f0 + end + end +end diff --git a/test/runtests.jl b/test/runtests.jl index bb1917a16..dd6f4d964 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,7 @@ using GeometryOps using Test using GeometryOps.GeoInterface +using GeometryOps.GeometryBasics using ArchGDAL const GI = GeoInterface @@ -10,7 +11,8 @@ const AG = ArchGDAL @testset "GeometryOps.jl" begin @testset "Primitives" begin include("primitives.jl") end @testset "Signed Area" begin include("methods/signed_area.jl") end - @testset "reproject" begin include("transformations/reproject.jl") end - @testset "flip" begin include("transformations/flip.jl") end - @testset "simplify" begin include("transformations/simplify.jl") end + @testset "Barycentric coordinate operations" begin include("methods/barycentric.jl") end + @testset "Reproject" begin include("transformations/reproject.jl") end + @testset "Flip" begin include("transformations/flip.jl") end + @testset "Simplify" begin include("transformations/simplify.jl") end end