From 42f88b8fb4da2756717e429ab52a5b8076706530 Mon Sep 17 00:00:00 2001 From: Joaquim Date: Mon, 23 Feb 2026 18:22:26 +0000 Subject: [PATCH] Make a gmt thin wrapper to reduce multiple recompilations. --- src/GMT.jl | 2 +- src/common_options.jl | 81 ++++++++++++++++++++++--------------------- src/gmt_main.jl | 23 ++++++++++++ src/psconvert.jl | 8 +++-- test/test_B-GMTs.jl | 2 +- test/test_GRDs.jl | 12 +++---- test/test_misc.jl | 2 +- test/test_views.jl | 2 +- 8 files changed, 79 insertions(+), 53 deletions(-) diff --git a/src/GMT.jl b/src/GMT.jl index 1f07e25b6..0cce2fa8c 100644 --- a/src/GMT.jl +++ b/src/GMT.jl @@ -398,7 +398,7 @@ using .Laszip grdimage(I, V=:q) grdview(rand(Float32, 32, 32), Vd=2) grdinfo(mat2grid(rand(Float32, 4, 4))) - Glix = gmt("grdmath", "-R0/10/0/10 -I2 X") + Glix = gmt("grdmath -R0/10/0/10 -I2 X") gmt_grdinfo_C(Glix) grdcontour(Glix) grd2cpt(Glix) diff --git a/src/common_options.jl b/src/common_options.jl index 2a94ecbed..13b234afd 100644 --- a/src/common_options.jl +++ b/src/common_options.jl @@ -2406,8 +2406,8 @@ function add_opt(d::Dict, cmd::String, opt::String, symbs::VMs, mapa; grow_mat=n end # --------------------------------------------------------------------------------------------------- -function genFun(this_key::Symbol, user_input::NamedTuple, mapa::NamedTuple)::String - (!haskey(mapa, this_key)) && return # Should it be a error? +function genFun(this_key::Symbol, user_input::NamedTuple, mapa)::String + (!haskey(mapa, this_key)) && return "" # Should it be a error? out::String = "" key = keys(user_input) # user_input = (rows=1, fill=:red) val_namedTup = mapa[this_key] # water=(rows="my", cols="mx", fill=add_opt_fill) @@ -2425,7 +2425,12 @@ function genFun(this_key::Symbol, user_input::NamedTuple, mapa::NamedTuple)::Str end # --------------------------------------------------------------------------------------------------- -function add_opt(nt::NamedTuple, mapa::NamedTuple, arg=nothing)::String +function add_opt(nt::NamedTuple, mapa::NamedTuple, arg=Float64[])::String # This wrapper is the only who gets recompiled when arg changes + _arg = (arg === nothing || isempty(arg)) ? Float64[] : arg + add_opt_1(nt, nt2dict(mapa), _arg) +end +function add_opt_1(nt::NamedTuple, d, arg)::String # +#function add_opt(nt::NamedTuple, mapa::NamedTuple, arg=nothing)::String # Generic parser of options passed in a NT and whose last element is another NT with the mapping # between expanded sub-options names and the original GMT flags. # ARG, is a special case to append to a matrix (can't realy be done in Julia) @@ -2433,68 +2438,69 @@ function add_opt(nt::NamedTuple, mapa::NamedTuple, arg=nothing)::String # add_opt((a=(1,0.5),b=2), (a="+a",b="-b")) # translates to: "+a1/0.5-b2" key = keys(nt); # The keys actually used in this call - d = nt2dict(mapa) # The flags mapping as a Dict (all possible flags of the specific option) + #d = nt2dict(mapa) # The flags mapping as a Dict (all possible flags of the specific option) cmd::String = ""; cmd_hold = Vector{String}(undef, 2); order = zeros(Int,2,1); ind_o = 0 count = zeros(Int, length(key)) for k = 1:numel(key)::Int # Loop over the keys of option's tuple !haskey(d, key[k]) && continue count[k] = k isa(nt[k], Dict) && (nt[k] = Base.invokelatest(dict2nt, nt[k])) - if (isa(d[key[k]], Tuple)) # Complexify it. Here, d[key[k]][2] must be a function name. + this_val = d[key[k]] + if (isa(this_val, Tuple)) # Complexify it. Here, d[key[k]][2] must be a function name. if (isa(nt[k], NamedTuple)) - if (d[key[k]][2] == add_opt_fill) - cmd *= string(d[key[k]][1])::String * d[key[k]][2]("", Dict(key[k] => nt[k]), [key[k]])::String + if (this_val[2] == add_opt_fill) + cmd *= string(this_val[1])::String * this_val[2]("", Dict(key[k] => nt[k]), [key[k]])::String else - local_opt = (d[key[k]][2] == helper_decorated) ? true : nothing # 'true' means single argout - cmd *= string(d[key[k]][1])::String * d[key[k]][2](nt2dict(nt[k]), local_opt)::String + local_opt = (this_val[2] == helper_decorated) ? true : nothing # 'true' means single argout + cmd *= string(this_val[1])::String * this_val[2](nt2dict(nt[k]), local_opt)::String end else # - if (length(d[key[k]]) == 2) # Run the function - cmd *= string(d[key[k]][1])::String * d[key[k]][2](Dict(key[k] => nt[k]), [key[k]])::String + if (length(this_val) == 2) # Run the function + cmd *= string(this_val[1])::String * this_val[2](Dict(key[k] => nt[k]), [key[k]])::String else # This branch is to deal with options -Td, -Tm, -L and -D of basemap & psscale ind_o += 1 (ind_o > 2) && (@warn("You passed more than 1 of the exclusive options in a anchor type option, keeping first but this may break."); ind_o = 1) - if (d[key[k]][2] === nothing) cmd_hold[ind_o] = d[key[k]][1] # Only flag char and order matters - elseif (length(d[key[k]][1]) == 2 && d[key[k]][1][1] == '-' && !isa(nt[k], Tuple)) # e.g. -L (&g, arg2str, 1) - cmd_hold[ind_o] = string(d[key[k]][1][2]) # where g + if (this_val[2] === nothing) cmd_hold[ind_o] = this_val[1] # Only flag char and order matters + elseif (length(this_val[1]) == 2 && this_val[1][1] == '-' && !isa(nt[k], Tuple)) # e.g. -L (&g, arg2str, 1) + cmd_hold[ind_o] = string(this_val[1][2]) # where g else # Run the fun - cmd_hold[ind_o] = (d[key[k]][1] == "") ? d[key[k]][2](nt[k]) : string(d[key[k]][1][end])::String * d[key[k]][2](nt[k])::String + cmd_hold[ind_o] = (this_val[1] == "") ? this_val[2](nt[k]) : string(this_val[1][end])::String * this_val[2](nt[k])::String end - order[ind_o] = d[key[k]][3]; # Store the order of this sub-option + order[ind_o] = this_val[3]; # Store the order of this sub-option end end - elseif (isa(d[key[k]], NamedTuple)) # + elseif (isa(this_val, NamedTuple)) # if (isa(nt[k], NamedTuple)) - cmd *= genFun(key[k], nt[k], mapa)::String + cmd *= genFun(key[k], nt[k], d)::String else # Create a NT where value = key. For example for: surf=(waterfall=:rows,) if (!isa(nt[1], Tuple)) # nt[1] may be a symbol, or string. E.g. surf=(water=:cols,) - cmd *= genFun(key[k], (; Symbol(nt[1]) => nt[1]), mapa)::String + cmd *= genFun(key[k], (; Symbol(nt[1]) => nt[1]), d)::String else if ((val = find_in_dict(d, [key[1]])[1]) !== nothing) # surf=(waterfall=(:cols,:red),) - cmd *= genFun(key[k], (; Symbol(nt[1][1]) => nt[1][1], keys(val)[end] => nt[1][end]), mapa)::String + cmd *= genFun(key[k], (; Symbol(nt[1][1]) => nt[1][1], keys(val)[end] => nt[1][end]), d)::String end end end - elseif (d[key[k]] == "1") # Means that only first char in value is retained. Used with units + elseif (this_val == "1") # Means that only first char in value is retained. Used with units t::String = arg2str(nt[k])::String if (t != "") cmd *= t[1] else cmd *= "1" # "1" is itself the flag end - elseif (d[key[k]] != "" && d[key[k]][1] == '|') # Potentialy append to the arg matrix (here in vector form) + elseif (this_val != "" && this_val[1] == '|') # Potentialy append to the arg matrix (here in vector form) if (isa(nt[k], AbstractArray) || isa(nt[k], Tuple)) if (isa(nt[k], AbstractArray)) append!(arg, reshape(nt[k], :)) else append!(arg, reshape(collect(nt[k]), :)) end end - cmd *= string(d[key[k]][2:end])::String # And now append the flag - elseif (d[key[k]] != "" && d[key[k]][1] == '_') # Means ignore the content, only keep the flag - cmd *= string(d[key[k]][2:end])::String # Just append the flag - elseif (d[key[k]] != "" && d[key[k]][end] == '1') # Means keep the flag and only first char of arg - cmd *= string(d[key[k]][1:end-1])::String * string(string(nt[k])[1])::String - elseif (d[key[k]] != "" && d[key[k]][end] == '#') # Means put flag at the end and make this arg first in cmd (coast -W) - cmd = arg2str(nt[k])::String * string(d[key[k]][1:end-1])::String * cmd + cmd *= string(this_val[2:end])::String # And now append the flag + elseif (this_val != "" && this_val[1] == '_') # Means ignore the content, only keep the flag + cmd *= string(this_val[2:end])::String # Just append the flag + elseif (this_val != "" && this_val[end] == '1') # Means keep the flag and only first char of arg + cmd *= string(this_val[1:end-1])::String * string(string(nt[k])[1])::String + elseif (this_val != "" && this_val[end] == '#') # Means put flag at the end and make this arg first in cmd (coast -W) + cmd = arg2str(nt[k])::String * string(this_val[1:end-1])::String * cmd else - cmd *= d[key[k]]::String * arg2str(nt[k])::String + cmd *= this_val::String * arg2str(nt[k])::String end end @@ -2509,14 +2515,6 @@ function add_opt(nt::NamedTuple, mapa::NamedTuple, arg=nothing)::String end if (occursin(':', cmd_hold[last])) # It must be a geog coordinate in dd:mm cmd = "g" * cmd - #= - elseif (length(cmd_hold[last]) > 2) # Temp patch to avoid parsing single char flags - rs = split(cmd_hold[last], '/') - if (length(rs) == 2) - x = tryparse(Float64, rs[1]); y = tryparse(Float64, rs[2]); - if (x !== nothing && y !== nothing && 0 <= x <= 1.0 && 0 <= y <= 1.0 && !occursin(r"[gjJxn]", string(cmd[1]))) cmd = "n" * cmd end # Otherwise, either a paper coord or error - end - =# end end @@ -2824,7 +2822,7 @@ function makecpt_raw(cmd::String, arg1=nothing)::GMTcpt # Raw version that already knows what the command is and has no input data. (IamModern[] && !contains(cmd, " -H")) && (cmd *= " -H") !startswith(cmd, "makecpt ") && (cmd = "makecpt " * cmd) - _r = gmt_GMTcpt(cmd, arg1) + _r = arg1 === nothing ? gmt_GMTcpt(cmd) : gmt_GMTcpt(cmd, arg1) r::GMTcpt = (_r !== nothing) ? _r : GMTcpt() # _r === nothing when we save CPT on disk. (contains(cmd, " -N") && !isempty(r)) && (r.bfn = ones(3,3)) # Cannot remove the bfn like in plain GMT so make it all whites CURRENT_CPT[] = r @@ -4110,8 +4108,11 @@ function common_grd(d::Dict, cmd::String, args...) (haskey(d, :Vd) && d[:Vd] > 2) && show_args_types(args...) show_non_consumed(d, cmd) (dbg_print_cmd(d, cmd) !== nothing) && return cmd # Vd=2 cause this return + # First case below is of a ARGS tuple(tuple) with all numeric inputs. - R = isa(args, Tuple{Tuple}) ? gmt(cmd, args[1]...) : gmt(cmd, args...) + n_args = 0 + for k = 1:numel(args) if (args[k] !== nothing) n_args += 1 end end # Drop the nothings + R = isa(args, Tuple{Tuple}) ? gmt(cmd, args[1]...) : gmt(cmd, args[1:n_args]...) (isGMTdataset(R) && contains(cmd, " -fg") && getproj(R) == "") && (isa(R, GMTdataset) ? R.proj4 = prj4WGS84 : R[1].proj4 = prj4WGS84) return R end diff --git a/src/gmt_main.jl b/src/gmt_main.jl index dc0824ad3..08da5de7c 100644 --- a/src/gmt_main.jl +++ b/src/gmt_main.jl @@ -5,6 +5,27 @@ except when using the ``monolithic`` mode. Usage: gmt("module_name `options`", args...) """ function gmt(cmd::String, args...) + n_argin::Int = length(args) + if (n_argin > 0) + if (isa(args[1], String)) + tok::String, r::String = strtok(cmd) + if (r == "") # User gave 'module' separately from 'options' + error("Please use gmt(\"module_name options\", args...) instead of gmt(\"module_name\", \"options\", args...)") + #cmd::String *= " " * args[1]::String # Cat with the progname and so pretend input followed the classic construct + #args = args[2:end] + #n_argin -= 1 + end + end + # We may have trailing [] args in modules + baka = n_argin + while (n_argin > 0 && (args[n_argin] === nothing)) n_argin -= 1 end + #baka != n_argin && println("-----------------------------------------> $cmd has $(baka-n_argin) empty args.") + baka != n_argin && (args = args[1:n_argin]) + end + _gmt(cmd, args...) +end + +function _gmt(cmd::String, args...) (cmd == "") && return nothing # Building docs with Quarto leads here when examples use ModernMode (cmd == "destroy") && return gmt_restart() @@ -13,6 +34,7 @@ function gmt(cmd::String, args...) # ----------- Minimal error checking ------------------------ n_argin::Int = length(args) + #= if (n_argin > 0) if (isa(args[1], String)) tok::String, r::String = strtok(cmd) @@ -25,6 +47,7 @@ function gmt(cmd::String, args...) # We may have trailing [] args in modules while (n_argin > 0 && (args[n_argin] === nothing)) n_argin -= 1 end end + =# # ----------------------------------------------------------- # 1. Get arguments, if any, and extract the GMT module name diff --git a/src/psconvert.jl b/src/psconvert.jl index f2fb16c92..566db1ac1 100644 --- a/src/psconvert.jl +++ b/src/psconvert.jl @@ -1,7 +1,7 @@ """ psconvert(cmd0::String="", arg1=nothing; kwargs...) -Place images or EPS files on maps. +Convert [E]PS file(s) to other formats using Ghostscript. Parameters ---------- @@ -77,9 +77,10 @@ end function psconvert_helper(cmd0::String, arg1, d::Dict{Symbol,Any}) - if (!isempty(cmd0)) && (arg1 === nothing) arg1 = cmd0 end + if (!isempty(cmd0)) && (arg1 === nothing) arg1 = cmd0 end # This is stupid, must be fixed. - cmd::String = add_opt(d, "", "A", [:A :adjust :crop]) + cmd::String = add_opt(d, "", "A", [:A :adjust :crop :bbox], (round="_+r", nostamp="_+u", bbox="_+i")) + needs_Te = contains(cmd, "+i") (cmd == " -A") && (cmd = cmd * "1p") # If just -A default to -A1p cmd = parse_these_opts(cmd, d, [[:D :out_dir :output_dir], [:E :dpi], [:F :out_name :output_name], [:G :ghost_path], [:I :resize], [:L :list_file], [:M :embed], [:N :bgcolor], [:P :portrait], [:Q :anti_aliasing], [:S :gs_command], [:Z :del_input_ps]]) @@ -96,6 +97,7 @@ function psconvert_helper(cmd0::String, arg1, d::Dict{Symbol,Any}) else cmd = add_opt(d, cmd, "T", [:T :format]) end + (needs_Te && !contains(cmd, " -Te")) && (cmd *= " -Te") # We need this when -A+i (bbox) to precent gmt() to try return an image if ((val = find_in_dict(d, [:C :gs_option])[1]) !== nothing) if (isa(val, String) || isa(val, Symbol)) diff --git a/test/test_B-GMTs.jl b/test/test_B-GMTs.jl index 0c5991736..2b2fb3e1c 100644 --- a/test/test_B-GMTs.jl +++ b/test/test_B-GMTs.jl @@ -168,7 +168,7 @@ gmtsimplify([0.0 0; 1.1 1.1; 2 2.2; 3.3 3], T="3k") println(" GMTREADWRITE") - G=gmt("grdmath", "-R0/10/0/10 -I1 5"); + G=gmt("grdmath -R0/10/0/10 -I1 5"); gmtwrite("lixo.grd", G, scale=10, offset=-10) GG = gmtread("lixo.grd", grd=true, varname=:z); GG = gmtread("lixo.grd", varname=:z); diff --git a/test/test_GRDs.jl b/test/test_GRDs.jl index ecf94b8a9..eeda6c477 100644 --- a/test/test_GRDs.jl +++ b/test/test_GRDs.jl @@ -1,5 +1,5 @@ println(" GRDINFO") - G=gmt("grdmath", "-R0/10/0/10 -I1 5"); + G=gmt("grdmath -R0/10/0/10 -I1 5"); r=gmt("grdinfo -C", G); @assert(r.data[1:1,1:10] == [0.0 10.0 0.0 10.0 5.0 5.0 1.0 1.0 11.0 11.0]) r2=grdinfo(G, C=true, V=:q); @@ -12,7 +12,7 @@ grdinfo(G); println(" GRD2CPT") - G=gmt("grdmath", "-R0/10/0/10 -I2 X"); + G=gmt("grdmath -R0/10/0/10 -I2 X"); C=grd2cpt(G); grd2cpt(G, cmap="lixo.cpt", V="q") @@ -24,11 +24,11 @@ @assert(sum(D1.data) == sum(D2.data)) println(" GRD2KML") - G=gmt("grdmath", "-R0/10/0/10 -I1 X -fg"); + G=gmt("grdmath -R0/10/0/10 -I1 X -fg"); grd2kml(G, I="+", N="NUL", V="q", Vd=dbg2) println(" GRDBLEND") - G3=gmt("grdmath", "-R5/15/0/10 -I1 X Y -Vq"); + G3=gmt("grdmath -R5/15/0/10 -I1 X Y -Vq"); grdblend(G,G3); println(" GRDCLIP") @@ -56,7 +56,7 @@ grdcontour!(G, axis="a", color=cpt, pen="+c", X=1, Y=1, N=cpt, Vd=dbg2) println(" GRDCUT") - G=gmt("grdmath", "-R0/10/0/10 -I1 X Y MUL"); + G=gmt("grdmath -R0/10/0/10 -I1 X Y MUL"); grdcut(G, limits=[3 9 2 8]); grdcut("lixo.grd", limits=[3 9 2 8], V=:q); # lixo.grd was written above in the gmtwrite test grdcut(data="lixo.grd", limits=[3 9 2 8], V=:q); @@ -163,7 +163,7 @@ grdsample(G, inc=0.5); # Use G of previous test println(" GRDTREND") - G = gmt("grdmath", "-R0/10/0/10 -I1 X Y MUL"); + G = gmt("grdmath -R0/10/0/10 -I1 X Y MUL"); grdtrend(G, model=3); mat2grid(ones(Float32, size(G.z,1), size(G.z,2))); W = mat2grid(rand(16,16), x=11:26, y=1:16); diff --git a/test/test_misc.jl b/test/test_misc.jl index e272c779c..64723d42d 100644 --- a/test/test_misc.jl +++ b/test/test_misc.jl @@ -109,7 +109,7 @@ @test D + D == [2 2; 2 2; 2 2] @test D - D == [0 0; 0 0; 0 0] - D2=grd2xyz(gmt("grdmath", "-R0/10/0/10 -I2 X")) + D2=grd2xyz(gmt("grdmath -R0/10/0/10 -I2 X")) cat(D, D2); cat([D], D2); cat([D], [D2]); diff --git a/test/test_views.jl b/test/test_views.jl index 9ce7e3d88..bda06a24d 100644 --- a/test/test_views.jl +++ b/test/test_views.jl @@ -39,7 +39,7 @@ grdview!(G, G=G, J="X6i", JZ=5, I=45, Q="s", C="topo", R="-15/15/-15/15/-1/1", r = grdview!(G, plane=(-6,:lightgray), surftype=(surf=true,mesh=:red), view="120/30", Vd=dbg2); @test startswith(r, "grdview -R-15.0/15.0/-15.0/15.0/-1.0/1.0 -J -n+a -p120/30 -N-6+glightgray -Qsmred") r = grdview(G, surf=(waterfall=(:rows,:red),surf=true, mesh=true, img=50), Vd=dbg2); -@test startswith(r, "grdview -R0/360/-90/90 -JX" * split(GMT.DEF_FIG_SIZE, '/')[1] * "/0" * " -Baf -Bza -n+a -Qmyredsmi50") +@test startswith(r, "grdview -R0/360/-90/90 -JX" * split(GMT.DEF_FIG_SIZE, '/')[1] * "/0" * " -Baf -Bza -n+a -Qsmi50") @test startswith(grdview(G, surf=(waterfall=:rows,), Vd=dbg2), "grdview -R0/360/-90/90 -JX" * split(GMT.DEF_FIG_SIZE, '/')[1] * "/0" * " -Baf -Bza -n+a -Qmy") @test startswith(grdview(G, surf=(waterfall=(rows=true, fill=:red),), Vd=dbg2), "grdview -R0/360/-90/90 -JX" * split(GMT.DEF_FIG_SIZE, '/')[1] * "/0" * " -Baf -Bza -n+a -Qmyred") @test_throws ErrorException("Wrong way of setting the drape (G) option.") grdview(rand(16,16), G=(1,2))