Plot a circle on a cartopy mercator projection - python

For a project I need to create a visualization that draws a circle around some locations on a map. The visualization used Cartopy v.0.18.0 to render the map. It uses the GoogleTiles class to fetch and display the tiles in the relevant region, and the add_patch(Patch.Circle(..., transform=ccrs.PlateCarree())) method to draw the circle.
tiles = GoogleTiles()
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(1, 1, 1, projection=tiles.crs)
ax.set_extent((-121.8,-122.55,37.25,37.85))
ax.add_image(tiles, 11)
ax.add_patch(Patch.Circle(xy=[-122.4015173428571, 37.78774634285715], radius = 0.021709041989311614 + 0.005, alpha=0.3, zorder=30, transform=ccrs.PlateCarree()))
plt.show()
However, although I tried several transform objects I either got a ellipse instead of a circle (e.g. using ccrs.PlateCarree()) or no circle at all (e.g. using ccrs.Mercator()).
I found several different solutions online (e.g. Drawing Circles with cartopy in orthographic projection), however, these were not for the Mercator projection and I sadly lack the projection/transformation knowledge to adapt these to my problem.
The only way I was able to produce a circular patch, was when I set the projection parameter on fig.add_subplot to ccrs.PlateCarree(). This, however, distorts the map and the labels become blured, so this is sadly not an acceptable solution.
As the project is due soon, a speedy reply would be much appreciated.

Thanks #swatchai this was the missing hint, so for those intested the code looks like this right now, and it does work! Hooray!
tiles = GoogleTiles()
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(1, 1, 1, projection=tiles.crs)
ax.set_extent((-121.8,-122.55,37.25,37.85))
ax.add_image(tiles, 11)
# The diameter is in degrees in EPSG:4326 coordinates therefore, the degrees have
# to be converted to km. At 37N the degree latitude is 11.0977 km.
ax.tissot(rad_km=(0.021709041989311614 + 0.005) * 11.0977, lons=[-122.4015], lats=[37.7877], alpha=0.3)
plt.show()
When executing the above code the following warning is thrown but it has visible effect on the result:
/opt/conda/lib/python3.8/site-packages/cartopy/mpl/geoaxes.py:761: UserWarning: Approximating coordinate system <cartopy._crs.Geodetic object at 0x7fa4c7529770> with the PlateCarree projection.
warnings.warn('Approximating coordinate system {!r} with the '
So thanks again #swatchai you saved my day!

Related

Is it possible to make a Cartopy projection where north is NOT facing up?

I'm trying to make a plot of the earth in geocentric solar ecliptic coordinates. This is a coordinate system centered at the earth's center and where +x points towards the sun, +z is parallel to the north ecliptic pole (aka, normal to the plane in which the earth orbits the sun), and +y is towards dusk.
To do this, I'm calculating the subsolar point and using that to find the point on the earth's surface (in latitude/longitude) that lines up with the Z axis. Then I'm making an orthographic projection of the Earth using cartopy, centered at the calculated latitude and longitude.
This is the graph I'm creating
I'm centering the map at the correct location. However, the problem is that I can't figure out how to rotate the map in two dimensions left and right. The only keywords the projection takes are central_longitude, central_latitude, and globe. There's nothing to tell it which way to rotate. It seems like cartopy only allows maps to be made so north is facing up, but I need to rotate the map sideways so the sun is facing the right side instead of the bottom.
Here's the code to generate that plot:
fig = plt.figure(figsize=(18,9))
t = dt.datetime(2014,12,30,0,0,0)
subsolar_lat, subsolar_long = sun_pos(t)
# Center at north ecliptic pole (GSE)
proj_long = subsolar_long
proj_lat = 90-abs(subsolar_lat)
proj=ccrs.Orthographic(proj_long, proj_lat)
left, bottom, width, height = [0.25, 0.25, 0.5, 0.5]
ax = fig.add_axes([left, bottom, width, height], projection = proj)
# Make the map look good
ax.gridlines()
ax.set_global()
ax.set_title(f"{t}")
ax.coastlines()
ax.add_feature(cf.OCEAN, alpha=0.2)
ax.add_feature(cf.LAND, alpha=0.5)
fill_dark_side(ax, time=t, color='black', alpha=0.5)
I found this question on StackOverflow which seems to be exactly what I'm trying to do. However, no one answered it, and their edit doesn't really solve my question either.
I've also tried a few different ways to rotate the axis, but none of them seem to work with Cartopy.
Is there a way to apply a transformation to this projection or to rotate an axis that uses Cartopy?
If you want to rotate the whole axis, matpltolib's floating_axes might help?
https://matplotlib.org/stable/gallery/axisartist/demo_floating_axes.html
https://stackoverflow.com/a/21654433/9703451

Matplotlib: plot arbitrary vectors on polar axes

There are previous questions about quiver plots on polar axes in matplotlib, however they concern vector fields. I'm interested in drawing arbitrary vectors on polar axes. If there is a genuine duplicate, please link it.
I'm writing some software which concerns a circular world. I'm plotting an agent's trajectory from the centre of a circular arena to the edge. This is visualised by drawing a vector from the centre of the circle to the edge. I'm trying to use matplotlib's quiver plot to plot vectors on a set of polar axes. Here's a minimum working example:
import matplotlib.pyplot as plt
import numpy as np
if __name__ == '__main__':
fig = plt.figure()
ax = fig.add_subplot(111, projection='polar')
# Plot origin (agent's start point)
ax.plot(0, 0, color='black', marker='o', markersize=5)
# Plot agent's path
ax.quiver((0, 0), (0, 1), color='black')
# Example of where (0, 1) should be
ax.plot(0, 1, color='black', marker='o', markersize=5)
# Plot configuration
ax.set_rticks([])
ax.set_rmin(0)
ax.set_rmax(1)
ax.set_thetalim(-np.pi, np.pi)
ax.set_xticks(np.linspace(np.pi, -np.pi, 4, endpoint=False))
ax.grid(False)
ax.set_theta_direction(-1)
ax.set_theta_zero_location("N")
plt.show()
If you run the code, you get this plot
The plot shows the origin plotted correctly, an example point at (0, 1) to show where the vector should end, then the vector itself which appears far too short (though the direction is correct). From the docs, I understand that quiver takes cartesian coordinates (x,y) denoting the start point of the vector and (u,v) denoting the vector's direction. In my previous experience with quiver (u,v) essentially denotes where the vector's tip will be, so in this case we'd expect the vector to be drawn from (0,0) to (0,1) which isn't the case and I don't know why.
In short, I want to be able to draw arbitrary vectors on a set of polar axes and quiver isn't working as I expected. Three questions:
Is my code actually sensible given my goal? I want to draw a unit vector from the origin to the edge of the polar plot.
Am I completely misunderstanding how to use quiver?
How can I draw arbitrary vectors on polar axes in matplotlib? I know about arrow and I'm going to give that a try though initial attempts were unsuccessful.
Short of using a standard plot and just defining my own polar system within it I'm completely stumped.
You did not specify u and v in ax.quiver(x,y,u,v). To make sure the arrow is 1 unit long you will need to set the scale und units as well.
ax.quiver(0,0,0,1, color='black', angles="xy", scale_units='xy', scale=1.)

Python: Matplotlib Surface_plot

I'm trying to Plot a high resolution surface_plot, but I would also really like some nice grid lines on top of it. If i use the gridlines in the same argument
ax.plot_surface(x_itp, y_itp, z_itp, rstride=1, cstride=1, facecolors=facecolors, linewidth=0.1)
I get a LOT of grid lines. If I, on the other hand, set "rstride" and "cstride" to higher values, my sphere will become ugly.
I then tried to smash a
ax.plot_wireframe(x_itp, y_itp, z_itp, rstride=3, cstride=3)
in afterwards, but it just lies on top of the colored sphere.. meaning that I can see the backside of the wireframe and then the surface_plot behind it all.
Have anyone tried this?
Another option was to use "Basemap" which can create a nice grid, but then I will have to adapt my colored surface to that.?!
My plot looks like this:
If I add edges to the map with a higher "rstride" and "cstride" then it looks like this:
code :
norm = plt.Normalize()
facecolors = plt.cm.jet(norm(d_itp))
# surface plot
fig, ax = plt.subplots(1, 1, subplot_kw={'projection':'3d', 'aspect':'equal'})
ax.hold(True)
surf = ax.plot_surface(x_itp, y_itp, z_itp, rstride=4, cstride=4, facecolors=facecolors)
surf.set_edgecolors("black")
I want to show the \theta and \phi angles around the sphere.. maybe with 30 degrees apart.
Cheers!
Morten
It looks like you may need to use basemap. With plot_surface() you can either have high resolution plot or low resolution with good grid on top. But not both. I just made a simple basemap with contour plot. I think you can do easily apply pcolor on it. Just do not draw continent and country boundary. Then, you have a nice sphere which gives more control. After making your plot, you can easily add grid on it.
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
map = Basemap(projection='ortho',lat_0=45,lon_0=-150)
map.drawmapboundary(fill_color='aquamarine')
map.drawmeridians(np.arange(0,360,30)) # grid every 30 deg
map.drawparallels(np.arange(-90,90,30))
nlats = 73; nlons = 145; delta = 2.*np.pi/(nlons-1)
lats = (0.5*np.pi-delta*np.indices((nlats,nlons))[0,:,:])
lons = (delta*np.indices((nlats,nlons))[1,:,:])
wave = 0.6*(np.sin(2.*lats)**6*np.cos(4.*lons))
mean = 0.5*np.cos(2.*lats)*((np.sin(2.*lats))**2 + 2.)
x, y = map(lons*180./np.pi, lats*180./np.pi) # projection from lat, lon to sphere
cs = map.contour(x,y,wave+mean,15,linewidths=1.5) # contour data. You can use pcolor() for your project
plt.title('test1')
plt.show()

Setting data limits in matplotlib basemap

I'm trying to wrap my head around matplotlib's basemap API. What I'd like to do is plot latitude and longitude data of a very small area (couple of km in either direction) on a cylindrical projection.
The problem is, I'm unable to understand how llcrnrlon, llcrnrlat, urcrnrlon and urcrnrlat parameters to the Basemap constructor work.
As far as I understand it, the llcrnrlon is the west-most longitude and llcrnrlat is the south-most latitude and urcrnrlon and urcrnrlat are the east-most and north-most longitude and latitude respectively.
In all these cases, given a set of coordinates, the (numerically) smallest longitudes are the west-most and the smallest latitudes are the south-most and vice-versa. Is this understanding correct?
I'm able to get the plot working by setting xlim and ylim on the underlying Axes object, but using the same values in the basemap constructor seem to push my data off the plot.
EDIT: See code below for a reproduction of the problem:
from matplotlib import pyplot
from mpl_toolkits import basemap
import random
lat_bounds = 52.063443, 52.072587
long_bounds = 1.010408, 1.024502
ax1 = pyplot.subplot(121)
ax2 = pyplot.subplot(122)
ax1.set_title('With ll* rr*')
ax2.set_title('With default values')
my_map1 = basemap.Basemap(projection='cyl', llcrnrlat=lat_bounds[0], llcrnrlon=long_bounds[0],
urcrnrlat=lat_bounds[1], urcrnrlon=long_bounds[1], ax=ax1)
my_map2 = basemap.Basemap(projection='cyl', ax=ax2)
data_lats = [random.uniform(*lat_bounds) for i in xrange(50)]
data_lons = [random.uniform(*long_bounds) for i in xrange(50)]
my_map1.plot(data_lats, data_lons)
my_map2.plot(data_lats, data_lons)
pyplot.show()
In the figures below, the right hand side image is made by using Basemap(projection='cyl') and the left hand side image is made by using Basemap(projection='cyl', llcrnrlat=lat_bounds[0], llcrnrlon=long_bounds[0], urcrnrlat=lat_bounds[1], urcrnrlon=long_bounds[1], ax=ax1)
Notice the dot in the right hand side image, which when zoomed using the matplotlib toolbar becomes the second image.
The problem in your example code is that you are passing the arguments to Basemap.plot() the wrong way around. The arguments to Basemap.plot are exactly the same as those to matplotlib.pyplot.plot, i.e.:
plot(x,y,*args,**kwargs)
In cylindrical coordinates, longitude is the x-coordinate and latitude is the y-coordinate, so you should do mymap1.plot(data_lons, data_lats). The reason it seemed to work in your second example is that longitudes of ~52 and latitudes of ~1 make sense. The points were plotted, but somewhere far away from your domain. If you panned the window of ax1 far enough, you would have seen them (which is the same as doing ax.set_xlim(lat_bounds) and ax.set_ylim(lon_bounds)).

pcolormesh () and contourf() do not work

Esteemed experts, am back with a problem I presented about two months ago, I have been working on it since with no success. This concerns superposition of contours on a basemap. I have looked at numerous examples on this, e.g. the example here: http://nbviewer.ipython.org/github/Unidata/tds-python-workshop/blob/master/matplotlib.ipynb
A sample of the data is on one of my previous posts, here: Contours with map overlay on irregular grid in python.
After preparing the data, here are plotting methods:
# Setting the plot size and text
fig = plt.figure(figsize=(10,8))
lev = [15, 20, 25, 30, 35, 40,45]
norm1 = colors.BoundaryNorm(lev, 256)
# Draw filled contours
# 1. pcolor does not show the filled contours
#cs = plt.pcolor(x,y,zi, cmap = cm.jet, norm = norm1)
# 2. pcolormesh does not show the filled contours
#cs = plt.pcolormesh(x,y,zi, shading = "flat", cmap=cmap)
# 3. contourf does not show the filled contours
#cs = plt.contourf(xi, yi, zi) #, levels=np.linspace(zi.min(),zi.max(),5))
cs = plt.contourf(xi, yi, zi, cmap = cm.jet, levels = lev, norm = norm1)
# 4. Draw line contours with contour()
#cs = m.contour(x,y,zi,linewidths=1.2) # This works
plt.scatter(data.Lon, data.Lat, c=data.Z, s=100,
vmin=zi.min(), vmax=zi.max()) # Does not work at all
# Color bar
#cbar = m.colorbar(fig,location='right',pad="10%")
fig.colorbar(cs)
# Plot a title
plt.figtext(.5,.05,'Figure 1. Mean Rainfall Onset Dates',fontsize=12,ha='center')
plt.show()
Sorry I am not able to post the plot examples, but:
pcolor, pcolormesh and contourf above all give a map without any filled contours but with a colorbar
the above plots without the map object give filled contours including scatter plot (without map background)
contour gives the map with contour lines superposed:
I am baffled because this is an example copy-pasted from the example in the link quoted above.
Any hint as to a possible cause of the problem would be appreciated
Zilore Mumba
you need to use the basemap to plot the contours vs using matplotlib.pyplot. see my example for some of my code.
#Set basemap and grid
px,py=n.meshgrid(x,y)
m=Basemap(projection='merc',llcrnrlat=20,urcrnrlat=55,
llcrnrlon=230,urcrnrlon=305,resolution='l')
X,Y=m(px,py)
#Draw Latitude Lines
#labels[left,right,top,bottom] 1=True 0=False
parallels = n.arange(0.,90,10.)
m.drawparallels(parallels,labels=[1,0,0,0],fontsize=10,linewidth=0.)
# Draw Longitude Lines
#labels[left,right,top,bottom] 1=True 0=False
meridians = n.arange(180.,360.,10.)
m.drawmeridians(meridians,labels=[0,0,0,1],fontsize=10,linewidth=0)
#Draw Map
m.drawcoastlines()
m.drawcountries()
m.drawstates()
m.fillcontinents(color='grey',alpha=0.1,lake_color='aqua')
#Plot Contour lines and fill
levels=[5.0,5.1,5.2,5.3,5.4,5.6,5.7,5.8,5.9,6.0]
cs=m.contourf(px,py,thickness,levels,cmap=p.cm.RdBu,latlon=True,extend='both')
cs2=m.contour(px,py,thickness,levels,latlon=True,colors='k')
#Plot Streamlines
m.streamplot(px,py,U,V,latlon=True,color='k')
#Add Colorbar
cbar = p.colorbar(cs)
cbar.add_lines(cs2)
cbar.ax.set_ylabel('1000 hPa - 500 hPa Thickness (km)')
#Title
p.title('Geostrophic Winds with Geopotential Thickness')
p.show()
Without knowing how your data look like it's a bit difficult to answer your question, but I'll try anyway. You might want to grid your data, for example, with an histogram, then contour the results.
For example, if you're interested in plotting 2D contours of points that have coordinates (x,y) and a third property (z) you want to use for the colors, you might give this a try
from numpy import *
H=histogram2d(x,y,weights=z)
contourf(H[0].T,origin='lower')
But, like I said, it's hard to understand what you're looking for if you're not giving details about your data. Have a look at the matplotlib guide for more examples http://matplotlib.org/examples/pylab_examples/contourf_demo.html

Categories

Resources