Is there a way to improve the rendering of this interactive matplotlib/cartopy script ?
Please install first ipympl
https://github.com/matplotlib/ipympl
I haven't found how to change the central longitude and latitude without instantiate a new figure.
This script is missing fluidity when clicking with left mouse button and choose the new center of the projection. I am looking for a trackball behaviour as in https://threejs.org/examples/misc_controls_trackball.
Any help/suggestion is welcomed.
%matplotlib widget
import numpy as np
import matplotlib.pyplot as plt
import cartopy
import cartopy.crs as ccrs
fig = plt.figure(figsize=(10,6), layout='constrained')
proj0 = ccrs.PlateCarree()
proj1 = ccrs.Orthographic(0, 80)
ax1 = fig.add_subplot(1, 1, 1, projection=proj1)
ax1.coastlines()
ax1.gridlines(xlocs=np.arange(-180,180,10), ylocs=np.arange(-80,90,10))
ax1.set_global()
def onpress(event):
global proj1
if event.button == 1:
lon, lat = proj0.transform_point(event.xdata, event.ydata, src_crs=proj1)
proj1 = ccrs.Orthographic(lon, lat)
ax1 = fig.add_subplot(1, 1, 1, projection=proj1)
ax1.coastlines()
ax1.gridlines(xlocs=np.arange(-180,180,10), ylocs=np.arange(-80,90,10))
ax1.set_global()
plt.draw()
fig.canvas.mpl_connect('button_press_event', onpress)
plt.show()
Related
I am making a set of figures with subplots in Jupyter Notebook using matplotlib and geopandas. The top plots (A & B) have geospatial data and use various basemaps (aerial imagery, shaded relief, etc.).
How can I rotate the top two plots 90-degrees, so that they are elongated?
(I will need to redo gridspec layout of course, but that is easy; what I don't know how to do is: rotate the plots but keep the geographic information for basemap plotting.)
Repeatable code is below.
import pandas as pd
import geopandas as gpd
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import contextily as ctx
from shapely.geometry import Point
plt.style.use('seaborn-whitegrid')
### DUMMY DATA
long, lat = [(-118.155, -118.051, -118.08), (38.89, 39.512, 39.1)]
q, t = [(0, 70500, 21000), (0, 8000, -1200)]
df = pd.DataFrame(list(zip(q, t, lat, long)), columns =['q', 't', 'lat', 'long'])
gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df['long'], df['lat']))
gdf.crs = "EPSG:4326"
### PLOTTING
fig = plt.figure(figsize=(10,7.5), constrained_layout=True)
gs = fig.add_gridspec(3, 2)
ax1 = fig.add_subplot(gs[0:2, 0])
ax2 = fig.add_subplot(gs[0:2, 1], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[-1, :])
### PlotA
gdf.plot(ax = ax1)
ctx.add_basemap(ax1, crs='epsg:4326', source=ctx.providers.Esri.WorldShadedRelief)
ax1.set_aspect('equal')
ax1.set_title('Plot-A')
ax1.tick_params('x', labelrotation=90)
### PlotB
gdf.plot(ax = ax2)
ctx.add_basemap(ax2, crs='epsg:4326', source=ctx.providers.Esri.WorldImagery, alpha=0.5)
ax2.set_aspect('equal')
ax2.set_title('Plot-B')
ax2.tick_params('x', labelrotation=90)
### PlotC
ax3.scatter(df.q, df.t)
ax3.set_aspect('equal')
ax3.set_title('Plot-C')
ax3.set_xlabel('q')
ax3.set_ylabel('t')
I've seen a few other questions on this topic, but the library has changed enough that the answers to those no longer seem to apply.
Rasterio used to include an example for plotting a rasterio raster on a Cartopy GeoAxes. The example went roughly like this:
import matplotlib.pyplot as plt
import rasterio
from rasterio import plot
import cartopy
import cartopy.crs as ccrs
world = rasterio.open(r"../tests/data/world.rgb.tif")
fig = plt.figure(figsize=(20, 12))
ax = plt.axes(projection=ccrs.InterruptedGoodeHomolosine())
ax.set_global()
plot.show(world, origin='upper', transform=ccrs.PlateCarree(), interpolation=None, ax=ax)
ax.coastlines()
ax.add_feature(cartopy.feature.BORDERS)
However, this code no longer draws the raster. Instead, I get something like this:
It should look like this:
When I asked about this in the rasterio issues tracker, they told me the example was deprecated (and deleted the example). Still, I wonder if there's some way to do what I'm trying to do. Can anyone point me in the right direction?
I think you may want to read the data to a numpy.ndarray and plot it using ax.imshow, where ax is your cartopy.GeoAxes (as you have it already). I offer an example of what I mean, below.
I clipped a small chunk of Landsat surface temperature and some agricultural fields for this example. Get them on this drive link.
Note fields are in WGS 84 (epsg 4326), Landsat image is in UTM Zone 12 (epsg 32612), and I want my map in Lambert Conformal Conic. Cartopy makes this easy.
import numpy as np
import cartopy.crs as ccrs
from cartopy.io.shapereader import Reader
from cartopy.feature import ShapelyFeature
import rasterio
import matplotlib.pyplot as plt
def cartopy_example(raster, shapefile):
with rasterio.open(raster, 'r') as src:
raster_crs = src.crs
left, bottom, right, top = src.bounds
landsat = src.read()[0, :, :]
landsat = np.ma.masked_where(landsat <= 0,
landsat,
copy=True)
landsat = (landsat - np.min(landsat)) / (np.max(landsat) - np.min(landsat))
proj = ccrs.LambertConformal(central_latitude=40,
central_longitude=-110)
fig = plt.figure(figsize=(20, 16))
ax = plt.axes(projection=proj)
ax.set_extent([-110.8, -110.4, 45.3, 45.6], crs=ccrs.PlateCarree())
shape_feature = ShapelyFeature(Reader(shapefile).geometries(),
ccrs.PlateCarree(), edgecolor='blue')
ax.add_feature(shape_feature, facecolor='none')
ax.imshow(landsat, transform=ccrs.UTM(raster_crs['zone']),
cmap='inferno',
extent=(left, right, bottom, top))
plt.savefig('surface_temp.png')
feature_source = 'fields.shp'
raster_source = 'surface_temperature_32612.tif'
cartopy_example(raster_source, feature_source)
The trick with Cartopy is to remember to use the projection keyword for your axes object, as this renders the map in a nice projection of your choice (LCC in my case). Use transform keyword to indicate what projection system your data is in, so Cartopy knows how to render it.
No need of rasterio. Get a bluemarble image, then plot it.
Here is the working code:
import cartopy
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig = plt.figure(figsize=(10, 5))
ax = plt.axes(projection=ccrs.InterruptedGoodeHomolosine())
# source of the image:
# https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73909/world.topo.bathy.200412.3x5400x2700.jpg
fname = "./world.topo.bathy.200412.3x5400x2700.jpg"
img_origin = 'lower'
img = plt.imread(fname)
img = img[::-1]
ax.imshow(img, origin=img_origin, transform=ccrs.PlateCarree(), extent=[-180, 180, -90, 90])
ax.coastlines()
ax.add_feature(cartopy.feature.BORDERS)
ax.set_global()
plt.show()
The output plot:
I'm trying to graph features of a data-set one by one by, via iteration.
So I want the graph to continuously update as I proceed through the loop.
I refered to this thread,real-time plotting in while loop with matplotlib but the answers are all over the place, and despite incorporating some of their suggestions as shown below, I still can't seem to get the code working. I'm using Jupyter Notebook.
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
colors = ["darkblue", "darkgreen"]
f, (ax1, ax2) = plt.subplots(1, 2, sharey=True, sharex = True)
for i in range(X.shape[-1]-1):
idx = np.where(y == 1)[0]
ax1.scatter(X[idx, i], X[idx, i+1], color=colors[0], label=1)
idx = np.where(y == 0)[0]
ax2.scatter(X[idx, i], X[idx, i+1], color=colors[1], label=0)
plt.draw()
plt.pause(0.0001)
Any suggestions?
Thank you.
This is an example for real-time plotting in a Jupyter Notebook
%matplotlib inline
%load_ext autoreload #Reload all modules every time before executing the Python code typed.
%autoreload 2
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
import time
colors = ["darkblue", "darkgreen"]
# initialise the graph and settings
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = fig.add_subplot(211)
plt.ion() # interactive mode
fig.show()
fig.canvas.draw() # matplotlib canvas drawing
# plotting loop
for i in range(X.shape[-1]-1):
ax1.clear()
ax2.clear()
idx = np.where(y == 1)[0]
ax1.scatter(X[idx, i], X[idx, i+1], color=colors[0], label=1)
idx = np.where(y == 0)[0]
ax2.scatter(X[idx, i], X[idx, i+1], color=colors[1], label=0)
fig.canvas.draw() # draw
time.sleep(0.5) # sleep
For an animation you need an interactive backend. %matplotlib inline is no interactive backend (it essentially shows a printed version of the figure).
You may decide not to run you code in jupyter but as a script. In this case you would need to put plt.ion() to put interactive mode on.
Another option would be to use a FuncAnimation, as e.g in this example. To run such a FuncAnimation in Jupyter you will still need some interactive backend, either %matplotlib tk or %matplotlib notebook.
From matplotlib 2.1 on, we can also create an animation using JavaScript.
from IPython.display import HTML
HTML(ani.to_jshtml())
Some complete example:
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
ax.axis([0,2*np.pi,-1,1])
l, = ax.plot([],[])
def animate(i):
l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
from IPython.display import HTML
HTML(ani.to_jshtml())
I would like to produce orthographic (polar) plots of Antarctica that are 'zoomed' with respect to the default settings. By default I get this:
Antarctica polar
The following script produced this.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.Orthographic(central_longitude=0.0, central_latitude=-90.))
ax.stock_img()
plt.show()
My best attempt to tell Cartopy 'limit the latitude to 60S to 90S' was:
ax.set_extent([-180,180,-60,-90], ccrs.PlateCarree())
unfortunately it does not give the desired result. Any ideas? Thanks in advance.
I'm not sure I fully understand what you're trying to do. Your example looks like a bounding box that was defined, but you'd like it rounded like your first example?
cartopy documentation has an example of this http://scitools.org.uk/cartopy/docs/latest/examples/always_circular_stereo.html:
import matplotlib.path as mpath
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
import cartopy.feature
def main():
fig = plt.figure(figsize=[10, 5])
ax1 = plt.subplot(1, 2, 1, projection=ccrs.SouthPolarStereo())
ax2 = plt.subplot(1, 2, 2, projection=ccrs.SouthPolarStereo(),
sharex=ax1, sharey=ax1)
fig.subplots_adjust(bottom=0.05, top=0.95,
left=0.04, right=0.95, wspace=0.02)
# Limit the map to -60 degrees latitude and below.
ax1.set_extent([-180, 180, -90, -60], ccrs.PlateCarree())
ax1.add_feature(cartopy.feature.LAND)
ax1.add_feature(cartopy.feature.OCEAN)
ax1.gridlines()
ax2.gridlines()
ax2.add_feature(cartopy.feature.LAND)
ax2.add_feature(cartopy.feature.OCEAN)
# Compute a circle in axes coordinates, which we can use as a boundary
# for the map. We can pan/zoom as much as we like - the boundary will be
# permanently circular.
theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.5
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpath.Path(verts * radius + center)
ax2.set_boundary(circle, transform=ax2.transAxes)
plt.show()
if __name__ == '__main__':
main()
I received the following email and wanted to make sure the answer to this question was available to everybody:
Hi,
I would like to setup a simple latitude longitude map, using cartopy, which crosses the dateline and shows east Asia on the left hand side with the west of North America on the right. The following google map is roughly what I am after:
https://maps.google.co.uk/?ll=56.559482,-175.253906&spn=47.333523,133.066406&t=m&z=4
Can this be done with Cartopy?
Good question. This is probably something which will come up time-and-time again, so I will go through this step-by-step before actually answering your specific question. For future reference, the following examples were written with cartopy v0.5.
Firstly, it is important to note that the default "latitude longitude" (or more technically PlateCarree) projection works in the forward range of -180 to 180. This means that you cannot plot the standard PlateCarree projection beyond this. There are several good reasons for this, most of which boil down to the fact that cartopy would have to do a lot more work when projecting both vectors and rasters (simple coastlines for example). Unfortunately the plot you are trying to produce requires precisely this functionality. To put this limitation into pictures, the default PlateCarree projection looks like:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
proj = ccrs.PlateCarree(central_longitude=0)
ax1 = plt.axes(projection=proj)
ax1.stock_img()
plt.title('Global')
plt.show()
Any single rectangle that you can draw on this map can legally be a zoomed in area (there is some slightly more advanced code in here, but the picture is worth a 1000 words):
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import shapely.geometry as sgeom
box = sgeom.box(minx=-90, maxx=45, miny=15, maxy=70)
x0, y0, x1, y1 = box.bounds
proj = ccrs.PlateCarree(central_longitude=0)
ax1 = plt.subplot(211, projection=proj)
ax1.stock_img()
ax1.add_geometries([box], proj, facecolor='coral',
edgecolor='black', alpha=0.5)
plt.title('Global')
ax2 = plt.subplot(212, projection=proj)
ax2.stock_img()
ax2.set_extent([x0, x1, y0, y1], proj)
plt.title('Zoomed in area')
plt.show()
Unfortunately the plot you want would require 2 rectangles with this projection:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import shapely.geometry as sgeom
box = sgeom.box(minx=120, maxx=260, miny=15, maxy=80)
proj = ccrs.PlateCarree(central_longitude=0)
ax1 = plt.axes(projection=proj)
ax1.stock_img()
ax1.add_geometries([box], proj, facecolor='coral',
edgecolor='black', alpha=0.5)
plt.title('Target area')
plt.show()
Hence it is not possible to draw a map that crosses the dateline using the standard PlateCarree definition. Instead we could change the PlateCarree definition's central longitude to allow a single box to be drawn of the area we are targeting:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import shapely.geometry as sgeom
box = sgeom.box(minx=120, maxx=260, miny=15, maxy=80)
x0, y0, x1, y1 = box.bounds
proj = ccrs.PlateCarree(central_longitude=180)
box_proj = ccrs.PlateCarree(central_longitude=0)
ax1 = plt.subplot(211, projection=proj)
ax1.stock_img()
ax1.add_geometries([box], box_proj, facecolor='coral',
edgecolor='black', alpha=0.5)
plt.title('Global')
ax2 = plt.subplot(212, projection=proj)
ax2.stock_img()
ax2.set_extent([x0, x1, y0, y1], box_proj)
plt.title('Zoomed in area')
plt.show()
Hopefully that shows you what it is you have to do to achieve your target map, the code above might be a little complex to achieve your goal, so to simplify slightly, the code I would write to produce the plot you want would be something like:
import cartopy.feature
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=180))
ax.set_extent([120, 260, 15, 80], crs=ccrs.PlateCarree())
# add some features to make the map a little more polished
ax.add_feature(cartopy.feature.LAND)
ax.add_feature(cartopy.feature.OCEAN)
ax.coastlines('50m')
plt.show()
This was a long answer, hopefully I have not only answered the question, but made some of the more complex details of map production and cartopy more clear to help smooth any future problems you may have.
Cheers,
For more details of above benjimin's comment,
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
from matplotlib.ticker import AutoMinorLocator, FixedLocator, MultipleLocator
def map_common(ax1,gl_loc=[True,True,False,True],gl_lon_info=range(-180,180,60),gl_dlat=30):
ax1.coastlines(color='silver',linewidth=1.)
gl = ax1.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
linewidth=0.6, color='gray', alpha=0.5, linestyle='--')
gl.ylabels_left = gl_loc[0]
gl.ylabels_right = gl_loc[1]
gl.xlabels_top = gl_loc[2]
gl.xlabels_bottom = gl_loc[3]
gl.xlocator = FixedLocator(gl_lon_info)
gl.ylocator = MultipleLocator(gl_dlat)
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
gl.xlabel_style = {'size': 11, 'color': 'k'}
gl.ylabel_style = {'size': 11, 'color': 'k'}
lon_boundary=np.arange(-240,-60,1.)
lat_boundary=np.arange(15,75,1.)
data=np.ones([lat_boundary.shape[0]-1,lon_boundary.shape[0]-1]) ## Data dimension is 1 less than boundaries
data=data*lat_boundary[:-1,None]
lon_offset=-150 ##
x,y=np.meshgrid(lon_boundary-lon_offset,lat_boundary)
fig=plt.figure()
fig.set_size_inches(7.5,5) ## (xsize, ysize)
ax1=fig.add_subplot(111,projection=ccrs.PlateCarree(central_longitude=lon_offset))
ax1.set_extent([-250,-50,10,80],crs=ccrs.PlateCarree())
props=dict(vmin=0,vmax=90,cmap=plt.cm.get_cmap('bone'),alpha=0.8)
cs=ax1.pcolormesh(x,y,data,**props)
ax1.set_title('Lon_Offset=-90')
map_common(ax1,gl_lon_info=[-180,-120,-60,120,],gl_dlat=15)
fnout='./map_over_dateline.png'
#plt.show()
plt.savefig(fnout,bbox_inches='tight',dpi=150)
Output of this program