@@ -175,9 +175,11 @@ with the same schema, but with the new geometry column.
175175This new table may be of the same type as the old one iff `Tables.materializer` is defined for
176176that table. If not, then a `NamedTuple` is returned.
177177=#
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)
181183 geometry_columns = if isnothing (geometrycolumn)
182184 GI. geometrycolumns (iterable)
183185 elseif geometrycolumn isa NTuple{N, <: Symbol } where N
@@ -187,31 +189,31 @@ function _apply_table(f::F, target, iterable::IterableType; geometrycolumn = not
187189 else
188190 throw (ArgumentError (" geometrycolumn must be a Symbol or a tuple of Symbols, got a $(typeof (geometrycolumn)) " ))
189191 end
190- if ! all ( Base. Fix2 (in, Tables . columnnames (iterable)), geometry_columns )
192+ if ! Base. issubset (geometry_columns, input_colnames )
191193 throw (ArgumentError (
192194 """
193195 `apply`: the `geometrycolumn` kwarg must be a subset of the column names of the table,
194196 got $(geometry_columns)
195197 but the table has columns
196- $(Tables . columnnames (iterable) )
198+ $(input_colnames )
197199 """
198200 ))
199201 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
200208 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 ... )
202210 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)
207213 # and try to rebuild the same table as the best type - either the original type of `iterable`,
208214 # 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... )
215217 # Finally, we ensure that metadata is propagated correctly.
216218 # This can only happen if the original table supports metadata reads,
217219 # and the result supports metadata writes.
@@ -246,6 +248,51 @@ function _apply_table(f::F, target, iterable::IterableType; geometrycolumn = not
246248 return result
247249end
248250
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+
249296# Rewrap all FeatureCollectionTrait feature collections as GI.FeatureCollection
250297# Maybe use threads to call _apply on component features
251298@inline function _apply (f:: F , target, :: GI.FeatureCollectionTrait , fc;
@@ -323,12 +370,20 @@ end
323370# So the `Target` is found. We apply `f` to geom and return it to previous
324371# _apply calls to be wrapped with the outer geometries/feature/featurecollection/array.
325372_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
326376# Define some specific cases of this match to avoid method ambiguity
327377for T in (
328378 GI. PointTrait, GI. LinearRing, GI. LineString,
329379 GI. MultiPoint, GI. FeatureTrait, GI. FeatureCollectionTrait
330380)
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
332387end
333388
334389
0 commit comments