Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New (breaking) Traits based release #33

Merged
merged 46 commits into from
May 16, 2022
Merged

New (breaking) Traits based release #33

merged 46 commits into from
May 16, 2022

Conversation

evetion
Copy link
Member

@evetion evetion commented Aug 24, 2020

Copy link
Member

@juliohm juliohm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome interface. Thank you for the effort again.

src/GeoInterfaceRFC.jl Outdated Show resolved Hide resolved
@rafaqz
Copy link
Member

rafaqz commented Sep 11, 2021

What is the status of this PR? It would be good to build GeoData.jl polygon operations off this rather than master.

.travis.yml Outdated Show resolved Hide resolved
Copy link
Member

@yeesian yeesian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thrown off by the [WIP] in the title, but the implementation is great! Sorry for my slowness in reviewing this PR!

@rafaqz
Copy link
Member

rafaqz commented Oct 15, 2021

I've been reading over this too now I'm using GeoInterface more. It seems like a real improvement, I'm keen to help getting it merged.

My comments so far are:

  • it seems that extent replaces bbox ? I can't find the implementation of extent
  • crs should probably return nothing instead of missing by default. nothing is more often used like that.
  • We will probably need more fallbacks that return nothing when called on an object that doesn't support the trait. I've just added these to the old GeoInterface for bbox and crs so we can call it just in case, as e.g. Shapefiles have bbox precalculated for everything, but other gemoetries may not.
  • We should make sure that coordnames and x/yconstant-propagates and findfist isn't a runtime lookup.

src/interface.jl Outdated
getpolygon(geom, i::Integer) = getpolygon(geomtype(geom), geom, i)

# Other methods
crs(geom) = missing # or conforming to <:CoordinateReferenceSystemFormat in GeoFormatTypes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
crs(geom) = missing # or conforming to <:CoordinateReferenceSystemFormat in GeoFormatTypes
crs(geom) = nothing # or conforming to <:CoordinateReferenceSystemFormat in GeoFormatTypes

src/interface.jl Outdated Show resolved Hide resolved
src/interface.jl Outdated Show resolved Hide resolved
README.md Outdated
There are also optional generic methods that could help or speed up operations:
```julia
GeoInterface.crs(geom)::Union{Missing, GeoFormatTypes.CoordinateReferenceSystemFormat}
GeoInterface.extent(geom) # geomtype -> GeoInterface.Rectangle
Copy link
Member

@rafaqz rafaqz Oct 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extent needs to be implemented.

Could it hold/be a NamedTuple ? Like extent(obj) = Extent((X=(xmin, xmax), Y=(ymin, ymax))) etc for how many dimensions there are, using the :X, :Y, :Z, :M names from the interface.

I find this organisation is easier to use than the xmin, ymin, xmax, ymax style bbox usually uses.

(but somehow I missed that its meant to return a Rectangle. I guess that makes sense in a different way.)

@rafaqz
Copy link
Member

rafaqz commented Nov 1, 2021

I would like to propose an Extent object to be used instead of Rectangle for the return value of extent. It wraps a NamedTuple because all fields are not always present.

struct Extent{T<:NamedTuple}
    bounds::T
end
Extent(; kw...) = Extent(values(kw))
Base.getproperty(ext::Extent, x::Symbol) = getproperty(getfield(ext, :bounds), x)

ext = Extent(X=(0.0, 10.0), Y=(0.0, 10.0))
ext.X

Giving it a specific type means we can dispatch on it, and including the dimension names means we know their order in the object.

@evetion
Copy link
Member Author

evetion commented Nov 6, 2021

Ah yes, I agree that we need some sort of bounds object. I've had issues finding a generic one, hence the default to a Rectangle. Nowadays I just revert to using a namedtuple, but am frustrated by the fact that a (min_x=1, min_y=2) != (min_y=2, min_x=1)(nor its types), which for all purposes would be equal.

@visr Do you remember my magical bbox code that you reviewed and we shelved for it being too complex? I can't seem to find it.

edit: Found it here JuliaGeo/GeoInterfaceRFC.jl@527198b#diff-64b0c51af7618fbd8cd973bf88690c8b22c309f9b718e4fdd41b7555769f23fdR25

@rafaqz
Copy link
Member

rafaqz commented Nov 6, 2021

Yeah, a NamedTuple is not enough. But its close to it... that's why I've just wrapped it with Extent. You get a lot of the code in your BoundingBox object for free just by forwarding to NamedTuple.

With the Extent wrapper we could define == to check that both objects have the same dimension names (but not necessarily the same order), and then match all of the values by the names.

@rafaqz
Copy link
Member

rafaqz commented Nov 6, 2021

Better implementation including ==

struct Extent{T<:NamedTuple}
    bounds::T
end
Extent(; kw...) = Extent(values(kw))

bounds(ext::Extent) = getfield(ext, :bounds)

Base.getproperty(ext::Extent, x::Symbol) = getproperty(bounds(ext), x)
Base.getindex(ext::Extent, x::Symbol) = getindex(bounds(ext), x)
import Base: ==
function (==)(a::Extent{<:NamedTuple{K1}}, b::Extent{<:NamedTuple{K2}}) where {K1, K2}
    length(K1) == length(K2) || return false
    all(map(k -> k in K1, K2)) || return false
    return all(map((k -> a[k] == b[k]), K1))
end

And the order doesn't matter for ==:

julia> Extent(X=(0, 1), Y=(3.0, 4.0)) == Extent(Y=(3.0, 4.0), X=(0, 1))
true

julia> @btime Extent(X=(0, 1), Y=(3.0, 4.0)) == Extent(Y=(3.0, 4.0), X=(0, 1))
  4.004 ns (0 allocations: 0 bytes)
true

@rafaqz
Copy link
Member

rafaqz commented Mar 1, 2022

Cautionary tale against using missing as a default return value in an interface:
JuliaArrays/ArrayInterface.jl#245

@evetion
Copy link
Member Author

evetion commented Mar 1, 2022

Thanks, that's good to know when updating this PR.

@evetion
Copy link
Member Author

evetion commented Mar 28, 2022

Some updates:

  • Removed merge conflicts
  • Added documentation (still more to do)
  • Removed primitives, they could be added back (Vector/Tuple is a Point? but what about other geometries?)
  • Added isgeometry check, mimicking istable from Tables.jl
  • Enumerated current packages using GeoInterface
  • it seems that extent replaces bbox ? I can't find the implementation of extent

Will now use Extents.jl once registered.

  • crs should probably return nothing instead of missing by default. nothing is more often used like that.

Done


TODO

  • We will probably need more fallbacks that return nothing when called on an object that doesn't support the trait. I've just added these to the old GeoInterface for bbox and crs so we can call it just in case, as e.g. Shapefiles have bbox precalculated for everything, but other gemoetries may not.
  • We should make sure that coordnames and x/yconstant-propagates and findfist isn't a runtime lookup.

And

  • Implement this in other packages (at least the most used)
  • Think about automatic converts between geometries based on GeoInterface
  • Do we need a native WKT/WKB implementation?

@evetion evetion changed the title [WIP] New (breaking) Traits based release New (breaking) Traits based release Apr 4, 2022
@evetion evetion requested a review from rafaqz April 4, 2022 12:13
Copy link
Member

@rafaqz rafaqz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty solid to me. I probably have to see it in PRs to give more extensive feedback.

docs/src/background/sf.md Outdated Show resolved Hide resolved
docs/src/background/sf.md Outdated Show resolved Hide resolved
docs/src/background/sf.md Outdated Show resolved Hide resolved
docs/src/background/sf.md Outdated Show resolved Hide resolved
docs/src/guides/developer.md Outdated Show resolved Hide resolved
docs/src/tutorials/usage.md Outdated Show resolved Hide resolved
src/defaults.jl Outdated Show resolved Hide resolved
src/defaults.jl Outdated Show resolved Hide resolved
Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
@rafaqz
Copy link
Member

rafaqz commented May 9, 2022

I just hit a findfirst error here - it doesn't work on Tuple.

const default_coord_names = (:X, :Y, :Z, :M)

x(::AbstractPointTrait, geom) = getcoord(geom, findfirst(coordnames(geom), :X))
y(::AbstractPointTrait, geom) = getcoord(geom, findfirst(coordnames(geom), :Y))
z(::AbstractPointTrait, geom) = getcoord(geom, findfirst(coordnames(geom), :Z))
m(::AbstractPointTrait, geom) = getcoord(geom, findfirst(coordnames(geom), :M))

And default_coord_names is a tuple ;)

@evetion
Copy link
Member Author

evetion commented May 9, 2022

I just hit a findfirst error here - it doesn't work on Tuple.

My bad, I've fixed it and added a test for it. Will add some more tests on the defaults, and enable coverage to prevent this.

@visr visr mentioned this pull request May 15, 2022
.github/workflows/CI.yml Outdated Show resolved Hide resolved
@evetion evetion mentioned this pull request May 16, 2022
12 tasks
@evetion evetion merged commit d8c7c22 into master May 16, 2022
This was referenced May 16, 2022
@rafaqz rafaqz deleted the v1-traits branch February 7, 2023 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants