How can I get Proj4 to transform multiple locations?

This is probably more of a Julia question than a Proj4 question, but how can I perform coordinate transformations on multiple locations at once? I’m writing a function that would need to handle the following three cases:

1: A single location

The Proj4.jl readme provides clear guidance, and I’m able to get it working perfectly:

using Proj4

# Define the transformation: 
trans = Proj4.Transformation("EPSG:4326", "+proj=utm +zone=32 +datum=WGS84");

# Define a single location: 
lat = 55; 
lon = 12; 

# A single location works perfectly: 
x, y = trans([lat,lon]);
println(x)
println(y)
>691875.632137542
>6.098907825129169e6

Perfect. Above, we’ve transformed (55N,12E) into projected coordinates in meters. Great, now try something similar for:

2: Multiple arbitrary locations:

Following a similar method to what’s described above, I’d like to transform four arbitrary geographic locations into projected coordinates. So here for lat and lon I enter four values, and try adding a . in the call to trans():

# Four locations:
lat = [54, 55, 56, 57];
lon = [11, 12, 13, 14];
x, y = trans.([lat, lon]);
println(x)
println(y)
>[3.3824660011659563e6, 7.011981052605791e6, 56.0, 57.0]
>[827871.392810812, 1.217618384382686e6, 13.0, 14.0]

Oh no! The location (55N,12E) no longer returns the same projected coordinate values as we got when we only entered it as a single location. And the last two points are just returning the same values we fed into trans().

3: A grid of points

I can think of two ways I might transform a grid of points. One way might be to transpose the lon variable:

# Four equally-spaced points in each dimension to creates a grid:  
lat = [54, 55, 56, 57]; 
lon = [11, 12, 13, 14]; 

# Does transposing lon work? (Nope)
x, y = trans.([lat, lon']);
>[3.3824660011659563e6, 7.011981052605791e6, 56.0, 57.0]
>[827871.392810812, 1.217618384382686e6, 13.0, 14.0]

How about an array comprehension? Seems like this might be able to create a grid of transformed coordinates:

x, y = [trans([lati, loni]) for lati=lat, loni=lon]
print(x)
print(y)
> [631090.8762477529, 5.985372985435059e6][627928.1913765112, 6.096620706492608e6]

That’s only two locations!

So what’s going on here? How can I get Proj4 to transform a single point, an arbitrary array of points, and a grid?

julia> [trans([latᵢ, lonᵢ]) for (latᵢ, lonᵢ) ∈ zip(lat, lon)]
4-element Vector{StaticArrays.SVector{2, Float64}}:
 [631090.8762477529, 5.985372985435059e6]
 [691875.632137542, 6.098907825129169e6]
 [749395.3327601748, 6.21330158730825e6]
 [803582.3889876883, 6.328506458168089e6]

EDIT: Scratch that, I missed that your issue is the left hand side assignment rather than the construction of your comprehension!

1 Like

For the grid case, your solution works, but you are only extracting two positions by assigning x and y.
If you want to get a matrix for the x and y positions you can do:

julia> grid = [trans([lati, loni]) for lati=lat, loni=lon]
4×4 Matrix{StaticArrays.SVector{2, Float64}}:
 [6.31091e5, 5.98537e6]  [6.96621e5, 5.98769e6]  …  [827619.0, 5.9951e6]
 [6.27928e5, 6.09662e6]  [6.91876e5, 6.09891e6]     [8.19704e5, 6.10623e6]
 [6.24726e5, 6.20788e6]  [6.87071e5, 6.21014e6]     [8.11691e5, 6.21737e6]
 [6.21486e5, 6.31916e6]  [6.8221e5, 6.32139e6]      [8.03582e5, 6.32851e6]
julia> first.(grid)
4×4 Matrix{Float64}:
 6.31091e5  6.96621e5       7.62132e5  827619.0
 6.27928e5  6.91876e5  755803.0             8.19704e5
 6.24726e5  6.87071e5       7.49395e5       8.11691e5
 6.21486e5  6.8221e5        7.42911e5       8.03582e5

julia> last.(grid)
4×4 Matrix{Float64}:
 5.98537e6  5.98769e6  5.99093e6  5.9951e6
 6.09662e6  6.09891e6  6.10211e6  6.10623e6
 6.20788e6  6.21014e6  6.2133e6   6.21737e6
 6.31916e6  6.32139e6  6.3245e6   6.32851e6
1 Like

You may consider also using GMT.jl

using GMT
lat = [54, 55, 56, 57]; lon = [11, 12, 13, 14];

lonlat2xy([lon[:] lat[:]], t_srs="+proj=utm +zone=32 +datum=WGS84")
4×2 Matrix{Float64}:
 6.31091e5  5.98537e6
 6.91876e5  6.09891e6
 7.49395e5  6.2133e6
 8.03582e5  6.32851e6
1 Like

@joa-quim I had not considered using GMT. Trying it out, however, I’m running into a this error:

using GMT
lat = [54, 55, 56, 57]; 
lon = [11, 12, 13, 14];

lonlat2xy([lon[:] lat[:]], t_srs="+proj=utm +zone=32 +datum=WGS84")
UndefVarError: lonlat2xy not defined

Stacktrace:
  [1] top-level scope
    @ ~/Documents/coursework/Untitled-1.ipynb:5
  [2] eval
    @ ./boot.jl:373 [inlined]
  [3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
    @ Base ./loading.jl:1196
  [4] #invokelatest#2
    @ ./essentials.jl:716 [inlined]
  [5] invokelatest
    @ ./essentials.jl:714 [inlined]
  [6] (::VSCodeServer.var"#164#165"{VSCodeServer.NotebookRunCellArguments, String})()
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.6.17/scripts/packages/VSCodeServer/src/serve_notebook.jl:19
  [7] withpath(f::VSCodeServer.var"#164#165"{VSCodeServer.NotebookRunCellArguments, String}, path::String)
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.6.17/scripts/packages/VSCodeServer/src/repl.jl:184
  [8] notebook_runcell_request(conn::VSCodeServer.JSONRPC.JSONRPCEndpoint{Base.PipeEndpoint, Base.PipeEndpoint}, params::VSCodeServer.NotebookRunCellArguments)
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.6.17/scripts/packages/VSCodeServer/src/serve_notebook.jl:13
  [9] dispatch_msg(x::VSCodeServer.JSONRPC.JSONRPCEndpoint{Base.PipeEndpoint, Base.PipeEndpoint}, dispatcher::VSCodeServer.JSONRPC.MsgDispatcher, msg::Dict{String, Any})
    @ VSCodeServer.JSONRPC ~/.vscode/extensions/julialang.language-julia-1.6.17/scripts/packages/JSONRPC/src/typed.jl:67
 [10] serve_notebook(pipename::String, outputchannel_logger::Base.CoreLogging.SimpleLogger; crashreporting_pipename::String)
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.6.17/scripts/packages/VSCodeServer/src/serve_notebook.jl:136
 [11] top-level scope
    @ ~/.vscode/extensions/julialang.language-julia-1.6.17/scripts/notebook/notebook.jl:32
 [12] include(mod::Module, _path::String)
    @ Base ./Base.jl:418
 [13] exec_options(opts::Base.JLOptions)
    @ Base ./client.jl:292
 [14] _start()
    @ Base ./client.jl:495

Any idea what’s going on here? I’ve just updated all my packages.

Did you install GMT itself as well or just GMT.jl wrapper? (It happens to us all)

2 Likes

Doh! Didn’t know I needed to install GMT separately from GMT.jl. Thanks for the tip!

No. Since a couple a couple of years GMT.jl automatically installs GMT via conda.
When GMT is not installed there are previous error messages.

What does GMTver prints? Do the tests run? e.g. ] test GMT

1 Like

Hmm. Here are the outputs from ] test GMT:

(@v1.7) pkg> test GMT
     Testing GMT
      Status `/private/var/folders/3q/36tmwvtx50ldmv31f23wfz7w0000gq/T/jl_xVksQB/Project.toml`
  [8f4d0f93] Conda v1.7.0
  [5752ebe1] GMT v0.41.2
  [ade2ca70] Dates `@stdlib/Dates`
  [44cfe95a] Pkg `@stdlib/Pkg`
  [de0858da] Printf `@stdlib/Printf`
  [10745b16] Statistics `@stdlib/Statistics`
  [8dfed614] Test `@stdlib/Test`
      Status `/private/var/folders/3q/36tmwvtx50ldmv31f23wfz7w0000gq/T/jl_xVksQB/Manifest.toml`
  [8f4d0f93] Conda v1.7.0
  [5752ebe1] GMT v0.41.2
  [682c06a0] JSON v0.21.3
  [69de0a69] Parsers v2.3.1
  [81def892] VersionParsing v1.3.0
  [0dad84c5] ArgTools `@stdlib/ArgTools`
  [56f22d72] Artifacts `@stdlib/Artifacts`
  [2a0f44e3] Base64 `@stdlib/Base64`
  [ade2ca70] Dates `@stdlib/Dates`
  [f43a241f] Downloads `@stdlib/Downloads`
  [b77e0a4c] InteractiveUtils `@stdlib/InteractiveUtils`
  [b27032c2] LibCURL `@stdlib/LibCURL`
  [76f85450] LibGit2 `@stdlib/LibGit2`
  [8f399da3] Libdl `@stdlib/Libdl`
  [37e2e46d] LinearAlgebra `@stdlib/LinearAlgebra`
  [56ddb016] Logging `@stdlib/Logging`
  [d6f4376e] Markdown `@stdlib/Markdown`
  [a63ad114] Mmap `@stdlib/Mmap`
  [ca575930] NetworkOptions `@stdlib/NetworkOptions`
  [44cfe95a] Pkg `@stdlib/Pkg`
  [de0858da] Printf `@stdlib/Printf`
  [3fa0cd96] REPL `@stdlib/REPL`
  [9a3f8284] Random `@stdlib/Random`
  [ea8e919c] SHA `@stdlib/SHA`
  [9e88b42a] Serialization `@stdlib/Serialization`
  [6462fe0b] Sockets `@stdlib/Sockets`
  [2f01184e] SparseArrays `@stdlib/SparseArrays`
  [10745b16] Statistics `@stdlib/Statistics`
  [fa267f1f] TOML `@stdlib/TOML`
  [a4e569a6] Tar `@stdlib/Tar`
  [8dfed614] Test `@stdlib/Test`
  [cf7118a7] UUIDs `@stdlib/UUIDs`
  [4ec0a83e] Unicode `@stdlib/Unicode`
  [e66e0078] CompilerSupportLibraries_jll `@stdlib/CompilerSupportLibraries_jll`
  [deac9b47] LibCURL_jll `@stdlib/LibCURL_jll`
  [29816b5a] LibSSH2_jll `@stdlib/LibSSH2_jll`
  [c8ffd9c3] MbedTLS_jll `@stdlib/MbedTLS_jll`
  [14a3606d] MozillaCACerts_jll `@stdlib/MozillaCACerts_jll`
  [4536629a] OpenBLAS_jll `@stdlib/OpenBLAS_jll`
  [83775a58] Zlib_jll `@stdlib/Zlib_jll`
  [8e850b90] libblastrampoline_jll `@stdlib/libblastrampoline_jll`
  [8e850ede] nghttp2_jll `@stdlib/nghttp2_jll`
  [3f19e933] p7zip_jll `@stdlib/p7zip_jll`
     Testing Running tests...
Test Summary: | Pass  Total
GMT           |    1      1
     Testing GMT tests passed 

So that happened in result of this command had errored

run(`gmt --version`)

Run it and you’ll see the true error message. Now the question is why. Did you have a previous GMT installation? On Linux some distros do not create a libgmt.so simlink and the GMT.jl detection code fails to detect GMT (solution is to manually create that simlink). But, again, when no GMT is detected conda should step in and install one for you.

You can load the input data as a lat-lon matrix and broadcast over its rows:

latlon_matrix = [lat lon]
trans.(eachrow(latlon_matrix))

Or load the input data as a lat-lon vector of vectors and broadcast over its elements:

latlon = [[x, y] for (x,y) in zip(lat,lon)]
trans.(latlon)

sorry for the bad advice!