JuliaGeo / Proj.jl

Julia wrapper for the PROJ cartographic projections library
MIT License
48 stars 9 forks source link

Would be good to add a to_epsg implimentation #88

Closed alex-s-gardner closed 10 months ago

alex-s-gardner commented 1 year ago

robustly retrieving the epsg from a crs requires an implementation of pyproj's to_epsg

visr commented 1 year ago

Isn't this already present?

https://github.com/JuliaGeo/Proj.jl/blob/d88918ca72426245bcf52889854ad2aa08d1e7c8/src/crs.jl#L129-L133

Although it looks like this doesn't use proj_get_id_auth_name to check if it is EPSG, so that's a bug.

alex-s-gardner commented 1 year ago

I believe this is a very limited version of to_epsg as it can only find an EPSG code if the Authority and code is contained in the CRS. For example it can't infer the EPSG code from a ProjString or wkt that does not include the authority. This is the same limitation as my ArchGDAL PR. to_epsg has a bunch of logic to try and infer the EPSG and also returns a confidence level that it found the right code.

alex-s-gardner commented 1 year ago

I think we'll need to use Proj.proj_identify

alex-s-gardner commented 1 year ago

Can someone provide an example of how to use Proj.proj_identify, I can't seem to figure out how to assign and retrieve the C objects.

visr commented 1 year ago

This seems to work:

using Proj
import GeoFormatTypes as GFT

# create a CRS from the proj string that represents EPSG:4326
crs = Proj.CRS("+proj=longlat +datum=WGS84 +no_defs +type=crs")
# attempt to identify the corresponding EPSG code using proj_identify
out_confidence = Ref(Ptr{Cint}(C_NULL))
pj_list = Proj.proj_identify(crs, "EPSG", out_confidence)
# TODO use these to check for error and list length
pj_list == C_NULL
Proj.proj_list_get_count(pj_list)
# get the first resulting CRS
pj_1 = Proj.proj_list_get(pj_list, 0)
crs_1 = Proj.CRS(pj_1)
confidence_1 = unsafe_load(out_confidence[])
# use i in unsafe_load(p::Ptr{T}, i::Integer=1) for other indices

# doesn't work: https://github.com/JuliaGeo/Proj.jl/issues/88#issuecomment-1645894753
GFT.EPSG(crs)
# does work:
GFT.EPSG(crs_1) == GFT.EPSG(4326)
alex-s-gardner commented 1 year ago

Should we introduce this into proj as to_epsg() then use that for GFT.EPSG(crs::CRS)?

visr commented 1 year ago

Yeah perhaps we can create a Proj.identify that is a nicely wrapped version of the C API Proj.proj_identify.

Then for GFT.EPSG(crs::CRS) we can use that function with auth_name set to EPSG and only accepting a confidence of 70 or higher like pyproj.

I just noticed my use of out_confidence was incorrect, that is an array that is filled by proj with the confidence level per entry.

alex-s-gardner commented 1 year ago

In the example above how should out_confidence be initialized then? I don't have a great mental model yet when calling out to C

visr commented 1 year ago

Hmm yeah I'm also not great with this stuff, but managed to figure it out, see the updated example.

So you need a reference to a pointer of the right type. We can initialize this with a null pointer, PROJ will point it to an array that it allocates itself. After calling the function we can see that out_confidence[] now points somewhere, and use unsafe_load to get the value(s). In this example I got 70. Note the C API docs that explain that we have to free the PROJ allocated array ourselves with Proj.proj_int_list_destroy(out_confidence[]).

alex-s-gardner commented 1 year ago

I'll work on a PR

alex-s-gardner commented 1 year ago

@visr for the higher level identify I think we should just return the top result. I can really see a use case for returning all matches. If that functionality is needed the user can drop a level to proj_identify

alex-s-gardner commented 1 year ago

@visr I think this is good to merge. If you agree I will work on to_EPSG next

visr commented 1 year ago

Left some comments on #92. For to_EPSG you mean GFT.EPSG(crs::CRS) right?