@@ -5,27 +5,40 @@ A feature wrapping the JSON object.
55"""
66struct Feature{T}
77 object:: T
8+ parent_properties:: Vector{Symbol}
89end
910
10- Feature {T} (f:: Feature{T} ) where {T} = f
11- Feature (; geometry:: Geometry , kwargs... ) =
11+ Feature (f:: Feature ) = f
12+ Feature (f:: Feature , names:: Vector{Symbol} ) = Feature (object (f), names)
13+ Feature {T} (f:: Feature , names:: Vector{Symbol} ) where T = Feature (object (f), names)
14+ Feature {T} (f:: Feature ) where {T} = f
15+ Feature (object) = Feature (object, Symbol[])
16+ Feature (; geometry:: Union{Geometry,Missing,Nothing} , kwargs... ) =
1217 Feature (merge ((type = " Feature" , geometry), kwargs))
13- Feature (geometry:: Geometry ; kwargs... ) =
18+ Feature (geometry:: Union{ Geometry,Missing,Nothing} ; kwargs... ) =
1419 Feature (merge ((type = " Feature" , geometry), kwargs))
1520
1621"""
1722 properties(f::Union{Feature,FeatureCollection})
1823
1924Access the properties JSON object of a Feature
2025"""
26+ properties (obj:: JSON3.Object ) = obj. properties
2127properties (f:: Feature ) = object (f). properties
2228
2329"""
2430 geometry(f::Feature)
2531
2632Access the JSON object that represents the Feature's geometry
2733"""
28- geometry (f:: Feature ) = geometry (object (f). geometry)
34+ function geometry (f:: Feature{<:JSON3.Object} )
35+ geom = geometry (object (f). geometry)
36+ return ismissing (geom) ? nothing : geom
37+ end
38+ function geometry (f:: Feature{<:NamedTuple} )
39+ geom = object (f). geometry
40+ return ismissing (geom) ? nothing : geom
41+ end
2942
3043coordinates (f:: Feature ) = coordinates (geometry (object (f). geometry))
3144
@@ -43,13 +56,23 @@ function Base.getproperty(f::Feature, nm::Symbol)
4356 geometry (f)
4457 else
4558 props = properties (f)
46- getproperty (props, nm)
59+ if hasproperty (props, nm)
60+ getproperty (props, nm)
61+ else
62+ if nm in getfield (f, :parent_properties )
63+ missing
64+ else
65+ error (" property `$nm ` is not part of Feature or parent FeatureCollection" )
66+ end
67+ end
4768 end
4869 return ifelse (x === nothing , missing , x)
4970end
5071
5172Base.:(== )(f1:: Feature , f2:: Feature ) = object (f1) == object (f2)
5273
74+
75+
5376"""
5477 FeatureCollection <: AbstractVector{Feature}
5578
@@ -61,25 +84,39 @@ and similarly the GeoInterface.jl interface.
6184struct FeatureCollection{T,O,A} <: AbstractVector{T}
6285 object:: O
6386 features:: A
87+ names:: Vector{Symbol}
88+ types:: Dict{Symbol,Type}
6489end
65-
66- function FeatureCollection (object:: O ) where {O}
90+ function FeatureCollection (object:: O ) where O
6791 features = object. features
68- T = isempty (features) ? Feature{Any} : typeof (Feature (first (features)))
69- return FeatureCollection {T,O,typeof(features)} (object, features)
92+ if isempty (features)
93+ names = Symbol[:geometry ]
94+ types = Dict {Symbol,Type} (:geometry => Union{Missing,Geometry})
95+ T = Feature{Any}
96+ else
97+ names, types = property_schema (features)
98+ insert! (names, 1 , :geometry )
99+ types[:geometry ] = Union{Missing,Geometry}
100+ f1 = first (features)
101+ T = if f1 isa JSON3. Object
102+ typeof (Feature (f1, names))
103+ elseif f1 isa NamedTuple && isconcretetype (eltype (features))
104+ typeof (Feature (f1, names))
105+ else
106+ T = Feature{Any}
107+ end
108+ end
109+ return FeatureCollection {T,O,typeof(features)} (object, features, names, types)
70110end
71-
72111function FeatureCollection (; features:: AbstractVector{T} , kwargs... ) where {T}
73- FT = ifelse (T <: Feature , T, Feature{T})
74112 object = merge ((type = " FeatureCollection" , features), kwargs)
75- return FeatureCollection {FT,typeof (object),typeof(features)} (object, features )
113+ return FeatureCollection (object)
76114end
115+ FeatureCollection (features:: AbstractVector ; kwargs... ) =
116+ FeatureCollection (; features, kwargs... )
77117
78- function FeatureCollection (features:: AbstractVector{T} ; kwargs... ) where {T}
79- FT = ifelse (T <: Feature , T, Feature{T})
80- object = merge ((type = " FeatureCollection" , features), kwargs)
81- return FeatureCollection {FT,typeof(object),typeof(features)} (object, features)
82- end
118+ names (fc:: FeatureCollection ) = getfield (fc, :names )
119+ types (fc:: FeatureCollection ) = getfield (fc, :types )
83120
84121"""
85122 features(fc::FeatureCollection)
@@ -90,16 +127,7 @@ features(fc::FeatureCollection) = getfield(fc, :features)
90127
91128# Base methods
92129
93- function Base. propertynames (fc:: FeatureCollection )
94- # get the propertynames from the first feature, if it exists
95- return if isempty (fc)
96- (:geometry ,)
97- else
98- f = first (fc)
99- propertynames (f)
100- end
101- end
102-
130+ Base. propertynames (fc:: FeatureCollection ) = getfield (fc, :names )
103131Base. getproperty (fc:: FeatureCollection , nm:: Symbol ) = getproperty .(fc, nm)
104132
105133Base. IteratorSize (:: Type{<:FeatureCollection} ) = Base. HasLength ()
@@ -109,31 +137,38 @@ Base.IteratorEltype(::Type{<:FeatureCollection}) = Base.HasEltype()
109137# read only AbstractVector
110138Base. size (fc:: FeatureCollection ) = size (features (fc))
111139Base. eltype (:: FeatureCollection{T} ) where {T<: Feature } = T
112- Base. getindex (fc:: FeatureCollection{T} , i) where {T<: Feature } = T (features (fc)[i])
113140Base. IndexStyle (:: Type{<:FeatureCollection} ) = IndexLinear ()
141+ function Base. getindex (fc:: FeatureCollection{T} , i) where {T<: Feature }
142+ f = features (fc)[i]
143+ if f isa Feature
144+ return f
145+ else
146+ return T (f, names (fc))
147+ end
148+ end
114149
115150function Base. iterate (fc:: FeatureCollection{T} ) where {T<: Feature }
116151 x = iterate (features (fc))
117152 x === nothing && return nothing
118153 val, state = x
119- return T (val), state
154+ return T (val, names (fc) ), state
120155end
121156
122157function Base. iterate (fc:: FeatureCollection{T} , state) where {T<: Feature }
123158 x = iterate (features (fc), state)
124159 x === nothing && return nothing
125160 val, state = x
126- return T (val), state
161+ return T (val, names (fc) ), state
127162end
128163
129- Base. show (io:: IO , fc:: FeatureCollection ) =
164+ Base. show (io:: IO , :: MIME"text/plain" , fc:: FeatureCollection ) =
130165 println (io, " FeatureCollection with $(length (fc)) Features" )
131166
132- function Base. show (io:: IO , f:: Feature )
167+ function Base. show (io:: IO , :: MIME"text/plain" , f:: Feature )
133168 geom = geometry (f)
134169 propnames = propertynames (f)
135170 n = length (propnames)
136- if geom === nothing
171+ if ismissing ( geom) || isnothing (geom)
137172 print (io, " Feature with null geometry" )
138173 else
139174 print (io, " Feature with a " , type (geom))
145180Tables. istable (:: Type{<:FeatureCollection} ) = true
146181Tables. rowaccess (:: Type{<:FeatureCollection} ) = true
147182Tables. rows (fc:: FeatureCollection ) = fc
183+ Tables. schema (fc:: FeatureCollection ) =
184+ Tables. Schema (getfield (fc, :names ), [getfield (fc, :types )[nm] for nm in getfield (fc, :names )])
148185
149186# methods that apply to all GeoJSON Objects
150187const GeoJSONObject = Union{Geometry,Feature,FeatureCollection}
@@ -164,3 +201,57 @@ type(x) = String(x.type)
164201bbox (x:: GeoJSONObject ) = get (object (x), :bbox , nothing )
165202
166203Base. show (io:: IO , :: MIME"text/plain" , x:: GeoJSONObject ) = show (io, x)
204+
205+ # Adapted from JSONTables.jl jsontable method
206+ # We cannot simply use their method as we need the key/value pairs
207+ # of the properties field, rather than the main object
208+ function property_schema (features)
209+ # Short cut for concrete eltypes of NamedTuple
210+ if features isa AbstractArray{<: NamedTuple } && isconcretetype (eltype (features))
211+ f1 = first (features)
212+ props = properties (f1)
213+ names = [keys (props)... ]
214+ types = Dict {Symbol,Type} (map ((k, v) -> k => typeof (v), keys (props), props)... )
215+ return names, types
216+ end
217+ # Otherwise find the shared names
218+ names = Symbol[]
219+ seen = Set {Symbol} ()
220+ types = Dict {Symbol, Type} ()
221+ for feature in features
222+ props = properties (feature)
223+ isnothing (props) && continue
224+ if isempty (names)
225+ for k in propertynames (props)
226+ k === :geometry && continue
227+ push! (names, k)
228+ types[k] = missT (typeof (props[k]))
229+ end
230+ seen = Set (names)
231+ else
232+ for nm in names
233+ if hasproperty (props, nm)
234+ T = types[nm]
235+ v = props[nm]
236+ if ! (missT (typeof (v)) <: T )
237+ types[nm] = Union{T, missT (typeof (v))}
238+ end
239+ else
240+ types[nm] = Union{Missing, types[nm]}
241+ end
242+ end
243+ for (k, v) in pairs (props)
244+ k === :geometry && continue
245+ if ! (k in seen)
246+ push! (seen, k)
247+ push! (names, k)
248+ types[k] = Union{Missing, missT (typeof (v))}
249+ end
250+ end
251+ end
252+ end
253+ return names, types
254+ end
255+
256+ missT (:: Type{Nothing} ) = Missing
257+ missT (:: Type{T} ) where {T} = T
0 commit comments