Skip to content

Commit

Permalink
Add DataAPI metadata passthrough to apply (#211)
Browse files Browse the repository at this point in the history
* Add DataAPI metadata passthrough

* Don't need input metadata for CRS/geometrycolumn
  • Loading branch information
asinghvi17 authored Sep 19, 2024
1 parent c486a40 commit 7675b03
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 4 deletions.
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

0 comments on commit 7675b03

Please sign in to comment.