GeoJSON.jl icon indicating copy to clipboard operation
GeoJSON.jl copied to clipboard

Support `Base.filter` on `FeatureCollection`

Open jfb-h opened this issue 1 year ago • 8 comments

It would be convenient if you could do something like filter(f -> f.NAME == "France", collection), but currently filtering is not supported.

Right now, I'm naivley pirating this like so:

function Base.filter(f::Function, fc::GeoJSON.FeatureCollection)
    features = [feat for feat in fc if f(feat)]
    GeoJSON.FeatureCollection(; bbox=nothing, features, crs=getfield(fc, :crs))
end

But I'm sure there is potential for improvement.

jfb-h avatar Apr 06 '24 10:04 jfb-h

Is there a reason not to convert to a DataFrames.jl DataFrame and filter that?

rafaqz avatar Apr 06 '24 16:04 rafaqz

Well my use case was GeoMakie, which works directly on FeatureCollections.

jfb-h avatar Apr 06 '24 18:04 jfb-h

You can pass the geometry column of a dataframe to GeoMakie and it should just work.

asinghvi17 avatar Apr 06 '24 19:04 asinghvi17

It doesn't, though. Here's a reproducer:

using CairoMakie, GeoMakie, GeoMakie.GeoJSON
using DataFrames, HTTP

url = "https://raw.githubusercontent.com/" *
      "nvkelso/natural-earth-vector/master/geojson/" *
      "ne_10m_admin_0_countries.geojson"

df = GeoJSON.read(HTTP.get(url).body) |> DataFrame

let fig = Figure()
    ax = GeoAxis(fig[1, 1])
    poly!(ax, df.geometry)
    fig
end

this throws a conversion error:

ERROR: `Makie.convert_arguments` for the plot type Scatter and its conversion trait PointBased() was unsuccessful.

The signature that could not be converted was:
::Vector{Any}
[...]

I suspect it has to do with the geometry column having both polygon and multipolygon entries:

julia> df.geometry
258-element Vector{Union{GeoJSON.MultiPolygon{2, Float32}, GeoJSON.Polygon{2, Float32}}}:
 2D MultiPolygonwith 264 sub-geometries
 2D MultiPolygonwith 17 sub-geometries
 2D MultiPolygonwith 163 sub-geometries
 2D Polygonwith 1 sub-geometries
[...]

(there seems to be a space missing in the show method for polygons, btw.)

So I guess it might just be an XY problem, but maybe having filter(f, ::FeatureCollection) would still make sense?

jfb-h avatar Apr 06 '24 20:04 jfb-h

ah yeah, somehow the type doesn't union to GeoJSON.AbstractGeometry, which is a bummer.

In this case you can do something like this: https://github.com/MakieOrg/GeoMakie.jl/blob/3ba224449aea3c78d81931b5a9a177be360d1ec6/src/conversions.jl#L16

basically forcibly convert all of your geometries to multipolygons, so that the array has the correct type.

asinghvi17 avatar Apr 06 '24 23:04 asinghvi17

As an aside GeoMakie.jl could accept any Tables.jl compatible table and get the GeoInterface.geometrycolumns column too remove one step.

But yeah that dispatch on mixed geometry vectors is annoying in a lot of places.

@asinghvi17 would wrapping a mixed column as a GeometryCollection be another hack to make that work?

rafaqz avatar Apr 06 '24 23:04 rafaqz

I'm not sure that GeoMakie would accept a geometrycollection, given that it has to go to multiple plot types. Perhaps after Makie v0.21 once we figure out this specapi thing...

We could special case it for the poly recipe so it only plots the polygon components of the geometrycollection though, that would probably work.

asinghvi17 avatar Apr 07 '24 00:04 asinghvi17

Anyway, I will close this. Operations like filter should just use a dataframe, we cant implement everything everywhere.

rafaqz avatar Apr 07 '24 10:04 rafaqz

If you want to use filter on a GeoJSON feature collection, you can use TableOperations.jl, which is implementation-agnostic - though it will probably be slower than DataFrames!

asinghvi17 avatar Aug 18 '24 22:08 asinghvi17