Skip to content

Commit f7627dc

Browse files
authored
Let the getbyattrib() also filter by the largest polygon of each group. (#1789)
* Let gmt_centroid_area select if return centroid,area or just one of them * Let the getbyattrib() also filter by the largest polygon of each group. * Add a scatter method to let do choropleths by painted symbols
1 parent b3175b6 commit f7627dc

4 files changed

Lines changed: 83 additions & 21 deletions

File tree

src/libgmt.jl

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -427,31 +427,39 @@ function gmt_free_mem(API::Ptr{Cvoid}, mem)
427427
ccall((:gmt_free_func, libgmt), Cvoid, (Cstring, Ptr{Cvoid}, Bool, Cstring), GMT_, mem, true, "Julia")
428428
end
429429

430-
function gmt_centroid_area(API::Ptr{Cvoid}, D, geo::Int=0)::Matrix{Float64}
430+
function gmt_centroid_area(API::Ptr{Cvoid}, D, geo::Int=0; ca::Int=3)::Matrix{Float64}
431+
# geo = 0 for Cartesian, !=0 for geographic
432+
# ca = 3 for (x,y,area), 2 for (x,y), 1 for area only
433+
mat = Matrix{Float64}(undef, length(D), ca)
431434
if (isa(D, Vector)) # 'D' is a vector of GMTdatasets but that type is not yet known here.
432-
mat = Matrix{Float64}(undef, length(D), 3)
433435
for k in eachindex(D)
434436
ref1, ref2, nr = pointercols(D[k])
435437
t = gmt_centroid_area(API, ref1, ref2, geo, nr)
436-
mat[k, 1], mat[k, 2], mat[k, 3] = t[1], t[2], t[3]
438+
if (ca == 3) mat[k, 1], mat[k, 2], mat[k, 3] = t[1], t[2], t[3] # To avoid allocs
439+
elseif (ca == 2) mat[k, 1], mat[k, 2] = t[1], t[2]
440+
else mat[k] = t[3]
441+
end
437442
end
438-
mat
439443
else
440444
ref1, ref2, nr = pointercols(D)
441-
gmt_centroid_area(API, ref1, ref2, geo, nr)
445+
t = gmt_centroid_area(API, ref1, ref2, geo, nr)[inds]
446+
mat = bla(mat, t, ca, 1)
447+
if (ca == 3) mat[1], mat[2], mat[3] = t[1], t[2], t[3] elseif (ca == 2) mat[1], mat[2] = t[1], t[2] else mat[1] = t[3] end
442448
end
449+
return mat
443450
end
444451

445-
function gmt_centroid_area(API::Ptr{Cvoid}, x, y, geo::Int=0, n::Int=0)::Matrix{Float64}
452+
function gmt_centroid_area(API::Ptr{Cvoid}, x, y, geo::Int=0, n::Int=0; ca::Int=3)::Matrix{Float64}
446453
# geo = 0 for Cartesian, !=0 for geographic
454+
# ca = 3 for (x,y,area), 2 for (x,y), 1 for area only
447455
@assert isa(x, Vector{Float64}) || isa(x, Ptr{Float64}) || isa(x, Ref{Float64}) "Bad type for x"
448456
!isa(x, Vector{Float64}) && (n == 0) && error("Must provide 'n' when x is not a Vector")
449457
(n == 0) && (n = length(x))
450458
pos = [0.0 0.0 0.0]
451459
area = ccall((:gmt_centroid_area, libgmt), Cdouble, (Cstring, Ptr{Cdouble}, Ptr{Cdouble}, UInt64, Int32, Ptr{Cdouble}),
452460
GMT_Get_Ctrl(API), x, y, n, geo, pos)
453461
pos[3] = abs(area)
454-
return pos
462+
return (ca == 3) ? pos : (ca == 2) ? pos[1:1,1:2] : pos[1:1,3:3]
455463
end
456464

457465
function pointercols(D::AbstractArray{Float64,2}, cols=(1,2))

src/plot.jl

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,15 +362,13 @@ Parameters
362362
+ **x**, **cross**
363363
+ **y**, **y_dash**
364364
365-
and select their sizes with the **markersize** or **size** keyword [default is 8p].
365+
and select their sizes with the `markersize` or `size` keyword [default is 8p].
366366
The marker size can be a scalar or a vector with same size numeber of rows of data. Units are
367367
points unless specified otherwise with (for example for cm) *par=(PROJ_LENGTH_UNIT=:c,)*
368368
- **W** | **pen** | **markeredgecolor** | **mec** :: [Type => Str]
369369
370370
Set pen attributes for lines or the outline of symbols
371371
- $(opt_savefig)
372-
373-
[`GMT man page`]($(GMTdoc)plot.html)
374372
"""
375373
function scatter(f::Function, range_x=nothing; first=true, kw...)
376374
rang = gen_coords4funs(range_x, "x"; kw...)
@@ -395,6 +393,19 @@ scatter!(arg1, arg2; kw...) = common_plot_xyz("", cat_2_arg2(arg1, arg2, true),
395393
bubblechart = scatter # Alias that supposedly only plots circles
396394
bubblechart! = scatter!
397395

396+
function scatter(D::Vector{<:GMTdataset{Float64,2}}; first=true, kw...)
397+
d = KW(kw)
398+
labels = String[]
399+
if ((s_val = hlp_desnany_str(d, [:labels], false)) !== "")
400+
ts = fish_attrib_in_str(s_val)
401+
labels = [D[k].attrib[ts] for k = 1:length(D)]
402+
end
403+
Dc = mat2ds(gmt_centroid_area(G_API[1], D, Int(isgeog(D)), ca=2), geom=wkbPoint, text=labels)
404+
(is_in_dict(d, [:marker, :Marker, :shape]) === nothing) && (d[:marker] = "circ")
405+
(is_in_dict(d, [:ms :markersize :MarkerSize :size]) === nothing) && (d[:ms] = "12p")
406+
_common_plot_xyz("", Dc, "bubble", !first, true, false, d)
407+
end
408+
398409
# ------------------------------------------------------------------------------------------------------
399410
scatter3(cmd0::String="", arg1=nothing; kw...) = common_plot_xyz(cmd0, mat2ds(arg1), "scatter3", true, true; kw...)
400411
scatter3!(cmd0::String="", arg1=nothing; kw...) = common_plot_xyz(cmd0, mat2ds(arg1), "scatter3", false, true; kw...)

src/spatial_funs.jl

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,14 @@ NOTE: Instead of ``getbyattrib`` one case use instead ``filter`` (...,`index=fal
178178
means select all elements from ``Antioquia`` and ``Caldas`` that have the attribute `feature_id` = 0.
179179
180180
A second set of attributes can be used to select elements by region, number of polygon vertices and area.
181-
For that, name the keyword with a leading underscore, e.g. `_region`, `_nps`, `_area`. Their values are
181+
For that, name the keyword with a leading underscore, e.g. `_region`, `_nps`, `_area`, `_unique`. Their values are
182182
passed respectively a 4 elements tuple and numbers. E.g. `_region=(-8.0, -7.0, 37.0, 38.0)`, `_nps=100`, `_area=10`.
183-
Areas are in square km when input is in geographic coordinates, otherwise squre data unites.
183+
Areas are in square km when input is in geographic coordinates, otherwise square data units. The `_unique` case is
184+
a bit special and is meant to be used when more than one polygon share the same attribute value (e.g. countries with islands).
185+
In that case, set the value of `_unique` to the name of the attribute that is shared by the polygons (e.g. `_unique="NAME"`).
186+
By default (e.g. `_unique=true`), the attribute name is `Feature_ID` which is the one used by GMT when creating unique
187+
IDs for polygons read from OGR formats (.shp, .geojson, etc). The uniqueness is determined by selecting the polygon
188+
with the largest area.
184189
185190
- `invert, or reverse, or not`: If `true` return all segments that do NOT match the query condition(s).
186191
@@ -208,7 +213,7 @@ function getbyattrib(D::Vector{<:GMTdataset}, ind_::Bool; kw...)::Vector{Int}
208213
invert = (find_in_kwargs(kw, [:invert :not :revert :reverse])[1] !== nothing)
209214
if ((_att = find_in_kwargs(kw, [:att :attrib])[1]) !== nothing) # For backward compat.
210215
if !isa(_att, NamedTuple)
211-
((val = find_in_kwargs(kw, [:val :value])[1]) === nothing) && error("Must provide the `attribute` VALUE.")
216+
((val = find_in_kwargs(kw, [:val :value])[1]) === nothing) && error("Must provide the `attribute` VALUE.")
212217
end
213218
atts, vals = isa(_att, NamedTuple) ? (string.(keys(_att)), string.(values(_att))) : ((string(_att),), (string(val),))
214219
_keys = repeat(" ", length(kw))
@@ -224,6 +229,7 @@ function getbyattrib(D::Vector{<:GMTdataset}, ind_::Bool; kw...)::Vector{Int}
224229
end
225230
atts, vals = Vector{String}(undef, count), Vector{String}(undef, count)
226231

232+
attrib_name = "Feature_ID" # The default name for the _unique attribute name
227233
for k = 1:numel(kw)
228234
vv = values(v[k])
229235
if (isa(vv, Tuple) && (_keys[k][1] != '_'))
@@ -236,6 +242,11 @@ function getbyattrib(D::Vector{<:GMTdataset}, ind_::Bool; kw...)::Vector{Int}
236242
kk += length(vv)
237243
else
238244
atts[kk], vals[kk] = _keys[k], string(vv)
245+
if (atts[kk] == "_unique")
246+
# If we can't parse the arg as a number then it must be an attribute name. If not, an error will be raised later.
247+
(vals[kk] != "true" && (tryparse(Int, vals[kk]) === nothing) && (tryparse(Float64, vals[kk]) === nothing)) && (attrib_name = vals[kk])
248+
vals[kk] = "0" # If _unique the value doesn't matter but must be parseable to float
249+
end
239250
kk += 1
240251
end
241252
(k == 1) && (dounion = kk) # Index that separates the unions from the interceptions
@@ -256,14 +267,30 @@ function getbyattrib(D::Vector{<:GMTdataset}, ind_::Bool; kw...)::Vector{Int}
256267
for k = 1:numel(D) _tf[k] = size(D[k].data,1) >= nps_ end
257268
_tf
258269
end
259-
function clip_area(D, areas_, area_, _tf) # Clip by number of points
270+
function clip_area(D, areas_, area_, _tf) # Clip by number of points
260271
for k = 1:numel(D) _tf[k] = areas_[k] >= area_ end
261272
_tf
262273
end
274+
function clip_unique(D, areas_, _tf, name) # Clip by uniqueness by using the areas to select the largest by group.
275+
att_tbl, att_names = make_attrtbl(D, true)
276+
((ind_name = findfirst(att_names .== name)) === nothing) && error("Attribute name $name not found in dataset.")
277+
_, ind = gunique(att_tbl[:,ind_name])
278+
for k = 1:numel(ind) _tf[ind[k]] = true end # Set the unique values to true.
279+
k = 1
280+
while (k <= numel(ind)-1)
281+
((ind[k+=1] - ind[k-1]) == 1) && continue
282+
n_start, n_end = ind[k - 1], ind[k] - 1
283+
# Here we have a group (from n_start to n_end) of the same type of attribute
284+
this_ind = argmax(view(areas_,n_start:n_end,1)) + n_start - 1 # Get the index of the largest area
285+
_tf[n_start], _tf[this_ind] = false, true # Turn off the first and turn on the largest of this group
286+
end
287+
_tf
288+
end
263289

264290
(ind = findfirst(atts .== "_region")) !== nothing && (lims = parse.(Float64, split(vals[ind][2:end-1], ", ")))
265291
(ind = findfirst(atts .== "_nps")) !== nothing && (nps = parse.(Float64, vals[ind]))
266-
(ind = findfirst(atts .== "_area")) !== nothing && (area = parse.(Float64, vals[ind]); areas = gmt_centroid_area(G_API[1], D, Int(isgeog(D)))[:,3])
292+
((ind = findfirst(atts .== "_area")) !== nothing || (ind = findfirst(atts .== "_unique")) !== nothing) &&
293+
(area = parse.(Float64, vals[ind]); areas = gmt_centroid_area(G_API[1], D, Int(isgeog(D)), ca=1); isgeog(D) && (areas .*= 1e-6)) # areas in km^2 if geographic coords
267294

268295
indices::Vector{Int} = Int[]
269296
ky = keys(D[1].attrib)
@@ -274,6 +301,7 @@ function getbyattrib(D::Vector{<:GMTdataset}, ind_::Bool; kw...)::Vector{Int}
274301
if (special)
275302
if (atts[n] == "_region") tf = clip_region(D, lims, tf)
276303
elseif (atts[n] == "_area") tf = clip_area(D, areas, area, tf)
304+
elseif (atts[n] == "_unique") tf = clip_unique(D, areas, tf, attrib_name)
277305
else tf = clip_np(D, nps, tf)
278306
end
279307
else
@@ -301,7 +329,18 @@ function getbyattrib(D::Vector{<:GMTdataset}; indices=false, kw...)::Union{Nothi
301329
end
302330

303331
# ---------------------------------------------------------------------------------------------------
332+
"""
333+
filter(D::Vector{<:GMTdataset}; kw...)
334+
335+
See `? getbyattrib`
336+
"""
304337
Base.:filter(D::Vector{<:GMTdataset}; kw...) = getbyattrib(D; kw...)
338+
# ---------------------------------------------------------------------------------------------------
339+
"""
340+
findall(D::Vector{<:GMTdataset}; kw...)
341+
342+
See `? getbyattrib`
343+
"""
305344
Base.:findall(D::Vector{<:GMTdataset}; kw...) = getbyattrib(D, true; kw...)
306345

307346
# ---------------------------------------------------------------------------------------------------

src/utils_project.jl

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ end
4444

4545
# -----------------------------------------------------------------------------------------------
4646
"""
47-
GI[,coast] = worldrectangular(GI; proj::String="+proj=vandg", pm=0, latlim=:auto, coast=false)
47+
GI[,coast] = worldrectangular(fname::String|GI::GItype; proj::String="+proj=vandg", pm=0, latlim=:auto, coast=false)
4848
4949
Try to create a rectangular map out miscellaneous and not cylindrical projections.
5050
@@ -60,7 +60,7 @@ Try to create a rectangular map out miscellaneous and not cylindrical projection
6060
other than the GSHHG GMT database.
6161
6262
### Returns
63-
A grid or an image and optionally the coastlines ... or errors. Not many projections support the procedure
63+
A grid or an image and optionally the coastlines. Not many projections support the procedure
6464
implemented in this function.
6565
The working or not is controlled by PROJ's `+over` option https://proj.org/usage/projections.html#longitude-wrapping
6666
@@ -157,12 +157,12 @@ end
157157

158158
# -----------------------------------------------------------------------------------------------
159159
"""
160-
GI[,coast] = leepacific(fname::String; latlims=nothing, lonlims=nothing, coast=true)
160+
GI[,coast] = leepacific(fname::String|GI::GItype; latlims=nothing, lonlims=nothing, coast=true)
161161
162162
Project a geographical grid/image in the Lee Oblated Stereographic projection centered on the Pacific Ocean.
163163
164-
- `GI`: A GMTgrid or GMTimage data type. `GI` can also be a string with a file name of a grid or image.
165-
NOTE: This grid/image should have longitudes covering the range 90 to 300 degrees (here lon in [0 360] is better)
164+
- `GI/fname`: A GMTgrid or GMTimage data type. `GI` can also be a string with a file name of a grid or image.
165+
NOTE: This grid/image should (ideally) have longitudes covering the range 76 to 319 degrees (here lon in [0 360] is better)
166166
- `latlims`: Latitudes used in `region` when reading the grid/image. The default is (-87, 75).
167167
- `lonlims`: Longitudes used in `region` when reading the grid/image. You cannot deviate mutch from (90,300).
168168
Actually, while a bug in GDAL is not fixed, you should not change the default values.
@@ -171,13 +171,17 @@ Project a geographical grid/image in the Lee Oblated Stereographic projection ce
171171
Pass `coast=D`, where `D` is vector of GMTdataset containing coastline polygons with a provenience
172172
other than the GSHHG GMT database. If `coast=false` the funtion returns only the projected grid/image.
173173
174+
Note: This function uses ``worldrectangular`` with the `+proj=lee_os` projection. If one wants to add
175+
other elements to the map (points, lines, etc) one must projectem first with the `+proj=lee_os` projection
176+
(use ``lonlat2xy`` or ``mapproject`` to that purpose).
177+
174178
### Returns
175179
A grid or an image and optionally the coastlines.
176180
177181
### Example:
178182
G,cl = leepacific("@earth_relief_10m_g");
179183
grdimage(G, shade=true, plot=(data=cl,), cmap=:geo, B=:none)
180-
plotgrid!(G_, show=true)
184+
plotgrid!(G, show=true)
181185
"""
182186
function leepacific(fname::String; region=(90.0, 300.0, -90.0, 75.0), latlims=nothing, lonlims=nothing, coast=true)
183187
reg = Float64.(collect(region))

0 commit comments

Comments
 (0)