Plotting line across international dateline with Cartopy - python

I am trying to plot global storm tracks, but when the storms cross the dateline (and longitudes go from ~360 to ~0), the line loops all the way around the plotting space.
Here's what the plot looks like. See the weird straight lines near the top.
Here's my code:
ax = plt.axes(projection=ccrs.Robinson())
ax.set_global()
ax.coastlines()
for i in range(nstorms-1): #loop through each TC
bidx = start_idx[i]
eidx = start_idx[i+1]
plt.plot(clons[bidx:eidx],clats[bidx:eidx],transform=ccrs.PlateCarree())
If I try changing the transform to Geodetic, it looks like this:

To plot polylines that cross the dateline, you need to sanitize the longitudes properly. For example, values 359 to 2 should be adjusted to 359 to 362. In the demo code below, sanitize_lonlist() is used to sanitize a list of longitude values before using it to plot a red zigzag line.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
def sanitize_lonlist(lons):
new_list = []
oldval = 0
treshold = 10 # used to compare adjacent longitudes
for ix,ea in enumerate(lons):
diff = oldval - ea
if (ix>0):
if (diff>treshold):
ea = ea+360
oldval = ea
new_list.append(ea)
return new_list
ax = plt.axes(projection=ccrs.Robinson())
ax.set_global()
ax.coastlines(alpha=0.3)
# sample long/lat data for demo purposes
# xdateline: list of longitudes that cross dateline several times
xdateline = [347,349,352,358,4,7,8,3,359,358,360,3,5,359,1,357,0,8,12,6,357,349]
# ydateline: list of accompanying latitudes
ydateline = range(len(xdateline))
# plot the line crossing dateline using `sanitized` values of longitudes
plt.plot(sanitize_lonlist(xdateline), ydateline, transform=ccrs.PlateCarree(), color='red')
plt.show()
Using the raw values of xdateline to plot with the line of code:-
plt.plot(xdateline, ydateline, transform=ccrs.PlateCarree(), color='red')
the plot will be:-

As per this github issue, this is expected behaviour since PlateCarree is a projected coordinate system.
The PlateCarree coordinate system is Cartesian where a line between two points is straight (in that coordinate system). The Cartesian system has no knowledge of datelines/antimeridians and so when you ask for a line between -170 and +170 you get a line of length 340. It can never be the case that the PlateCarree projection interprets these numbers and chooses to draw a non-cartesian line
One solution is to use the Geodetic transform in your plot calls:
plt.plot(clons[bidx:eidx], clats[bidx:eidx], transform=ccrs.Geodetic())
Or modify your data to make more sense when using the PlateCarree system, e.g. by identifying where values loop from 360-> 0 and adding 360 to all values after that occurs. You could shift them onto a different range (e.g. -180..180) but you'll have the same issue with data crossing +/- 180 as you do with 0/360 currently.

Related

Random (false data) lines appearing in contourf plot at certain # of levels

I'm trying to use matplotlib and contourf to generate some filled (polar) contour plots of velocity data. I have some data (MeanVel_Z_Run16_np) I am plotting on theta (Th_Run16) and r (R_Run16), as shown here:
fig,ax = plt.subplots(subplot_kw={'projection':'polar'})
levels = np.linspace(-2.5,4,15)
cplot = ax.contourf(Th_Run16,R_Run16,MeanVel_Z_Run16_np,levels,cmap='plasma')
ax.set_rmax(80)
ax.set_rticks([15,30,45,60])
rlabels = ax.get_ymajorticklabels()
for label in rlabels:
label.set_color('#E6E6FA')
cbar = plt.colorbar(cplot,pad=0.1,ticks=[0,3,6,9,12,15])
cbar.set_label(r'$V_{Z}$ [m/s]')
plt.show()
This generates the following plot:
Velocity plot with 15 levels:
Which looks great (and accurate), outside of that random straight orange line roughly between 90deg and 180deg. I know that this is not real data because I plotted this in MATLAB and it did not appear there. Furthermore, I have realized it appears to relate to the number of contour levels I use. For example, if I bump this code up to 30 levels instead of 15, the result changes significantly, with odd triangular regions of uniform value:
Velocity plot with 30 levels:
Does anyone know what might be going on here? How can I get contourf to just plot my data without these strange misrepresentations? I would like to use 15 contour levels at least. Thank you.

Plot the speed of the proton against magnetic field in Python

I have been trying to plot the linear speed of the proton against magnetic field. The proton is moving in a circular orbit in an
uniform magnetic field perpendicular to the velocity. The radius of the orbit changes from 14 cm to 8 cm when the uniform magnetic field increases from 0.35 T to 2 T.
#!/usr/bin/env python
import matplotlib.pyplot as plt
import numpy as np
import scipy.constants
p = 1.6
q = scipy.constants.e
p_mass = scipy.constants.proton_mass
B_List = []
r_List = []
v_List = []
for r in np.linspace (0.14, 0.08):
for B in np.linspace (0.35, 2):
v = (r*q*B)/p_mass
B_List.append(B)
v_List.append(v)
fig = plt.figure()
plt.plot(B_List,v_List)
fig.suptitle('Linear Speed Vs Magnatic Field', fontsize=18)
plt.xlabel('Magnatic field (T)', fontsize=14)
plt.ylabel('Speed of the Proton (m/s)', fontsize=14)
plt.show()
For some reasons, I got a multiple lines on the graph.
I tried to make the spaces = 2 (in linspace) as follows:
for r in np.linspace (0.14, 0.08,2, 2):
for B in np.linspace (0.35, 2, 2):
but still, those multiple lines are appearing.
My question here, how to get rid of those lines?
Your main problem is that you use the plot function in pyplot to show your points. That function draws line segments between consecutive plotted points.
Instead, use the scatter function, which just shows the points without any connecting line segments. When I replace plot with scatter, I get the result below. Click on the graphic to see it full size--then you will see that each of those apparent vertical line segments are actually 50 points close together. That seems to be correct, though there are no circular paths that you mention. Is that what you want?

How to plot contours from a polar stereographic grib2 file in Python

I am trying to plot a CMC grib2 pressure forecast file using matplotlib to plot the pressure contours. The description of the grib2 grid can be found here: https://weather.gc.ca/grib/grib2_reg_10km_e.html. The grib2 file is found in this directory: http://dd.weather.gc.ca/model_gem_regional/10km/grib2/00/000/ and starts with CMC_reg_PRMSL_MSL_0_ps10km followed by the date. It is a grib file containing pressure at mean sea level.
My problem is that I end up having some straight line contours that follow the lines of latitude on top of the actual pressure contours. I thought it might be because I am plotting in PlateCarree as opposed to Geodetic but the contour plot will not allow using Geodetic. The result of my plot is:
Code is as follows:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import datetime as dt
import cartopy
import cartopy.crs as ccrs
import Nio
gr = Nio.open_file('./data/CMC_reg_PRMSL_MSL_0_ps10km_2018111800_P000.grib2', 'r')
print(gr)
names = gr.variables.keys()
print("Variable Names:", names)
dims = gr.dimensions
print("Dimensions: ", dims)
attr = gr.attributes.keys()
print("Attributes: ", attr)
obs = gr.variables['PRMSL_P0_L101_GST0'][:]
lats = gr.variables["gridlat_0"][:]
lons = gr.variables["gridlon_0"][:]
fig = plt.figure(figsize=(15, 2))
intervals = range(95000, 105000, 400)
ax=plt.axes([0.,0.,1.,1.],projection=ccrs.PlateCarree())
obsobj = plt.contour(lons, lats, obs, intervals, cmap='jet',transform=ccrs.PlateCarree())
states_provinces = cartopy.feature.NaturalEarthFeature(
category='cultural',
name='admin_1_states_provinces_lines',
scale='50m',
facecolor='none')
ax.add_feature(cartopy.feature.BORDERS)
ax.coastlines(resolution='10m')
ax.add_feature(states_provinces,edgecolor='gray')
obsobj.clabel()
colbar =plt.colorbar(obsobj)
Any suggestions would be appreciated.
UPDATE
For anyone without PyNIO the following can be used to reproduce using the dump files in the comments section.
Just remove all the references to NIO and replace the lats, lons, obs assignment with the following.
lats = np.load('lats.dump')
lons = np.load('lons.dump')
obs = np.load('obs.dump')
The problem
The problem is that the grid winds around the earth. Hence there will be points on the grid at -180° whose nearst neighbor sits at +180°, i.e. the grid wraps around the antimeridian. The following plots the grid index along both directions. One can see that the first grid row (black) appears on both sides of the plot.
Hence a contour line following the pacific westwards needs to then cross straight through the plot to continue towards japan on the other side of the plot. This will lead to the undesired lines
A solution
A solution is to mask the outer points of the PlateCarree out. Those occur in the middle of the grid. Cutting the grid at coordinates of longitude larger than 179° or smaller than -179°, as well as leaving the north pole out would look like
where the blue denotes the cut out points.
Applying this to the contour plot gives:
import matplotlib.pyplot as plt
import numpy as np
import cartopy
import cartopy.crs as ccrs
lats = np.load('data/lats.dump')
lons = np.load('data/lons.dump')
obs = np.load('data/obs.dump')
intervals = range(95000, 105000, 400)
fig, ax = plt.subplots(figsize=(15,4), subplot_kw=dict(projection=ccrs.PlateCarree()))
fig.subplots_adjust(left=0.03, right=0.97, top=0.8, bottom=0.2)
mask = (lons > 179) | (lons < -179) | (lats > 89)
maskedobs = np.ma.array(obs, mask=mask)
pc = ax.contour(lons, lats, maskedobs, intervals, cmap='jet', transform=ccrs.PlateCarree())
ax.add_feature(cartopy.feature.BORDERS)
ax.coastlines(resolution='10m')
colbar =plt.colorbar(pc)
plt.show()
If you are sum up your longitude by +180 to avoid negative coordinates, your code should be running. A coordinate transformation should be legit from my point of view.

Matplotlib 2.02 plotting within a for loop

I am having trouble with two things on a plot I am generating within a for loop, my code loads some data in, fits it to a function using curve_fit and then plots measured data and the fit on the same plot for 5 different sets of measured y value (the measured data is represent by empty circle markers and fit by a solid line as the same color as the marker)
Firstly I am struggling to reduce the linewidth of the fit (solid line) however much I reduce the float value of linewidth, I can increase the size just not decrease it by the value displayed in the output below. Secondly I would like the legend to display only circle markers not circles with lines through - I cannot seem to get this to work, any ideas?
Here is my code and attached is the output plot and data file on google drive share link (for some reason it's cutting off long lines of text on this post):
import scipy
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
#define vogel-fulcher-tamman (VFT) function
def vft(x,sigma_0,temp_vf,D):
return np.log(sigma_0)-((D*temp_vf)/(x-temp_vf))
#load and sort data
data=np.genfromtxt('data file',skip_header=3)
temp=data[:,0]
inverse_temp=data[:,1]
dc_conduct=np.log10(data[:,2:11])
only_adam=dc_conduct[:,4:9]
colors = ['b','g','r','c','m']
labels = ['50mg 2-adam','300mg 2-adam','100 mg 2-adam','150 mg 2-adam','250mg
2-adam']
for i in range(0,len(only_adam)):
#fit VTF function
y=only_adam[:,i]
popt, pcov = curve_fit(vft,temp,y)
#plotting
plt.plot(inverse_temp,y,color=colors[i],marker='o',markerfacecolor='none',
label=labels[i])
plt.plot(inverse_temp,vft(temp, *popt),linewidth=0.00001,linestyle='-
',color=colors[i])
plt.ylabel("Ionic Conductivity [Scm**2/mol]")
plt.xlabel("1000 / [T(K)]")
plt.axis('tight')
plt.legend(loc='lower left')
You are looping over the rows of only_adam, but index the columns of that array with the loop variable i. This does not make sense and leads to the error shown.
The plot that shows the data points has lines in it. Those are the lines shown. You cannot make them smaller by decreasing the other plot's linewidth. Instead you need to set the linestyle of that plot off, e.g. plot(..., ls="")

Python - Interpolating a gap in scattered data points

I'm trying to interpolate a gap I have between data points. The data I have is 2 arrays of time and acceleration. The acceleration array consist of values that can be considered periodic. The original data points with the gap look like this:
data points with gap
I am trying to do the interpolation by using the scipy.interpolate.interp1d as illustrated below:
interpolation_func = interpolate.interp1d(time, acceleration,
kind='slinear')
new_time = np.arange(np.min(time), np.max(time), 0.1)
new_acc = interpolation_func(new_time)
plt.figure(2, figsize=(14, 8))
plt.title('Interpolated uncalibrated acceleration data')
plt.scatter(new_time, new_acc, c=new_time[:], s=1, vmin=np.min(new_time),
vmax=np.max(new_time))
plt.colorbar()
plt.xlabel('Time [s]')
plt.ylabel('Acceleration')
plot_fig2 = (output_folder + "kinematic_plot2.png")
plt.savefig(plot_fig2)
However, the result I'm getting is not accurate because I'm get a line that connects the last point from 1st group of scattered points, on the left side of the gap, and the first point from the 2nd group of points, on the right side of the gap. The wrong result looks like this:
Wrong result
I have tried other options from the scipy.interpolate.interp1d function, other than the kind slinear, but all of them would flatten the scattered points on both sides of the gap and fill in the gap with a polynomial graph, which is not what I need. Are there any options in python to interpolate the gap I have between the scattered points?

Categories