Unable to plot circles on a map projection in basemap using Python - python-3.x

I'm trying to plot circles on a miller projection map using a center latitude, longitude and radius. I can't get the circles to show up on the map projection. I've tried plotting them using different techniques as shown in the links.
How to plot a circle in basemap or add artiste
How to make smooth circles on basemap projections
Here is my code:
def plot_notams(dict_of_filtered_notams):
''' Create a map of the US and plot all NOTAMS from a given time period.'''
'''Create the map'''
fig = plt.figure(figsize=(8,6), dpi=200)
ax = fig.add_subplot(111)
m = Basemap(projection='mill',llcrnrlat=20, urcrnrlat=55, llcrnrlon=-135, urcrnrlon=-60, resolution='h')
m.drawcoastlines()
m.drawcountries(linewidth=2)
m.drawstates()
m.fillcontinents(color='coral', lake_color='aqua')
m.drawmapboundary(fill_color='aqua')
m.drawmeridians(np.arange(-130, -65, 10), labels=[1,0,0,1], textcolor='black')
m.drawparallels(np.arange(20, 60, 5), labels=[1,0,0,1], textcolor='black')
''' Now add the NOTAMS to the map '''
notam_data = dict_of_filtered_notams['final_notam_list']
for line in notam_data:
notam_lat = float(line.split()[0])
notam_lon = float(line.split()[1])
coords = convert_coords(notam_lon, notam_lat)
notam_lon, notam_lat = coords[0], coords[1]
FL400_radius = np.radians(float(line.split()[2]))
x,y = m(notam_lon, notam_lat)
print("notam_lon = ",notam_lon, "notam_lat = ", notam_lat,"\n")
print("x,y values = ",'%.3f'%x,",",'%.3f'%y,"\n")
print("FL400_radius = ",('% 3.2f' % FL400_radius))
print("")
cir = plt.Circle((x,y), FL400_radius, color="white", fill=False)
ax.add_patch(cir)
(The convert_coords function is simply formatting the notam_lon/notam_lat values into a usable format as shown in the data below.)
Here is what my data looks like (you can see where it's being printed in the code above):
notam_lon = -117.7839 notam_lat = 39.6431
x,y values = 1914342.075 , 2398770.441
FL400_radius = 6.98
Here's an image of what my code above produces:
I also tried using the map.plot() function (specifically, m.plot(x,y, "o")) in place of "ax.add_patch(cir)." That worked but plotted points or "o's," of course. Here's the image produced by replacing "ax.add_patch(cir)" with "m.plot(x,y, "o")."
And as a final note, I'm using basemap 1.2.0-1 and matplotlib 3.0.3. I haven't found any indication that these versions are incompatible. Also, this inability to plot a circle wasn't an issue 2 months ago when I did this last. I'm at a loss here. I appreciate any feedback. Thank you.

To plot circles on a map, you need appropriate locations (x,y) and radius. Here is a working code and resulting plot.
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
# make up 10 data points for location of circles
notam_lon = np.linspace(-117.7839, -100, 10)
notam_lat = np.linspace(39.6431, 52, 10)
# original radius of circle is too small
FL400_radius = 6.98 # what unit?
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111)
m = Basemap(projection='mill', llcrnrlat=20, urcrnrlat=55, llcrnrlon=-135, urcrnrlon=-60, resolution='l')
# radiusm = (m.ymax-m.ymin)/10. is good for check plot
radiusm = FL400_radius*10000 # meters, you adjust as needed here
for xi,yi in zip(notam_lon, notam_lat):
# xy=m(xi,yi): conversion (long,lat) to (x,y) on map
circle1 = plt.Circle(xy=m(xi,yi), radius=radiusm, \
edgecolor="blue", facecolor="yellow", zorder=10)
#ax.add_patch(circle1) # deprecated
ax.add_artist(circle1) # use this instead
m.drawcoastlines()
m.drawcountries(linewidth=2)
m.drawstates()
m.fillcontinents(color='coral', lake_color='aqua')
# m.drawmapboundary(fill_color='aqua') <-- causes deprecation warnings
# use this instead:
rect = plt.Rectangle((m.xmin,m.ymin), m.xmax-m.xmin, m.ymax-m.ymin, facecolor="aqua", zorder=-10)
ax.add_artist(rect)
m.drawmeridians(np.arange(-130, -65, 10), labels=[1,0,0,1], textcolor='black')
m.drawparallels(np.arange(20, 60, 5), labels=[1,0,0,1], textcolor='black')
plt.show()
The output map:
Hope this is useful.

Related

How to combine two geometries into one plot in Python

Question background: I am trying to make two geometries in a one plot in python. I have made one geometry which is an object having mesh as shown in figure below. The respective code is also mentioned here.
df_1_new = pd.DataFrame()
df_1_new['X_coordinate']=pd.Series(x_new)
df_1_new['Y_coordinate']=pd.Series(y_new)
df_1_new['node_number'] = df_1_new.index
df_1_new = df_1_new[['node_number','X_coordinate','Y_coordinate']]
plt.scatter(x_new, y_new)
plt.show
The second geometry, which is a circle and I made this geometry running below code.
from matplotlib import pyplot as plt, patches
plt.rcParams["figure.figsize"] = [9.00, 6.50]
plt.rcParams["figure.autolayout"] = True
fig = plt.figure()
ax = fig.add_subplot()
circle1 = plt.Circle((2, 2), radius=5, fill = False)
ax.add_patch(circle1)
ax.axis('equal')
plt.show()
My question: How can I combine both geometries mentioned above in a one plot. I would like to place my circle around my geometry (object). Geometry has a centroid (2, 2) and I want to place my circle's centroid exactly on the centroid of geometry therefore I will be having a circle around my geometry. What code I should write. Kindly help me on this.
For your reference: I want my plot just like in below picture.
you need to do all the plotting between the subplot creation and before you issue the plt.show() command, as any command after it will create a new figure.
from matplotlib import pyplot as plt, patches
plt.rcParams["figure.figsize"] = [9.00, 6.50]
plt.rcParams["figure.autolayout"] = True
fig = plt.figure()
ax = fig.add_subplot()
# other plt.scatter or plt.plot here
plt.scatter([3,4,5,6,4],[5,4,2,3,2]) # example
circle1 = plt.Circle((2, 2), radius=5, fill = False)
ax.add_patch(circle1)
ax.axis('equal')
plt.show()
image example
to get the points inside the circle, you need to play with the circle radius and center till you get it right.
something you can do is to make the circle at the np.median of your x and y values, so you are sure about the center position.

Coordinate conversion problem of a FITS file

I have loaded and plotted a FITS file in python.
With the help of a previous post, I have managed to get the conversion of the axis from pixels to celestial coordinates. But I can't manage to get them in milliarcseconds (mas) correctly.
The code is the following
import numpy as np
import matplotlib.pyplot as plt
import astropy.units as u
from astropy.wcs import WCS
from astropy.io import fits
from astropy.utils.data import get_pkg_data_filename
filename = get_pkg_data_filename('hallo.fits')
hdu = fits.open(filename)[0]
wcs = WCS(hdu.header).celestial
wcs.wcs.crval = [0,0]
plt.subplot(projection=wcs)
plt.imshow(hdu.data[0][0], origin='lower')
plt.xlim(200,800)
plt.ylim(200,800)
plt.xlabel('Relative R.A ()')
plt.ylabel('Relative Dec ()')
plt.colorbar()
The output looks like
The y-label is cut for some reason, I do not know.
As it was shown in another post, one could use
wcs.wcs.ctype = [ 'XOFFSET' , 'YOFFSET' ]
to switch it to milliarcsecond, and I get
but the scale is incorrect!.
For instance, 0deg00min00.02sec should be 20 mas and not 0.000002!
Did I miss something here?
Looks like a spectral index map. Nice!
I think the issue might be that FITS implicitly uses degrees for values like CDELT. And they should be converted to mas explicitly for the plot.
The most straightforward way is to multiply CDELT values by 3.6e6 to convert from degrees to mas.
However, there is a more general approach which could be useful if you want to convert to different units at some point:
import astropy.units as u
w.wcs.cdelt = (w.wcs.cdelt * u.deg).to(u.mas)
So it basically says first that the units of CDELT are degrees and then converts them to mas.
The whole workflow is like this:
def make_transform(f):
'''use already read-in FITS file object f to build pixel-to-mas transformation'''
print("Making a transformation out of a FITS header")
w = WCS(f[0].header)
w = w.celestial
w.wcs.crval = [0, 0]
w.wcs.ctype = [ 'XOFFSET' , 'YOFFSET' ]
w.wcs.cunit = ['mas' , 'mas']
w.wcs.cdelt = (w.wcs.cdelt * u.deg).to(u.mas)
print(w.world_axis_units)
return w
def read_fits(file):
'''read fits file into object'''
try:
res = fits.open(file)
return res
except:
return None
def start_plot(i,df=None, w=None, xlim = [None, None], ylim=[None, None]):
'''starts a plot and returns fig,ax .
xlim, ylim - axes limits in mas
'''
# make a transformation
# Using a dataframe
if df is not None:
w = make_transform_df(df)
# using a header
if w is not None:
pass
# not making but using one from the arg list
else:
w = make_transform(i)
# print('In start_plot using the following transformation:\n {}'.format(w))
fig = plt.figure()
if w.naxis == 4:
ax = plt.subplot(projection = w, slices = ('x', 'y', 0 ,0 ))
elif w.naxis == 2:
ax = plt.subplot(projection = w)
# convert xlim, ylim to coordinates of BLC and TRC, perform transformation, then return back to xlim, ylim in pixels
if any(xlim) and any(ylim):
xlim_pix, ylim_pix = limits_mas2pix(xlim, ylim, w)
ax.set_xlim(xlim_pix)
ax.set_ylim(ylim_pix)
fig.add_axes(ax) # note that the axes have to be explicitly added to the figure
return fig, ax
rm = read_fits(file)
wr = make_transform(rm)
fig, ax = start_plot(RM, w=wr, xlim = xlim, ylim = ylim)
Then just plot to the axes ax with imshow or contours or whatever.
Of course, this piece of code could be reduced to meet your particular needs.

Control marker properties in seaborn pairwise boxplot

I'm trying to plot a boxplot for two different datasets on the same plot. The x axis are the hours in a day, while the y axis goes from 0 to 1 (let's call it Efficiency). I would like to have different markers for the means of each dataset' boxes. I use the 'meanprops' for seaborn but that changes the marker style for both datasets at the same time. I've added 2000 lines of data in the excel that can be downloaded here. The values might not coincide with the ones in the picture but should be enough.
Basically I want the red squares to be blue on the orange boxplot, and red on the blue boxplot. Here is what I managed to do so far:
I tried changing the meanprops by using a dictionary with the labels as keys , but it seems to be entering a loop (in PyCharm is says Evaluating...)
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
#make sure you have your path sorted out
group1 = pd.read_excel('group1.xls')
ax,fig = plt.subplots(figsize = (20,10))
#does not work
#ax = sns.boxplot(data=group1, x='hour', y='M1_eff', hue='labels',showfliers=False, showmeans=True,\
# meanprops={"marker":{'7':"s",'8':'s'},"markerfacecolor":{'7':"white",'8':'white'},
#"markeredgecolor":{'7':"blue",'8':'red'})
#works but produces similar markers
ax = sns.boxplot(data=group1, x='hour', y='M1_eff', hue='labels',showfliers=False, showmeans=True,\
meanprops={"marker":"s","markerfacecolor":"white", "markeredgecolor":"blue"})
plt.legend(title='Groups', loc=2, bbox_to_anchor=(1, 1),borderaxespad=0.5)
# Add transparency to colors
for patch in ax.artists:
r, g, b, a = patch.get_facecolor()
patch.set_facecolor((r, g, b, .4))
ax.set_xlabel("Hours",fontsize=14)
ax.set_ylabel("M1 Efficiency",fontsize=14)
ax.tick_params(labelsize=10)
plt.show()
I also tried the FacetGrid but to no avail (Stops at 'Evaluating...'):
g = sns.FacetGrid(group1, col="M1_eff", hue="labels",hue_kws=dict(marker=["^", "v"]))
g = (g.map(plt.boxplot, "hour", "M1_eff")
.add_legend())
g.show()
Any help is appreciated!
I don't think you can do this using sns.boxplot() directly. I think you'll have to draw the means "by hand"
N=100
df = pd.DataFrame({'hour':np.random.randint(0,3,size=(N,)),
'M1_eff': np.random.random(size=(N,)),
'labels':np.random.choice([7,8],size=(N,))})
x_col = 'hour'
y_col = 'M1_eff'
hue_col = 'labels'
width = 0.8
hue_order=[7,8]
marker_colors = ['red','blue']
# get the offsets used by boxplot when hue-nesting is used
# https://github.com/mwaskom/seaborn/blob/c73055b2a9d9830c6fbbace07127c370389d04dd/seaborn/categorical.py#L367
n_levels = len(hue_order)
each_width = width / n_levels
offsets = np.linspace(0, width - each_width, n_levels)
offsets -= offsets.mean()
fig, ax = plt.subplots()
ax = sns.boxplot(data=df, x=x_col, y=y_col, hue=hue_col, hue_order=hue_order, showfliers=False, showmeans=False)
means = df.groupby([hue_col,x_col])[y_col].mean()
for (gr,temp),o,c in zip(means.groupby(level=0),offsets,marker_colors):
ax.plot(np.arange(temp.values.size)+o, temp.values, 's', c=c)

How to plot Lon/Lat values at the border of a orthographic cartopy plot?

I use some shapefile data of the outline of Antartica in cartopy and this works fine. I can generate a plot with the shapefile and some more information on it. But I'm not able to plot the Longitude and Latitude information at the border of the image.
I use the orthographic projection with central_longitude and central_latitude.
I also need to mention that I'm comparably new to cartopy.
My code:
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.io.shapereader import Reader
# 01
b01e01_lat = -73.86750000
b01e01_lon = -60.22694444
b01e02_lat = -73.89166667
b01e02_lon = -56.68500000
b01e03_lat = -74.87222222
b01e03_lon = -58.26805556
b01e04_lat = -74.85000000
b01e04_lon = -60.43083333
b01e05_lat = -73.86750001
b01e05_lon = -60.22694445
b01_lat = np.array([b01e01_lat,b01e02_lat,b01e03_lat,b01e04_lat, b01e01_lat, b01e05_lat])
b01_lon = np.array([b01e01_lon,b01e02_lon,b01e03_lon,b01e04_lon, b01e01_lon, b01e05_lon])
# 02
b02e01_lat = -73.94555556
b02e01_lon = -51.00055556
b02e02_lat = -74.22333333
b02e02_lon = -49.37000000
b02e03_lat = -74.87555556
b02e03_lon = -50.71888889
b02e04_lat = -74.87583333
b02e04_lon = -51.00055556
b02e05_lat = -73.94555557
b02e05_lon = -51.00055557
fname='Coastline_Antarctica_v02.shp'
#ax = plt.axes(projection=ccrs.SouthPolarStereo())
plt.figure()
ax = plt.axes(projection=ccrs.Orthographic(central_longitude=-41,
central_latitude=-71))
ax.set_extent([-85,-12,-75,-60], crs=ccrs.PlateCarree())
ax.add_geometries(Reader(fname).geometries(),ccrs.Orthographic(central_longitude=-0,
central_latitude=-90), color='grey')
ax.gridlines()
plt.plot(b01_lon,b01_lat, color='r', transform=ccrs.PlateCarree())
plt.plot(b02_lon,b02_lat, color='r', transform=ccrs.PlateCarree())
plt.show()
With this I get the following plot (without the blue shapes):
Any help appreciated!
If you run your code to produce interactive plot (using %matplotlib notebook on jupyter notebook), you can move the mouse cursor to read the locations that you need to plot the labels.
With this method, I can get the approximate (long, lat) locations for plotting 2 sample labels. The code to plot them is as follows:
ax.text(-80.6, -57.0, '{0}\N{DEGREE SIGN} S '.format(57), va='center', ha='right',
transform=ccrs.PlateCarree())
ax.text(-75.15, -56.0, '{0}\N{DEGREE SIGN} W '.format(75), va='bottom', ha='center',
transform=ccrs.PlateCarree())
And the output plot will look like this:

Can't add matplotlib colorbar ticks

I am trying to add ticks and labels to a color bar, but it just doesn't seem to show up in the output. I have tried two approaches(as shown in the code below). Second appraoch was to do as shown in another question on Stack Overflow here: How to add Matplotlib Colorbar Ticks.
I must be overlooking something very simple here as I am a beginner in Matplotlib and Python.
I have managed to obtain the color bar, but the ticks I want just don't show up. Any help here will be greatly appreciated as I have been stuck at it for hours after trying and searching.
Here is the code I used to generate a heatmap using hexbin over a basemap.
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from matplotlib.colors import LinearSegmentedColormap
from matplotlib import cm
#Loading data from CSV file
DATA_FILE = '....../Population_data.csv'
roc_data = pd.read_csv(DATA_FILE)
roc_data.head()
#Creating figure window
fig = plt.figure(figsize=(14,10))
ax = fig.add_subplot(111)
#Drawing the basemap
m = Basemap(projection='merc', lat_0=43.12, lon_0=-77.626,
resolution = 'i',llcrnrlon=-78.236,
llcrnrlat=42.935,
urcrnrlon=-77.072,
urcrnrlat=43.349)
m.drawcoastlines()
m.drawcounties(zorder=20, color='red')
m.drawcountries()
m.drawmapboundary()
#plotting the heatmap using hexbin
x, y = m(roc_data['Longitude'].values, roc_data['Latitude'].values)
values = roc_data['Total(20-64)']
m.hexbin(x, y, gridsize = 125, bins = 'log', C = values, cmap = cm.Reds)
#Defining minimum, mean and maximum population values
max_p = roc_data['Total(20-64)'].max()
min_p = roc_data['Total(20-64)'].min()
mean_p = roc_data['Total(20-64)'].mean()
#Adding Colorbar
cb = m.colorbar(location = 'bottom', format = '%d', label = 'Population by Census Blocks')
#setting ticks
#cb.set_ticks([48, 107, 1302]) #First approach, didn't work
#cb.set_ticklabels(['Min', 'Mean', 'Max'])
cb.set_ticks([min_p, mean_p, max_p]) #Second appraoch, assumed ticks and tick labels should be same
cb.set_ticklabels([min_p, mean_p, max_p]) #from the above mentioned stackoverflow question, but did't work
plt.show()
The output I get by using the first or second approach for colorbar ticks is the same. It is as here:
Heatmap and colorbar with no ticks and labels
I want the minimum, median and maximum population values (48, 107 and 1302) to be shown on the colorbar with the labels Min, Mean and Max. Thank you for your time
When plotting the hexbin plot with mode bins = 'log', the colors will be plotted with a logarithmic scaling. This means that if the data minimum, mean and maximum are min, mean and max, their values on the logarithmically scaled colorbar are log10(min), log10(mean), log10(max).
The ticks on the colorbar therefore needs to be set with the log values. The ticklabels can be set to any value. However I would think that simply putting something like "mean" on a logarithmic scale may not be too informative.
A particularity is that the minimum of the colorbar is actually log10(min+1). The +1 is due to the log which is negative below 1.
Here is a complete example.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
from mpl_toolkits.basemap import Basemap
from matplotlib import cm
lon = -78.236+np.random.rand(1000)*(-77.072+78.236)
lat = 42.935 + np.random.rand(1000)*(43.349-42.935)
t = 99+np.random.normal(10,20,1000)
t[:50] = np.linspace(48,1302)
roc_data = pd.DataFrame({'Longitude':lon, 'Latitude':lat, "T":t })
#Creating figure window
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111)
#Drawing the basemap
m = Basemap(projection='merc', lat_0=43.12, lon_0=-77.626,
resolution = 'i',llcrnrlon=-78.236,
llcrnrlat=42.935,
urcrnrlon=-77.072,
urcrnrlat=43.349)
m.drawcoastlines()
m.drawcounties(zorder=20, color='red')
m.drawcountries()
m.drawmapboundary()
#plotting the heatmap using hexbin
x, y = m(roc_data['Longitude'].values, roc_data['Latitude'].values)
values = roc_data['T']
m.hexbin(x, y, gridsize = 125, bins = 'log', C = values, cmap = cm.Reds) #bins = 'log',
#Defining minimum, mean and maximum population values
max_p = roc_data['T'].max()
min_p = roc_data['T'].min()
mean_p = roc_data['T'].mean()
print [min_p, mean_p, max_p]
print [np.log10(min_p), np.log10(mean_p), np.log10(max_p)]
#Adding Colorbar
cb = m.colorbar(location = 'bottom', format = '%d', label = 'Population by Census Blocks') #format = '%d',
#setting ticks
cb.set_ticks([np.log10(min_p+1), np.log10(mean_p), np.log10(max_p)])
cb.set_ticklabels(['Min\n({:.1f})'.format(min_p), 'Mean\n({:.1f})'.format(mean_p), 'Max\n({:.1f})'.format(max_p)])
plt.tight_layout()
plt.show()

Resources