@@ -175,9 +175,11 @@ with the same schema, but with the new geometry column.
175
175
This new table may be of the same type as the old one iff `Tables.materializer` is defined for
176
176
that table. If not, then a `NamedTuple` is returned.
177
177
=#
178
- function _apply_table (f:: F , target, iterable:: IterableType ; geometrycolumn = nothing , preserve_default_metadata = false , threaded, kw... ) where {F, IterableType}
179
- _get_col_pair (colname) = colname => Tables. getcolumn (iterable, colname)
180
- # We extract the geometry column and run `apply` on it.
178
+ function _apply_table (f:: F , target, iterable:: IterableType ; geometrycolumn = nothing , preserve_default_metadata = false , threaded, kw... ) where {F, IterableType} # We extract the geometry column and run `apply` on it.
179
+ # First, we need the table schema:
180
+ input_schema = Tables. schema (iterable)
181
+ input_colnames = input_schema. names
182
+ # then, we find the geometry column(s)
181
183
geometry_columns = if isnothing (geometrycolumn)
182
184
GI. geometrycolumns (iterable)
183
185
elseif geometrycolumn isa NTuple{N, <: Symbol } where N
@@ -187,31 +189,31 @@ function _apply_table(f::F, target, iterable::IterableType; geometrycolumn = not
187
189
else
188
190
throw (ArgumentError (" geometrycolumn must be a Symbol or a tuple of Symbols, got a $(typeof (geometrycolumn)) " ))
189
191
end
190
- if ! all ( Base. Fix2 (in, Tables . columnnames (iterable)), geometry_columns )
192
+ if ! Base. issubset (geometry_columns, input_colnames )
191
193
throw (ArgumentError (
192
194
"""
193
195
`apply`: the `geometrycolumn` kwarg must be a subset of the column names of the table,
194
196
got $(geometry_columns)
195
197
but the table has columns
196
- $(Tables . columnnames (iterable) )
198
+ $(input_colnames )
197
199
"""
198
200
))
199
201
end
202
+ # here we apply the function to the geometry column(s).
203
+ apply_kw = if isempty (used_reconstruct_table_kwargs (iterable))
204
+ kw
205
+ else
206
+ Base. structdiff (values (kw), NamedTuple{used_reconstruct_table_kwargs (iterable)})
207
+ end
200
208
new_geometry_vecs = map (geometry_columns) do colname
201
- _apply (f, target, Tables. getcolumn (iterable, colname); threaded, kw ... )
209
+ _apply (f, target, Tables. getcolumn (iterable, colname); threaded, apply_kw ... )
202
210
end
203
- # Then, we obtain the schema of the table,
204
- old_schema = Tables. schema (iterable)
205
- # filter the geometry column out,
206
- new_names = filter (x -> ! (x in geometry_columns), old_schema. names)
211
+ # Then, we filter the geometry column(s) out,
212
+ new_names = filter (x -> ! (x in geometry_columns), input_colnames)
207
213
# and try to rebuild the same table as the best type - either the original type of `iterable`,
208
214
# or a named tuple which is the default fallback.
209
- result = Tables. materializer (iterable)(
210
- merge (
211
- NamedTuple {geometry_columns, Base.Tuple{typeof.(new_geometry_vecs)...}} (new_geometry_vecs),
212
- NamedTuple (Iterators. map (_get_col_pair, new_names))
213
- )
214
- )
215
+ # See the function directly below this one for the actual fallback implementation.
216
+ result = reconstruct_table (iterable, geometry_columns, new_geometry_vecs, new_names; kw... )
215
217
# Finally, we ensure that metadata is propagated correctly.
216
218
# This can only happen if the original table supports metadata reads,
217
219
# and the result supports metadata writes.
@@ -246,6 +248,51 @@ function _apply_table(f::F, target, iterable::IterableType; geometrycolumn = not
246
248
return result
247
249
end
248
250
251
+
252
+ """
253
+ used_reconstruct_table_kwargs(input)
254
+
255
+ Return a tuple of the kwargs that should be passed to `reconstruct_table` for the given input.
256
+
257
+ This is "semi-public" API, and required for any input type that defines `reconstruct_table`.
258
+ """
259
+ function used_reconstruct_table_kwargs (input)
260
+ ()
261
+ end
262
+
263
+ """
264
+ reconstruct_table(input, geometry_column_names, geometry_columns, other_column_names, args...; kwargs...)
265
+
266
+ Reconstruct a table from the given input, geometry column names,
267
+ geometry columns, and other column names.
268
+
269
+ Any function that defines `reconstruct_table` must also define `used_reconstruct_table_kwargs`.
270
+
271
+ The input must be a table.
272
+
273
+ The function should return a best-effort attempt at a table of the same type as the input,
274
+ with the new geometry column(s) and other columns.
275
+
276
+ The fallback implementation invokes `Tables.materializer`. But if you want to be efficient
277
+ and pass e.g. arbitrary kwargs to the materializer, or materialize in a different way, you
278
+ can do so by overloading this function for your desired input type.
279
+
280
+ This is "semi-public" API and while it may add optional arguments, it will not add new required
281
+ positional arguments. All implementations must allow arbitrary kwargs to pass through and harvest
282
+ what they need.
283
+ """
284
+ function reconstruct_table (input, geometry_column_names, geometry_columns, other_column_names, args... ; kwargs... )
285
+ @assert Tables. istable (input)
286
+ _get_col_pair (colname) = colname => Tables. getcolumn (input, colname)
287
+
288
+ return Tables. materializer (input)(
289
+ merge (
290
+ NamedTuple {geometry_column_names, Base.Tuple{typeof.(geometry_columns)...}} (geometry_columns),
291
+ NamedTuple (Iterators. map (_get_col_pair, other_column_names))
292
+ )
293
+ )
294
+ end
295
+
249
296
# Rewrap all FeatureCollectionTrait feature collections as GI.FeatureCollection
250
297
# Maybe use threads to call _apply on component features
251
298
@inline function _apply (f:: F , target, :: GI.FeatureCollectionTrait , fc;
@@ -323,12 +370,20 @@ end
323
370
# So the `Target` is found. We apply `f` to geom and return it to previous
324
371
# _apply calls to be wrapped with the outer geometries/feature/featurecollection/array.
325
372
_apply (f:: F , :: TraitTarget{Target} , :: Trait , geom; crs= GI. crs (geom), kw... ) where {F,Target,Trait<: Target } = f (geom)
373
+ function _apply (a:: WithTrait{F} , :: TraitTarget{Target} , trait:: Trait , geom; crs= GI. crs (geom), kw... ) where {F,Target,Trait<: Target }
374
+ a (trait, geom; Base. structdiff (values (kw), NamedTuple{(:threaded , :calc_extent )})... )
375
+ end
326
376
# Define some specific cases of this match to avoid method ambiguity
327
377
for T in (
328
378
GI. PointTrait, GI. LinearRing, GI. LineString,
329
379
GI. MultiPoint, GI. FeatureTrait, GI. FeatureCollectionTrait
330
380
)
331
- @eval _apply (f:: F , target:: TraitTarget{<:$T} , trait:: $T , x; kw... ) where F = f (x)
381
+ @eval begin
382
+ _apply (f:: F , target:: TraitTarget{<:$T} , trait:: $T , x; kw... ) where F = f (x)
383
+ function _apply (a:: WithTrait{F} , target:: TraitTarget{<:$T} , trait:: $T , x; kw... ) where F
384
+ a (trait, x; Base. structdiff (values (kw), NamedTuple{(:threaded , :calc_extent )})... )
385
+ end
386
+ end
332
387
end
333
388
334
389
0 commit comments