forked from NCAS-CMS/cf-plot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtrajectory.py
More file actions
504 lines (450 loc) · 16.1 KB
/
Copy pathtrajectory.py
File metadata and controls
504 lines (450 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
from copy import deepcopy
import cartopy.crs as ccrs
import cf
import numpy as np
from .colour import cbar
from .colour import cscale
from .layout_runtime import ensure_runtime_session, finalize_runtime_session, gset
from .map_runtime import (
_apply_current_map_title,
_apply_map_axes_with_toggles,
_apply_map_features,
_ensure_map_axes,
)
from .state import plotvars
from . import utility
def traj(
f=None,
title=None,
ptype=0,
linestyle="-",
linewidth=1.0,
linecolor="b",
marker="o",
markevery=1,
markersize=5.0,
markerfacecolor="r",
markeredgecolor="g",
markeredgewidth=1.0,
latmax=None,
latmin=None,
axes=True,
xaxis=True,
yaxis=True,
verbose=None,
legend=False,
legend_lines=False,
xlabel=None,
ylabel=None,
xticks=None,
yticks=None,
xticklabels=None,
yticklabels=None,
colorbar=None,
colorbar_position=None,
colorbar_orientation="horizontal",
colorbar_title=None,
colorbar_text_up_down=False,
colorbar_text_down_up=False,
colorbar_drawedges=True,
colorbar_fraction=None,
colorbar_thick=None,
colorbar_anchor=None,
colorbar_shrink=None,
colorbar_labels=None,
vector=False,
head_width=0.4,
head_length=1.0,
fc="k",
ec="k",
zorder=None,
):
"""
| The interface to trajectory plotting in cf-plot.
|
| The minimum use is traj(f) where f is a CF field.
|
| f - CF data used to make a line plot
| linestyle='-' - line style
| linecolor='b' - line colour
| linewidth=1.0 - line width
| marker='o' - marker for points along the line
| markersize=30 - size of the marker
| markerfacecolor='b' - colour of the marker face
| markeredgecolor='g' - colour of the marker edge
| legend=False - plot different colour markers based on a set of user
| levels
| zorder=None - order for plotting
| verbose=None - Set to True to get a verbose listing of what traj is doing
|
| The following parameters override any CF data defaults:
| title=None - plot title
| axes=True - plot x and y axes
| xaxis=True - plot xaxis
| yaxis=True - plot y axis
| xlabel=None - x name
| ylabel=None - y name
| xticks=None - x ticks
| xticklabels=None - x tick labels
| yticks=None - y ticks
| yticklabels=None - y tick labels
| colorbar=None - plot a colorbar
| colorbar_position=None - position of colorbar
| [xmin, ymin, x_extent,y_extent] in normalised
| coordinates. Use when a common colorbar
| is required for a set of plots. A typical set
| of values would be [0.1, 0.05, 0.8, 0.02]
| colorbar_orientation=None - orientation of the colorbar
| colorbar_title=None - title for the colorbar
| colorbar_text_up_down=False - if True horizontal colour bar labels
| alternate above (start) and below the
| colour bar
| colorbar_text_down_up=False - if True horizontal colour bar labels
| alternate below (start) and above the
| colour bar
| colorbar_drawedges=True - draw internal divisions in the colorbar
| colorbar_fraction=None - space for the colorbar - default = 0.21, in
| normalised coordinates
| colorbar_thick=None - thickness of the colorbar - default = 0.015, in
| normalised coordinates
| colorbar_anchor=None - default=0.5 - anchor point of colorbar within
| the fraction space. 0.0 = close to plot,
| 1.0 = further away
| colorbar_shrink=None - value to shrink the colorbar by. If the colorbar
| exceeds the plot area then values of 1.0, 0.55
| or 0.5m ay help it better fit the plot area.
| colorbar_labels=None - labels for the colorbar. Default is to use the
| levels defined
| using cfp.levs
| Vector options
| vector=False - Draw vectors
| head_width=2.0 - vector head width
| head_length=2.0 - vector head length
| fc='k' - vector face colour
| ec='k' - vector edge colour
"""
is_1d_data = False
if f.ndim == 1:
is_1d_data = True
is_dsg = False
if f.DSG or is_1d_data: # registered as a DSG or if is 1D, assume is DSG
is_dsg = True
if verbose:
print("traj - making a trajectory plot")
if isinstance(f, cf.FieldList):
errstr = (
"\n\ncfp.traj - cannot make a trajectory plot from a field list "
"- need to pass a field\n\n"
)
raise TypeError(errstr)
# Read in data
# Find the auxiliary lons and lats if provided
has_lons = False
has_lats = False
for mydim in list(f.auxiliary_coordinates()):
name = utility.cf_var_name(field=f, dim=mydim)
if name in ["longitude"]:
lons = np.squeeze(f.construct(mydim).array)
has_lons = True
if name in ["latitude"]:
lats = np.squeeze(f.construct(mydim).array)
has_lats = True
data = f.array
# Raise an error if lons and lats not found in the input data
if not has_lons or not has_lats:
message = "\n\n\ntraj error\n"
if not has_lons:
message += "missing longitudes in the field auxiliary data\n"
if not has_lats:
message += "missing latitudes in the field auxiliary data\n"
message += "\n\n\n"
raise TypeError(message)
if latmax is not None:
pts = np.where(lats >= latmax)
if np.size(pts) > 0:
lons[pts] = np.nan
lats[pts] = np.nan
if latmin is not None:
pts = np.where(lats <= latmin)
if np.size(pts) > 0:
lons[pts] = np.nan
lats[pts] = np.nan
# Set potential user axis labels
user_xlabel = xlabel
user_ylabel = ylabel
# Set plotting parameters
continent_linestyle = plotvars.continent_linestyle or "-"
##################
# Open a new plot if necessary
##################
auto_session = ensure_runtime_session(pos=1)
# Set up mapping
if plotvars.user_mapset == 0:
plotvars.lonmin = -180
plotvars.lonmax = 180
plotvars.latmin = -90
plotvars.latmax = 90
_ensure_map_axes()
mymap = plotvars.mymap
# Set the plot limits
gset(
xmin=plotvars.lonmin,
xmax=plotvars.lonmax,
ymin=plotvars.latmin,
ymax=plotvars.latmax,
user_gset=0,
)
# Make lons and lats 2d if they are 1d
ndim = np.ndim(lons)
if ndim == 1:
lons = lons.reshape(1, -1)
lats = lats.reshape(1, -1)
ntracks = np.shape(lons)[0]
if ndim == 1:
ntracks = 1
if legend or legend_lines:
# Check levels are not None
levs = plotvars.levels
if plotvars.levels is not None:
if verbose:
print(
"traj - plotting different colour markers based on a "
"user set of levels"
)
levs = plotvars.levels
else:
# Automatic levels
if verbose:
print("traj - generating automatic legend levels")
dmin = np.nanmin(data)
dmax = np.nanmax(data)
levs, mult = utility.gvals(dmin=dmin, dmax=dmax, mod=False)
# Add extend options to the levels if set
if plotvars.levels_extend == "min" or plotvars.levels_extend == "both":
levs = np.append(-1e-30, levs)
if plotvars.levels_extend == "max" or plotvars.levels_extend == "both":
levs = np.append(levs, 1e30)
# Set the default colour scale
if plotvars.cscale_flag == 0:
cscale("viridis", ncols=np.size(levs) + 1)
plotvars.cscale_flag = 0
# User selected colour map but no mods so fit to levels
if plotvars.cscale_flag == 1:
cscale(plotvars.cs_user, ncols=np.size(levs) + 1)
plotvars.cscale_flag = 1
##################################
# Line, symbol and vector plotting
##################################
for track in np.arange(ntracks):
xpts = lons[track, :]
ypts = lats[track, :]
if is_1d_data:
data2 = data
else:
data2 = data[track, :]
xpts_orig = deepcopy(xpts)
xpts = np.mod(xpts + 180, 360) - 180
# Check if xpts are only within the remapped longitudes above
if np.min(xpts) < -170 or np.max(xpts) > 170:
xpts = xpts_orig
for ix in np.arange(np.size(xpts) - 1):
diff = xpts[ix + 1] - xpts[ix]
if diff >= 60:
xpts[ix + 1] = xpts[ix + 1] - 360.0
if diff <= -60:
xpts[ix + 1] = xpts[ix + 1] + 360.0
# Plot lines and markers
plot_linewidth = linewidth
plot_markersize = markersize
if legend:
plot_markersize = 0.0
if plot_linewidth > 0.0 or plot_markersize > 0.0:
if verbose and track == 0 and linewidth > 0.0:
print("plotting lines")
if verbose and track == 0 and markersize > 0.0:
print("plotting markers")
if legend_lines is False:
mymap.plot(
xpts,
ypts,
color=linecolor,
linewidth=plot_linewidth,
linestyle=linestyle,
marker=marker,
markevery=markevery,
markersize=plot_markersize,
markerfacecolor=markerfacecolor,
markeredgecolor=markeredgecolor,
markeredgewidth=markeredgewidth,
zorder=zorder,
clip_on=True,
transform=ccrs.PlateCarree(),
)
else:
line_xpts = xpts.compressed()
line_ypts = ypts.compressed()
line_data = data2.compressed()
for i in np.arange(np.size(line_xpts) - 1):
val = (line_data[i] + line_data[i + 1]) / 2.0
col = plotvars.cs[np.max(np.where(val > plotvars.levels))]
mymap.plot(
line_xpts[i : i + 2],
line_ypts[i : i + 2],
color=col,
linewidth=plot_linewidth,
linestyle=linestyle,
zorder=zorder,
clip_on=True,
transform=ccrs.PlateCarree(),
)
# Plot vectors
if vector:
if verbose and track == 0:
print("plotting vectors")
if zorder is None:
plot_zorder = 101
else:
plot_zorder = zorder
if plotvars.proj == "cyl":
if isinstance(xpts, np.ma.MaskedArray):
pts = np.ma.MaskedArray.count(xpts)
else:
pts = xpts.size
for pt in np.arange(pts - 1):
mymap.arrow(
xpts[pt],
ypts[pt],
xpts[pt + 1] - xpts[pt],
ypts[pt + 1] - ypts[pt],
head_width=head_width,
head_length=head_length,
fc=fc,
ec=ec,
length_includes_head=True,
zorder=plot_zorder,
clip_on=True,
transform=ccrs.PlateCarree(),
)
# Plot different colour markers based on a user set of levels
if legend:
# For polar stereographic plots mask any points outside the
# plotting limb
if plotvars.proj == "npstere":
pts = np.where(lats < plotvars.boundinglat)
if np.size(pts) > 0:
lats[pts] = np.nan
if plotvars.proj == "spstere":
pts = np.where(lats > plotvars.boundinglat)
if np.size(pts) > 0:
lats[pts] = np.nan
for track in np.arange(ntracks):
xpts = lons[track, :]
ypts = lats[track, :]
if is_1d_data:
data2 = data
else:
data2 = data[track, :]
for i in np.arange(np.size(levs) - 1):
color = plotvars.cs[i]
if np.ma.is_masked(data2):
pts = np.ma.where(
np.logical_and(data2 >= levs[i], data2 <= levs[i + 1])
)
else:
pts = np.where(
np.logical_and(data2 >= levs[i], data2 <= levs[i + 1])
)
if zorder is None:
plot_zorder = 101
else:
plot_zorder = zorder
if np.size(pts) > 0:
# Define the data to plot
if is_dsg or is_1d_data:
data_colours = [
plotvars.cs[np.max(np.where(d > plotvars.levels))]
for d in data2[pts]
]
else:
data_colours = color
mymap.scatter(
xpts[pts],
ypts[pts],
s=markersize,
c=data_colours,
marker=marker,
edgecolors=markeredgecolor,
linewidths=plot_linewidth,
transform=ccrs.PlateCarree(),
zorder=plot_zorder,
)
# Axes
_apply_map_axes_with_toggles(
axes=axes,
xaxis=xaxis,
yaxis=yaxis,
xticks=xticks,
xticklabels=xticklabels,
yticks=yticks,
yticklabels=yticklabels,
user_xlabel=user_xlabel,
user_ylabel=user_ylabel,
)
_apply_map_features(
mymap=mymap,
continent_color=plotvars.continent_color,
continent_thickness=plotvars.continent_thickness,
continent_linestyle=continent_linestyle,
)
# Title
if title is not None:
_apply_current_map_title(title)
# Color bar
plot_colorbar = False
if colorbar is None and legend:
plot_colorbar = True
if colorbar is None and legend_lines:
plot_colorbar = True
if colorbar:
plot_colorbar = True
if plot_colorbar:
if colorbar_title is None:
colorbar_title = "No Name"
if hasattr(f, "id"):
colorbar_title = f.id
nc = f.nc_get_variable(False)
if nc:
colorbar_title = f.nc_get_variable()
if hasattr(f, "short_name"):
colorbar_title = f.short_name
if hasattr(f, "long_name"):
colorbar_title = f.long_name
if hasattr(f, "standard_name"):
colorbar_title = f.standard_name
if hasattr(f, "Units"):
if str(f.Units) == "":
colorbar_title += ""
else:
colorbar_title += f"({utility._supscr(str(f.Units))})"
levs = plotvars.levels
if colorbar_labels is not None:
levs = colorbar_labels
cbar(
levs=levs,
labels=levs,
orientation=colorbar_orientation,
position=colorbar_position,
text_up_down=colorbar_text_up_down,
text_down_up=colorbar_text_down_up,
drawedges=colorbar_drawedges,
fraction=colorbar_fraction,
thick=colorbar_thick,
shrink=colorbar_shrink,
anchor=colorbar_anchor,
title=colorbar_title,
verbose=verbose,
)
##########
# Save plot
##########
finalize_runtime_session(auto_session=auto_session, view=True)