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

[WIP] Add a Tables.jl extension for FeatureCollection and Feature #156

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
9 changes: 8 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ version = "1.3.6"
Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
GeoFormatTypes = "68eda718-8dee-11e9-39e7-89f7f65f511f"

[weakdeps]
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"

[extensions]
GeoInterfaceTablesExt = "Tables"

[compat]
Extents = "0.1.1"
GeoFormatTypes = "0.4"
julia = "1"
Tables = "1"
julia = "1.9"

[extras]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Expand Down
129 changes: 129 additions & 0 deletions ext/GeoInterfaceTablesExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
module GeoInterfaceTablesExt

using GeoInterface
using GeoInterface.Wrappers
using Tables

# This module is meant to extend the Tables.jl interface to features and feature collections, such that they can be used with Tables.jl.
# This enables the use of the Tables.jl ecosystem with GeoInterface wrapper geometries.

# First, define the Tables interface

Tables.istable(::Type{<: Wrappers.FeatureCollection}) = true
Tables.isrowtable(::Type{<: Wrappers.FeatureCollection}) = true
Tables.rowaccess(::Type{<: Wrappers.FeatureCollection}) = true
Tables.rows(fc::Wrappers.FeatureCollection{P, C, E}) where {P <: Union{AbstractArray{<: Wrappers.Feature}, Tuple{Vararg{<: Wrappers.Feature}}}, C, E} = GeoInterface.getfeature(fc)
Tables.rows(fc::Wrappers.FeatureCollection) = Iterators.map(Wrappers.Feature, GeoInterface.getfeature(fc))
Tables.schema(fc::Wrappers.FeatureCollection) = property_schema(GeoInterface.getfeature(fc))

Check warning on line 17 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L12-L17

Added lines #L12 - L17 were not covered by tests

# Define the row access interface for feature wrappers
function Tables.getcolumn(row::Wrappers.Feature, i::Int)
if i == 1
return GeoInterface.geometry(row)

Check warning on line 22 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L20-L22

Added lines #L20 - L22 were not covered by tests
else
return GeoInterface.properties(row)[i-1]

Check warning on line 24 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L24

Added line #L24 was not covered by tests
end
end
Tables.getcolumn(row::Wrappers.Feature, nm::Symbol) = nm === :geometry ? GeoInterface.geometry(row) : Tables.getcolumn(GeoInterface.properties(row), nm)
Tables.columnnames(row::Wrappers.Feature) = (:geometry, propertynames(GeoInterface.properties(row))...)

Check warning on line 28 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L27-L28

Added lines #L27 - L28 were not covered by tests

# Copied from GeoJSON.jl
# Credit to [Rafael Schouten](@rafaqz)
# Adapted from JSONTables.jl jsontable method
# We cannot simply use their method as we have concrete types and need the key/value pairs
# of the properties field, rather than the main object
# TODO: Is `missT` required?
# TODO: The `getfield` is probably required once
missT(::Type{Nothing}) = Missing
missT(::Type{T}) where {T} = T

Check warning on line 38 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L37-L38

Added lines #L37 - L38 were not covered by tests

function property_schema(features)

Check warning on line 40 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L40

Added line #L40 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this work for GeoJSON too? Would be good to delete the code there if it does

# Otherwise find the shared names
names = Set{Symbol}()
types = Dict{Symbol,Type}()
for feature in features
props = GeoInterface.properties(feature)
isnothing(props) && continue
if isempty(names)
for k in keys(props)
k === :geometry && continue
push!(names, k)
types[k] = missT(typeof(props[k]))
end
push!(names, :geometry)
types[:geometry] = missT(typeof(GeoInterface.geometry(feature)))

Check warning on line 54 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L42-L54

Added lines #L42 - L54 were not covered by tests
else
for nm in names
T = types[nm]
if haskey(props, nm)
v = props[nm]
if !(missT(typeof(v)) <: T)
types[nm] = Union{T,missT(typeof(v))}

Check warning on line 61 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L56-L61

Added lines #L56 - L61 were not covered by tests
end
elseif hasfield(typeof(feature), nm)
v = getfield(feature, nm)
if !(missT(typeof(v)) <: T)
types[nm] = Union{T,missT(typeof(v))}

Check warning on line 66 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L63-L66

Added lines #L63 - L66 were not covered by tests
end
elseif !(T isa Union && T.a === Missing)
types[nm] = Union{Missing,types[nm]}

Check warning on line 69 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L68-L69

Added lines #L68 - L69 were not covered by tests
end
end
for (k, v) in pairs(props)
k === :geometry && continue
if !(k in names)
push!(names, k)
types[k] = Union{Missing,missT(typeof(v))}

Check warning on line 76 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L71-L76

Added lines #L71 - L76 were not covered by tests
end
end

Check warning on line 78 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L78

Added line #L78 was not covered by tests
end
end
return collect(names), types

Check warning on line 81 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L80-L81

Added lines #L80 - L81 were not covered by tests
end



# Finally, define the metadata interface. FeatureCollection wrappers have no metadata, so we simply specify geometry columns and CRS.

Tables.DataAPI.metadatasupport(::Type{<: Wrappers.FeatureCollection}) = (; read = true, write = false)
Tables.DataAPI.metadatakeys(::Wrappers.FeatureCollection) = ("GEOINTERFACE:geometrycolumns", "GEOINTERFACE:crs")
function Tables.DataAPI.metadata(fc::Wrappers.FeatureCollection, key::AbstractString; style = false)
result = if key == "GEOINTERFACE:geometrycolumns"
(:geometry,)
elseif key == "GEOINTERFACE:crs"
if isnothing(GeoInterface.crs(fc))
nothing

Check warning on line 95 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L88-L95

Added lines #L88 - L95 were not covered by tests
# or
#=
GeoFormatTypes.ESRIWellKnownText(
"""
ENGCRS["Undefined Cartesian SRS with unknown unit",
EDATUM["Unknown engineering datum"],
CS[Cartesian,2],
AXIS["X",unspecified,
ORDER[1],
LENGTHUNIT["unknown",0]],
AXIS["Y",unspecified,
ORDER[2],
LENGTHUNIT["unknown",0]]]
"""
)
=#
else
GeoInterface.crs(fc)

Check warning on line 113 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L113

Added line #L113 was not covered by tests
end
else
throw(KeyError(key))

Check warning on line 116 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L116

Added line #L116 was not covered by tests
end

if style
return (result, :note)

Check warning on line 120 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L119-L120

Added lines #L119 - L120 were not covered by tests
else
return result

Check warning on line 122 in ext/GeoInterfaceTablesExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/GeoInterfaceTablesExt.jl#L122

Added line #L122 was not covered by tests
end
end




end # module
Loading