Skip to content
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
11 changes: 3 additions & 8 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,17 @@ Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
GeoFormatTypes = "68eda718-8dee-11e9-39e7-89f7f65f511f"

[weakdeps]
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"

[extensions]
GeoInterfaceRecipesBaseExt = "RecipesBase"
GeoInterfaceMakieExt = ["Makie", "GeometryBasics"]
GeoInterfaceTablesExt = "Tables"

[compat]
DataAPI = "1"
CairoMakie = "0.15"
Extents = "0.1.1"
GeoFormatTypes = "0.4"
GeometryBasics = "0.5"
Makie = "0.23, 0.24"
RecipesBase = "1"
Tables = "1"
julia = "1.10"

[extras]
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))

# Define the row access interface for feature wrappers
function Tables.getcolumn(row::Wrappers.Feature, i::Int)
if i == 1
return GeoInterface.geometry(row)
else
return GeoInterface.properties(row)[i-1]
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))...)

# 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

function property_schema(features)
# 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)))
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))}
end
elseif hasfield(typeof(feature), nm)
v = getfield(feature, nm)
if !(missT(typeof(v)) <: T)
types[nm] = Union{T,missT(typeof(v))}
end
elseif !(T isa Union && T.a === Missing)
types[nm] = Union{Missing,types[nm]}
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))}
end
end
end
end
return collect(names), types
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
# 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)
end
else
throw(KeyError(key))
end

if style
return (result, :note)
else
return result
end
end




end # module
Loading