Skip to content

Commit 334c0ec

Browse files
authored
Implement crs and geometrycolumn fallbacks via DataAPI (#161)
* Implement crs and geometrycolumn fallbacks via DataAPI * fix function name * use type not object to query DataAPI * Move core functionality to a new `metadata.jl` * add aftercare for geometry columns * add docs about geometrycolumns and crs for feature collections * minor bugfixes + add tests * avoid extra entry * add constants that define what the keys are, for portability
1 parent 1db072e commit 334c0ec

File tree

7 files changed

+104
-3
lines changed

7 files changed

+104
-3
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ authors = ["JuliaGeo and contributors"]
44
version = "1.3.8"
55

66
[deps]
7+
DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
78
Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
89
GeoFormatTypes = "68eda718-8dee-11e9-39e7-89f7f65f511f"
910

1011
[compat]
12+
DataAPI = "1"
1113
Extents = "0.1.1"
1214
GeoFormatTypes = "0.4"
1315
julia = "1"

docs/src/guides/developer.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,22 @@ GeoInterface.geometrycolumns(::customcollection) = (:geometry,) # can be multip
8686

8787
The `geometrycolumns` enables other packages to know which field in a row, or column in a table, contains the geometry or geometries.
8888

89+
It's important to note that the `geometrycolumns` should always return a `Tuple` of `Symbol`s. However, it does have a fallback method
90+
that uses [DataAPI.jl](https://github.com/JuliaData/DataAPI.jl) metadata, if it exists, to retrieve the geometry columns. This relies on
91+
the `"GEOINTERFACE:geometrycolumns"` metadata key. GeoInterface.jl compatible writers may set this metadata key if writing to a format
92+
that does not have its own mechanism to store known geometry columns, like Arrow.
93+
94+
Optionally, the `crs` method can also be implemented:
95+
```julia
96+
GeoInterface.crs(::customcollection)
97+
```
98+
99+
This should return a `GeoFormatTypes.CoordinateReferenceSystem` type, such as `EPSG(code::Int)`, `WellKnownText(GeoFormatTypes.CRS(), wkt::String)`,
100+
or `ProjString(p4::String)`. See [GeoFormatTypes.jl](https://github.com/JuliaGeo/GeoFormatTypes.jl) for more information.
101+
102+
The `crs` method also has a fallback that uses [DataAPI.jl](https://github.com/JuliaData/DataAPI.jl) metadata, if it exists, to retrieve the CRS.
103+
GeoInterface searches for the `"GEOINTERFACE:crs"` metadata key to retrieve the CRS.
104+
89105
## Geospatial Operations
90106
```julia
91107
distance(geomtrait(a), geomtrait(b), a, b)

src/GeoInterface.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module GeoInterface
33
using Extents: Extents, Extent
44
using GeoFormatTypes: CoordinateReferenceSystemFormat
55
using Base.Iterators: flatten
6+
import DataAPI
67

78
export testgeometry, isgeometry, trait, geomtrait, ncoord, getcoord, ngeom, getgeom
89

@@ -55,6 +56,7 @@ include("fallbacks.jl")
5556
include("utils.jl")
5657
include("base.jl")
5758
include("wrappers.jl")
59+
include("metadata.jl")
5860

5961
using .Wrappers
6062
using .Wrappers: geointerface_geomtype

src/fallbacks.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,11 @@ issimple(t::AbstractMultiPointTrait, geom) = allunique((getgeom(t, geom)))
9595
issimple(t::AbstractMultiCurveTrait, geom) = all(issimple.(getgeom(t, geom)))
9696
isclosed(t::AbstractMultiCurveTrait, geom) = all(isclosed.(getgeom(t, geom)))
9797

98-
crs(::Nothing, geom) = nothing
99-
crs(::AbstractTrait, geom) = nothing
98+
"The key used to retrieve and store the CRS from DataAPI.jl metadata, if no other solution exists in that format."
99+
const GEOINTERFACE_CRS_KEY = "GEOINTERFACE:crs"
100+
101+
crs(::Nothing, geom) = _get_dataapi_metadata(geom, GEOINTERFACE_CRS_KEY, nothing) # see `metadata.jl`
102+
crs(::AbstractTrait, geom) = _get_dataapi_metadata(geom, GEOINTERFACE_CRS_KEY, nothing) # see `metadata.jl`
100103

101104
# FeatureCollection
102105
getfeature(t::AbstractFeatureCollectionTrait, fc) = (getfeature(t, fc, i) for i in 1:nfeature(t, fc))

src/interface.jl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,28 @@ automatically delegate to this method.
3838
isfeaturecollection(x::T) where {T} = isfeaturecollection(T)
3939
isfeaturecollection(::Type{T}) where {T} = false
4040

41+
"""
42+
The key used to retrieve and store the geometrycolumns from DataAPI.jl metadata, if no other solution exists in that format.
43+
"""
44+
const GEOINTERFACE_GEOMETRYCOLUMNS_KEY = "GEOINTERFACE:geometrycolumns"
45+
4146
"""
4247
GeoInterface.geometrycolumns(featurecollection) => (:geometry,)
4348
4449
Retrieve the geometrycolumn(s) of `featurecollection`; the fields (or columns in a table)
4550
which contain geometries that support GeoInterface.
51+
52+
This is always a `Tuple` of `Symbol`s.
4653
"""
47-
geometrycolumns(featurecollection) = (:geometry,)
54+
function geometrycolumns(featurecollection)
55+
gcs = _get_dataapi_metadata(featurecollection, GEOINTERFACE_GEOMETRYCOLUMNS_KEY, (:geometry,)) # see `metadata.jl`
56+
return _aftercare_geometrycolumns(gcs)
57+
end
58+
59+
_aftercare_geometrycolumns(gcs::Tuple{Vararg{Symbol}}) = gcs
60+
_aftercare_geometrycolumns(gcs::Tuple{Vararg{String}}) = Symbol.(gcs)
61+
_aftercare_geometrycolumns(gcs::String) = (Symbol(gcs),)
62+
_aftercare_geometrycolumns(gcs::Symbol) = (gcs,)
4863

4964
"""
5065
GeoInterface.geometry(feat) => geom

src/metadata.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# This file contains internal helper functions to get metadata from DataAPI objects.
2+
# At some point, it may also contain methods to set metadata.
3+
4+
"""
5+
Internal function.
6+
7+
## Extended help
8+
9+
_get_dataapi_metadata(geom, key, default)
10+
11+
Get metadata associated with key `key` from some object, `geom`, that has DataAPI.jl metadata support.
12+
13+
If the object does not have metadata support, or the key does not exist, return `default`.
14+
"""
15+
function _get_dataapi_metadata(geom::GeomType, key, default) where GeomType
16+
if DataAPI.metadatasupport(GeomType).read # check that the type has metadata, and supports reading it
17+
if key in DataAPI.metadatakeys(geom) # check that the key exists
18+
return DataAPI.metadata(geom, key; style = false) # read the metadata
19+
end
20+
end
21+
return default
22+
end
23+

test/test_dataapi.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Test
2+
using GeoInterface
3+
using GeoFormatTypes: EPSG
4+
using GeoInterface.DataAPI
5+
6+
struct TestMetadata
7+
geometrycolumns
8+
crs
9+
end
10+
11+
DataAPI.metadatasupport(::Type{TestMetadata}) = (; read = true, write = false)
12+
DataAPI.metadatakeys(::TestMetadata) = ("GEOINTERFACE:geometrycolumns", "GEOINTERFACE:crs")
13+
function DataAPI.metadata(x::TestMetadata, key::String; style::Bool=false)
14+
if key === "GEOINTERFACE:geometrycolumns"
15+
style ? (x.geometrycolumns, :note) : x.geometrycolumns
16+
elseif key === "GEOINTERFACE:crs"
17+
style ? (x.crs, :note) : x.crs
18+
else
19+
nothing
20+
end
21+
end
22+
DataAPI.metadata(x::TestMetadata, key::String, default; style::Bool=false) = something(DataAPI.metadata(x, key; style), style ? (default, :note) : default)
23+
24+
25+
26+
27+
28+
@testset "DataAPI" begin
29+
td = TestMetadata((:g,), nothing)
30+
@test GeoInterface.geometrycolumns(td) == (:g,)
31+
@test GeoInterface.crs(td) === nothing
32+
33+
td = TestMetadata((:g,), EPSG(4326))
34+
@test GeoInterface.geometrycolumns(td) == (:g,)
35+
@test GeoInterface.crs(td) == EPSG(4326)
36+
37+
td = TestMetadata("geometry1", EPSG(4326))
38+
@test GeoInterface.geometrycolumns(td) == (:geometry1,)
39+
@test GeoInterface.crs(td) == EPSG(4326)
40+
end

0 commit comments

Comments
 (0)