Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DataAPI metadata passthrough to apply #211

Merged
merged 3 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.1.11"

[deps]
CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298"
DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
DelaunayTriangulation = "927a84f5-c5f4-47a5-9785-b46e178433df"
ExactPredicates = "429591f6-91af-11e9-00e2-59fbe8cec110"
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
Expand Down
2 changes: 1 addition & 1 deletion src/GeometryOps.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ using GeoInterface
using GeometryBasics
using LinearAlgebra, Statistics

import Tables
import Tables, DataAPI
import GeometryBasics.StaticArrays
import DelaunayTriangulation # for convex hull and triangulation
import ExactPredicates
Expand Down
37 changes: 36 additions & 1 deletion src/primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,47 @@ function _apply_table(f::F, target, iterable::IterableType; threaded, kw...) whe
new_names = filter(Base.Fix1(!==, geometry_column), old_schema.names)
# and try to rebuild the same table as the best type - either the original type of `iterable`,
# or a named tuple which is the default fallback.
return Tables.materializer(iterable)(
result = Tables.materializer(iterable)(
merge(
NamedTuple{(geometry_column,), Base.Tuple{typeof(new_geometry)}}((new_geometry,)),
NamedTuple(Iterators.map(_get_col_pair, new_names))
)
)
# Finally, we ensure that metadata is propagated correctly.
# This can only happen if the original table supports metadata reads,
# and the result supports metadata writes.
if DataAPI.metadatasupport(typeof(result)).write
# Copy over all metadata from the original table to the new table,
# if the original table supports metadata reading.
if DataAPI.metadatasupport(IterableType).read
for (key, (value, style)) in DataAPI.metadata(iterable; style = true)
# Default styles are not preserved on data transformation, so we must skip them!
style == :default && continue
# We assume that any other style is preserved.
DataAPI.metadata!(result, key, value; style)
end
end
# We don't usually care about the original table's metadata for GEOINTERFACE namespaced
# keys, so we should set the crs and geometrycolumns metadata if they are present.
# Ensure that `GEOINTERFACE:geometrycolumns` and `GEOINTERFACE:crs` are set!
mdk = DataAPI.metadatakeys(result)
# If the user has asked for geometry columns to persist, they would be here,
# so we don't need to set them.
if !("GEOINTERFACE:geometrycolumns" in mdk)
# If the geometry columns are not already set, we need to set them.
DataAPI.metadata!(result, "GEOINTERFACE:geometrycolumns", (geometry_column,); style = :default)
end
# Force reset CRS always, since you can pass `crs` to `apply`.
new_crs = if haskey(kw, :crs)
kw[:crs]
else
GI.crs(iterable) # this will automatically check `GEOINTERFACE:crs` unless the type has a specialized implementation.
end

DataAPI.metadata!(result, "GEOINTERFACE:crs", new_crs; style = :default)
end

return result
end

# Rewrap all FeatureCollectionTrait feature collections as GI.FeatureCollection
Expand Down
13 changes: 11 additions & 2 deletions test/primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ using Test

import ArchGDAL as AG
import GeometryBasics as GB
import GeoFormatTypes as GFT
import GeometryOps as GO
import GeoInterface as GI
import LibGEOS as LG
import Proj
import Shapefile
import DataFrames
import DataFrames, Tables, DataAPI
using Downloads: download
using ..TestHelpers

Expand Down Expand Up @@ -54,7 +55,9 @@ poly = GI.Polygon([lr1, lr2])

@testset "DataFrames" begin
countries_df = DataFrames.DataFrame(countries_table)
centroid_df = GO.apply(GO.centroid, GO.TraitTarget(GI.PolygonTrait(), GI.MultiPolygonTrait()), countries_df);
GO.DataAPI.metadata!(countries_df, "note metadata", "note metadata value"; style = :note)
GO.DataAPI.metadata!(countries_df, "default metadata", "default metadata value"; style = :default)
centroid_df = GO.apply(GO.centroid, GO.TraitTarget(GI.PolygonTrait(), GI.MultiPolygonTrait()), countries_df; crs = GFT.EPSG(3031));
@test centroid_df isa DataFrames.DataFrame
centroid_geometry = centroid_df.geometry
# Test that the centroids are correct
Expand All @@ -64,6 +67,12 @@ poly = GI.Polygon([lr1, lr2])
@test all(missing_or_equal.(centroid_df[!, column], countries_df[!, column]))
end
end
@testset "Metadata preservation (or not)" begin
@test DataAPI.metadata(centroid_df, "note metadata") == "note metadata value"
@test !("default metadata" in DataAPI.metadatakeys(centroid_df))
@test DataAPI.metadata(centroid_df, "GEOINTERFACE:geometrycolumns") == (:geometry,)
@test DataAPI.metadata(centroid_df, "GEOINTERFACE:crs") == GFT.EPSG(3031)
end
end
end
end
Expand Down
Loading