diff --git a/src/choropleth.jl b/src/choropleth.jl index afb2177ce..ebeeaf2de 100644 --- a/src/choropleth.jl +++ b/src/choropleth.jl @@ -132,7 +132,7 @@ function extract_ids_vals(D, attrib::String) vals = deleteat!(vals, ind) ids = deleteat!(ids, ind) end - ids, parse.(Float64, vals) + ids, (tryparse(Float64, vals[1]) === nothing) ? collect(1.0:length(vals)) : parse.(Float64, vals) end # ------------------------------------------------------------------------------------------------------ diff --git a/src/gdal_utils.jl b/src/gdal_utils.jl index b4f09793e..d03d2ff1e 100644 --- a/src/gdal_utils.jl +++ b/src/gdal_utils.jl @@ -515,6 +515,20 @@ function getregion(input; pad=0, xSize=0, ySize=0, gridreg::Bool=false, sds::Int return x_min, x_max, y_min, y_max, x_inc, y_inc end +# --------------------------------------------------------------------------------------------------- +""" + opr_R = getR(X, full::Bool=false)::String + +Return a string with the -R option made up of the region of 'X'. + +`X` is a GMTimage, GMTgrid or GMTdataset(s). The `full` flag adds the " -R" prefix. +""" +function getR(X, full::Bool=false)::String + x_min, x_max, y_min, y_max = getregion(X) + pre = full ? " -R" : "" + @sprintf("%s%f/%f/%f/%f", pre, x_min, x_max, y_min, y_max) +end + # --------------------------------------------------------------------------------------------------- function coords_resque(dataset) # We come here when GDAL stuburnly finds a netcdf file indign and ignores its coordinates diff --git a/src/gmtlogo.jl b/src/gmtlogo.jl index a1a5c0564..3054e3df9 100644 --- a/src/gmtlogo.jl +++ b/src/gmtlogo.jl @@ -56,7 +56,7 @@ function logo(cmd0::String=""; first=true, kwargs...) if (!occursin("-R", cmd)) cmd = @sprintf("-R0/%f/0/%f ", 2r, 2r) * cmd end if (!occursin("-J", cmd)) cmd = " -Jx1 " * cmd end if (do_GMTjulia) # Too soon to use the show, fmt, ... - do_show, fmt, savefig = get_show_fmt_savefig(d, true) # Default is to show + do_show, fmt, savefig = get_show_fmt_savefig(d, false) # Default is to !show end cmd = "psxy " * c * cmd ((_r = check_dbg_print_cmd(d, cmd)) !== nothing && startswith(_r, "psxy")) && return _r diff --git a/src/grdimage.jl b/src/grdimage.jl index 7f83f70e3..2a1e80258 100644 --- a/src/grdimage.jl +++ b/src/grdimage.jl @@ -50,7 +50,7 @@ function grdimage(cmd0::String="", arg1=nothing, arg2=nothing, arg3=nothing; fir (!haskey(d, :R) && any(startswith.(cmd0, ["@earth_", "@mars_", "@pluto_", "@moon_", "@venus_"]))) && (CTRL.limits[1:4] = CTRL.limits[7:10] = [-180, 180, -90, 90]) - if (arg1 === nothing && haskey(d, :R) && guess_T_from_ext(cmd0) == " -Ti") + if (arg1 === nothing && (get(d, :R, nothing) !== nothing) && guess_T_from_ext(cmd0) == " -Ti") _opt_R = d[:R] t = (isa(_opt_R, Tuple) || isa(_opt_R, VMr)) ? ["$(_opt_R[1])", "$(_opt_R[2])", "$(_opt_R[3])", "$(_opt_R[4])"] : split(_opt_R, '/') @@ -77,22 +77,6 @@ end function _grdimage(cmd0::String, arg1, arg2, arg3, O::Bool, K::Bool, d::Dict) arg4 = nothing # For the r,g,b + intensity case -#= - common_insert_R!(d, O, cmd0, arg1) # Set -R in 'd' out of grid/images (with coords) if limits was not used - - # Remote files with no -R are all global. Set CTRL.limits so we can guess the projection. - (!haskey(d, :R) && any(startswith.(cmd0, ["@earth_", "@mars_", "@pluto_", "@moon_", "@venus_"]))) && - (CTRL.limits[1:4] = CTRL.limits[7:10] = [-180, 180, -90, 90]) - - if (arg1 === nothing && haskey(d, :R) && guess_T_from_ext(cmd0) == " -Ti") - _opt_R = d[:R] - t = (isa(_opt_R, Tuple) || isa(_opt_R, VMr)) ? - ["$(_opt_R[1])", "$(_opt_R[2])", "$(_opt_R[3])", "$(_opt_R[4])"] : split(_opt_R, '/') - opts = ["-projwin", t[1], t[4], t[2], t[3]] # -projwin - arg1 = cut_with_gdal(cmd0, opts) - cmd0 = "" - end -=# # Prevent that J=guess is applied to a non-geog grid/image (arg1 !== nothing && (symb = is_in_dict(d, [:proj :projection])) !== nothing && (d[symb] == "guess" || d[symb] == :guess) && !isgeog(arg1)) && @@ -102,8 +86,11 @@ function _grdimage(cmd0::String, arg1, arg2, arg3, O::Bool, K::Bool, d::Dict) (is_in_dict(d, [:A :img_out :image_out]) !== nothing) && (d[:B] = "none") # When -A is used, -B is forbiden cmd::String, opt_B::String, opt_J::String, opt_R::String = parse_BJR(d, "", "", O, " -JX" * split(DEF_FIG_SIZE, '/')[1] * "/0") (startswith(opt_J, " -JX") && !contains(opt_J, "/")) && (cmd = replace(cmd, opt_J => opt_J * "/0")) # When sub-regions - (!has_opt_B && isa(arg1, GMTimage) && (isimgsize(arg1) || CTRL.limits[1:4] == zeros(4)) && opt_B == DEF_FIG_AXES_BAK) && - (cmd = replace(cmd, opt_B => "")) # Dont plot axes for plain images if that was not required + sim = (!has_opt_B && isa(arg1, GMTimage) && (isimgsize(arg1) || CTRL.limits[1:4] == zeros(4)) && opt_B == DEF_FIG_AXES_BAK) + if (!sim && cmd0 !== "" && !has_opt_B) + sim = (lowercase(splitext(cmd0)[2]) in [".jpg", ".jpeg", ".png", ".bmp", ".webp"]) + end + sim && (cmd = replace(cmd, opt_B => "")) cmd, = parse_common_opts(d, cmd, [:UVXY :params :margin :c :f :n :p :t]; first=!O) cmd = parse_these_opts(cmd, d, [[:A :img_out :image_out], [:D :img_in :image_in], [:E :dpi], [:G :bit_color], @@ -122,21 +109,6 @@ function _grdimage(cmd0::String, arg1, arg2, arg3, O::Bool, K::Bool, d::Dict) end end -#= - if (isa(arg1, Matrix{<:Real}) || isa(arg1, Array{<:Real,3})) - if (isa(arg1, Matrix{UInt8}) || isa(arg1, Matrix{UInt16}) || isa(arg1, Array{UInt8,3})) - arg1 = mat2img(arg1; d...) - else - arg1 = mat2grid(arg1) - (isa(arg2, Matrix{<:Real})) && (arg2 = mat2grid(arg2)) - (isa(arg3, Matrix{<:Real})) && (arg3 = mat2grid(arg3)) - end - elseif (isa(arg1, GMTimage) && size(arg1, 3) <= 3 && eltype(arg1.image) <: UInt16) - arg1 = mat2img(arg1; d...) - (haskey(d, :stretch) || haskey(d, :histo_bounds)) && delete!(d, [:histo_bounds, :stretch]) - end -=# - set_defcpt!(d, cmd0, arg1) # When dealing with a remote grid assign it a default CPT (isa(arg1, GMTgrid) && arg1.cpt != "") && (d[:this_cpt] = arg1.cpt) (haskey(d, :this_cpt) && isfile(d[:this_cpt])) && (CURRENT_CPT[] = gmtread(d[:this_cpt])) diff --git a/src/montage.jl b/src/montage.jl new file mode 100644 index 000000000..cf208fe6f --- /dev/null +++ b/src/montage.jl @@ -0,0 +1,94 @@ +export montage + +# This file was initially created by Claude and still needs lots of cleanings. + + +# ------------------------------------------------------------------------------------------ +""" + montage(images; kwargs...) + +Display multiple images arranged in a grid using GMT's subplot. + +### Arguments +- `images`: Vector of GMTimage/GMTgrid objects, filenames, or a 3D/4D array. + +### Keywords +- `grid`: Tuple (nrows, ncols) specifying grid dimensions. Default: approximately square. +- `panels_size`: Size of each panel in cm, e.g. `5` or `(5, 4)`. Default: auto. +- `margin`: Margin between panels. Default: "0.1c" +- `title`: Overall figure title. +- `titles`: Vector of titles for each panel. +- `frame`: Frame setting for panels. Default: :none +- `indices`: Vector of indices selecting which images to display. +- `show`: Display the result. Default: true. +- `savefig`: Filename to save the montage. + +### Example +```julia +# From files +montage(["img1.png", "img2.png", "img3.png", "img4.png"], grid=(2,2)) + +# From GMTimage array with titles +imgs = [gmtread("img\$i.png") for i in 1:6] +montage(imgs, grid=(2,3), titles=["A","B","C","D","E","F"], panels_size=5) + +# From 3D array +montage(rand(UInt8, 64, 64, 9), grid=(3,3), margin="0.2c") +``` +""" +function montage(images; grid=nothing, panels_size=nothing, margin="0.0c", + title=nothing, titles=nothing, frame=:none, indices=nothing, + show::Bool=true, noR::Bool=false, kw...) + + (indices !== nothing) && (images = images[indices]) # Apply indices selection + ((n = length(images)) == 0) && error("No images to display") + nrows, ncols = _montage_grid_size(n, grid) # Calculate grid dimensions + + # Build subplot options + subplot_kw = Dict{Symbol, Any}(:grid => (nrows, ncols), :margin => margin, :frame => frame) + + if panels_size !== nothing + subplot_kw[:panels_size] = panels_size + else # Auto size based on grid + ps = max(3, min(8, 18 / max(nrows, ncols))) + subplot_kw[:panels_size] = ps + end + + title !== nothing && (subplot_kw[:title] = title) + + # Merge additional kwargs + for (k, v) in kw + subplot_kw[k] = v + end + subplot_kw[:Vd] = 1 + + subplot(; subplot_kw...) # Create subplot + + # Plot each image + k = 0 + for row in 1:nrows + for col in 1:ncols + k += 1 + panel_title = (titles !== nothing && k <= length(titles)) ? titles[k] : nothing + opt_R = isa(images[k], GItype) ? (noR ? "" : getR(images[k])) : "" + viz(images[k], panel=(row, col), title=panel_title, R=(opt_R !== "" ? opt_R : nothing), Vd=1, show=false) + #grdimage(images[k], panel=(row, col), title=panel_title, R=(opt_R !== "" ? opt_R : nothing), Vd=1, show=false) + end + end + subplot(show ? :show : :end) # End subplot + + return nothing +end + +# Calculate grid dimensions +function _montage_grid_size(n, size) + if size === nothing || all(x -> x === nothing || x == 0 || (isa(x, AbstractFloat) && isnan(x)), size) + ncols = ceil(Int, sqrt(n)) + nrows = ceil(Int, n / ncols) + else + nrows, ncols = size + (nrows == 0 || (isa(nrows, AbstractFloat) && isnan(nrows))) && (nrows = ceil(Int, n / ncols)) + (ncols == 0 || (isa(ncols, AbstractFloat) && isnan(ncols))) && (ncols = ceil(Int, n / nrows)) + end + return Int(nrows), Int(ncols) +end diff --git a/src/utils.jl b/src/utils.jl index 3f760ab30..954191928 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1769,3 +1769,4 @@ end include("getdcw.jl") include("choropleth.jl") +include("montage.jl")