diff --git a/src/base.jl b/src/base.jl index 60238078..9a54583e 100644 --- a/src/base.jl +++ b/src/base.jl @@ -28,17 +28,30 @@ GeoInterface.coordnames(::PointTrait, geom::NamedTuple{Keys,NTuple{N,T}}) where # Default features using NamedTuple and AbstractArray -const NamedTupleFeature = NamedTuple{(:geometry, :properties)} +# Any named tuple with a `:geometry` field is a feature +_is_namedtuple_feature(::Type{<:NamedTuple{K}}) where K = :geometry in K +_is_namedtuple_feature(nt::NamedTuple) = _is_namedtuple_feature(typeof(nt)) + +GeoInterface.isfeature(T::Type{<:NamedTuple}) = _is_namedtuple_feature(T) +GeoInterface.trait(nt::NamedTuple) = _is_namedtuple_feature(nt) ? FeatureTrait() : nothing +GeoInterface.geometry(nt::NamedTuple) = _is_namedtuple_feature(nt) ? nt.geometry : nothing +GeoInterface.properties(nt::NamedTuple) = _is_namedtuple_feature(nt) ? _nt_properties(nt) : nothing + +# Use Val to force constant propagation through `reduce` +function _nt_properties(nt::NamedTuple{K}) where K + keys = reduce(K; init=()) do acc, k + k == :geometry ? acc : (acc..., k) + end + return NamedTuple{keys}(nt) +end -GeoInterface.isfeature(::Type{<:NamedTupleFeature}) = true -GeoInterface.trait(::NamedTupleFeature) = FeatureTrait() -GeoInterface.geometry(f::NamedTupleFeature) = f.geometry -GeoInterface.properties(f::NamedTupleFeature) = f.properties +const MaybeArrayFeatureCollection = AbstractArray{<:NamedTuple} -const ArrayFeatureCollection = AbstractArray{<:NamedTupleFeature} +_is_array_featurecollection(::Type{<:AbstractArray{T}}) where {T<:NamedTuple} = _is_namedtuple_feature(T) +_is_array_featurecollection(A::AbstractArray{<:NamedTuple}) = _is_array_featurecollection(typeof(A)) -GeoInterface.isfeaturecollection(::Type{<:ArrayFeatureCollection}) = true -GeoInterface.trait(::ArrayFeatureCollection) = FeatureCollectionTrait() -GeoInterface.nfeature(::FeatureCollectionTrait, fc::ArrayFeatureCollection) = Base.length(fc) -GeoInterface.getfeature(::FeatureCollectionTrait, fc::ArrayFeatureCollection, i::Integer) = fc[i] -GeoInterface.geometrycolumns(fc::ArrayFeatureCollection) = (:geometry,) +GeoInterface.isfeaturecollection(T::Type{<:MaybeArrayFeatureCollection}) = _is_array_featurecollection(T) +GeoInterface.trait(fc::MaybeArrayFeatureCollection) = _is_array_featurecollection(fc) ? FeatureCollectionTrait() : nothing +GeoInterface.nfeature(::FeatureCollectionTrait, fc::MaybeArrayFeatureCollection) = _is_array_featurecollection(fc) ? Base.length(fc) : nothing +GeoInterface.getfeature(::FeatureCollectionTrait, fc::MaybeArrayFeatureCollection, i::Integer) = _is_array_featurecollection(fc) ? fc[i] : nothing +GeoInterface.geometrycolumns(fc::MaybeArrayFeatureCollection) = _is_array_featurecollection(fc) ? (:geometry,) : nothing diff --git a/src/interface.jl b/src/interface.jl index 5800e9de..bfa678c0 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -92,7 +92,6 @@ Returns the object type, such as [`FeatureTrait`](@ref). For all `isgeometry` objects `trait` is the same as `geomtrait(obj)`, e.g. [`PointTrait`](@ref). """ -# trait(geom::T) where T = isgeometry(T) ? geomtrait(geom) : nothing trait(geom) = geomtrait(geom) # All types diff --git a/src/types.jl b/src/types.jl index a1462afd..1a81cf87 100644 --- a/src/types.jl +++ b/src/types.jl @@ -1,5 +1,7 @@ +"An AbstractTrait type for all geometries, features and feature collections." +abstract type AbstractTrait end "An AbstractGeometryTrait type for all geometries." -abstract type AbstractGeometryTrait end +abstract type AbstractGeometryTrait <: AbstractTrait end "An AbstractGeometryCollectionTrait type for all geometrycollections." abstract type AbstractGeometryCollectionTrait <: AbstractGeometryTrait end @@ -82,11 +84,11 @@ struct MultiPolygonTrait <: AbstractMultiPolygonTrait end "An AbstractFeatureTrait for all features" -abstract type AbstractFeatureTrait end +abstract type AbstractFeatureTrait <: AbstractTrait end "A FeatureTrait holds `geometries`, `properties` and an `extent`" struct FeatureTrait <: AbstractFeatureTrait end "An AbstractFeatureCollectionTrait for all feature collections" -abstract type AbstractFeatureCollectionTrait end +abstract type AbstractFeatureCollectionTrait <: AbstractTrait end "A FeatureCollectionTrait holds objects of `FeatureTrait` and an `extent`" struct FeatureCollectionTrait <: AbstractFeatureCollectionTrait end diff --git a/test/test_primitives.jl b/test/test_primitives.jl index 37c16949..410ee7e6 100644 --- a/test/test_primitives.jl +++ b/test/test_primitives.jl @@ -344,7 +344,9 @@ end end @testset "NamedTupleFeature" begin - feature = (; geometry=(1, 2), properties=(a="x", b="y")) + feature = (; geometry=(1, 2), a="x", b="y", c="z") + GeoInterface.geometry(feature) = (1, 2) + @test GeoInterface.properties(feature) == (a="x", b="y", c="z") @test GeoInterface.testfeature(feature) @test GeoInterface.testfeaturecollection([feature, feature]) end