I want to make simple static maps for use in journal papers. I work in the Arctic and I make a lot of map images that show equipment layout, vessel tracks, and source and receiver locations. I can't do it in CARTOPY. For example, 1 deg of latitude (74 to 75N) and 2.5 degrees of longitude (-92.5 to -90.0) at a mid-latitude of say 74.5N. You can't get the coastline to work properly. The map is often empty, but it should show a portion of the coastline of Devon Island, NU. If I make the plot a bigger region (something like 30deg by 30deg), it works, but you will see that the coordinates displayed in the graph window don't line up properly. The X, Y values match the graph axes, but the lat, lon values are in parentheses are shifted. In the worst case, the lat, lons come out as 0.00n deg or even nearly half a world away.
I've tried multiple ways of invoking the extent. Different projections. Nothing seems to work.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
from cartopy.feature import NaturalEarthFeature
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import numpy as np
import matplotlib.ticker as mticker
# the limits of the map
# extent = (-100., -50.0, 60.0, 80.0) # try this, you'll get a shifted plot on northern Alaska
extent = (-92.5, -90.0, 74.0, 75.0) # try this, you'll get a blank plot. axes shifted badly.
# set the projection type
c_lon, c_lat = (extent[0] + extent[1])/2., (extent[2] + extent[3])/2.
proj = ccrs.PlateCarree(central_longitude=c_lon)
# proj = ccrs.Mercator(c_lon) # I've tried these as well
# proj = ccrs.Orthographic(c_lon, c_lat)
gax = plt.axes(projection=proj)
gax.set_extent(extent)
gax.set_ylim((extent[2], extent[3]))
gax.set_xlim((extent[0], extent[1]))
# now add the coastline. This only works for big maps. Small regions fail.
coastline_10m = NaturalEarthFeature(category='physical', name='coastline', \
facecolor='none', scale='10m')
gax.add_feature(coastline_10m, edgecolor='gray')
# draw a grid with labelled lat and lon. Suppress ticklabels on the top and right.
gl = gax.gridlines(crs=proj, draw_labels=True) # only works with PlateCarree()
gl.xlabels_top = None
gl.ylabels_right = False
# now we put labels on the X and Y axes. You have to move these around manually.
gax.text(-0.2, 0.55, 'Latitude [Deg]', va='bottom', ha='center',
rotation='vertical', rotation_mode='anchor',
transform=gax.transAxes)
gax.text(0.5, -0.12, 'Longitude [Deg]', va='bottom', ha='center',
rotation='horizontal', rotation_mode='anchor',
transform=gax.transAxes)
# approximately correct for the aspect ratio
plt.gca().set_aspect(1.0/(np.cos(np.pi*(extent[2] + extent[3])/(2.*180.))))
plt.show()
macOS Catalina, Anaconda python3.8.3, IPython 7.19.0, cartopy 0.17 (the highest version supported. Anaconda says 0.18, but it installs 0.17).
Some obvious errors:
set_extent() needs option crs=ccrs.PlateCarree()
set_xlim() and set_ylim need data (map projection) coordinates.
And set_xlim() and set_ylim change what you have done with set_extent() in the previous line. They must be used correctly. Most of your cases, they should not be used.
Related
I have a dataframe with locations given as longitude and latitude coordinates (in degrees). Those locations are around New York. Therefore I setup a Basemap in Python that nicely shows all those locations. Works fine!
But: the map is drawn inline and it's very tiny. How can I force that figure to be let's say 3 times larger (zoom=3).
Here's the code. The data is from the Kaggle Two Sigma Rental Listing challenge.
%matplotlib inline
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
# New York Central Park
# Longitude: -73.968285
# Latitude: 40.785091
m = Basemap(projection='merc',llcrnrlat=40,urcrnrlat=42,\
llcrnrlon=-75, urcrnrlon=-72, resolution='i', area_thresh=50, lat_0=40.78, lon_0=-73.96)
m.drawmapboundary()
m.drawcoastlines(color='black', linewidth=0.4)
m.drawrivers(color='blue')
m.fillcontinents(color='lightgray')
lons = df['longitude'].values
lats = df['latitude'].values
x,y = m(lons, lats)
# r = red; o = circle marker (see: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.plot)
m.plot(x, y, 'ro', markersize=4)
plt.show()
normally it would be as simple as:
plt.figure(figsize=(20,10))
How do you change the size of figures drawn with matplotlib?
but there are some other options too, see:
How to maximize a plt.show() window using Python
also to get the current size (for the purpose of "zoom")
How to get matplotlib figure size
regarding the specific issue:
the figure is inline inside a Jupyter notebook
before creating or plotting the map/figure:
import matplotlib
matplotlib.rcParams['figure.figsize'] = (30,30)
I am trying to add hatching (like dots, hashes, .. ) over contour map. Such hatching could represent the only the statistically significant contours, or contours with certain criteria. Like the following image on nature article (second and third plot) http://www.nature.com/articles/srep16853/figures/3.
The following code show plot of precipitation from NOAA data available for download at.
import numpy as np
import sys
import netCDF4 as nc
import matplotlib.pyplot as plt
import matplotlib.mlab as m
import mpl_toolkits.basemap as bm
import os
sys.path.insert(0, '../');import py4met as sm;reload(sm)
#- Reading data for a timeslice, latitude, and longitude:
diri_output="./"
diri="./"
tmp_file = nc.Dataset(diri+"precip.mon.mean.nc","r")
print(tmp_file.variables)
p_pre = tmp_file.variables['precip']
lat = tmp_file.variables['lat'][:]
lon = tmp_file.variables['lon'][:]
time = tmp_file.variables['time']
tmp_file.close
lat1=np.min(lat)
lat2=np.max(lat)
lon1=np.min(lon)
lon2=np.max(lon)
[lonall, latall] = np.meshgrid(lon[:], lat[:])
plt.figure(num=None, figsize=(8+4, 6+4), dpi=80, facecolor='w', edgecolor='k')
mapproj = bm.Basemap(projection='cyl',llcrnrlat=lat1, llcrnrlon=lon1,urcrnrlat=lat2, urcrnrlon=lon2,resolution='l')
mapproj.drawcoastlines()
mapproj.drawmapboundary(fill_color='white')
mapproj.drawcountries()
x, y = mapproj(lonall, latall)
plt.contourf(x,y,p_pre[240,:,:],cmap=plt.cm.GnBu)
plt.colorbar(orientation='horizontal',pad=0.05,shrink=0.6)
plt.title("title")
xx,yy=np.where(p_pre[240,:,:] >= 20)
sig=np.copy(p_pre[0,:,:])
sig[:,:]=1
sig[xx,yy]=0
#plt.contourf(x,y,sig,hatches=['.'])
plt.show()
I want to hatch all contours above 20 mm, so I used the above command
plt.contourf(x,y,sig,hatches=['.'])
but it didn’t work (it make dotes everywhere on the map and not only contours with specific criteria), thus I commented it.
Any ideas.
See this matplotlib example page for a demo of how hatches can be used with contourf. Of particular relevance to your problem is that (1) there is a keyword level that contourf takes in order to establish the bounds of what values get colored and/or hatched and (2) an empty string "" can be used for the absence of a hatch.
So, instead of the plt.contourf line you have commented out try
levels = [p_pre[240,:,:].min(), 20, p_pre[240,:,:].max()]
plt.contourf(x, y, p_pre[240,:,:], levels=levels, hatches=["", "."], alpha=0)
I had trouble recreating your plot from the data you linked to, so I generated some random data to make the image below using the same principles I describe above.
I am trying to create a color wheel in Python, preferably using Matplotlib. The following works OK:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
xval = np.arange(0, 2*pi, 0.01)
yval = np.ones_like(xval)
colormap = plt.get_cmap('hsv')
norm = mpl.colors.Normalize(0.0, 2*np.pi)
ax = plt.subplot(1, 1, 1, polar=True)
ax.scatter(xval, yval, c=xval, s=300, cmap=colormap, norm=norm, linewidths=0)
ax.set_yticks([])
However, this attempt has two serious drawbacks.
First, when saving the resulting figure as a vector (figure_1.svg), the color wheel consists (as expected) of 621 different shapes, corresponding to the different (x,y) values being plotted. Although the result looks like a circle, it isn't really. I would greatly prefer to use an actual circle, defined by a few path points and Bezier curves between them, as in e.g. matplotlib.patches.Circle. This seems to me the 'proper' way of doing it, and the result would look nicer (no banding, better gradient, better anti-aliasing).
Second (relatedly), the final plotted markers (the last few before 2*pi) overlap the first few. It's very hard to see in the pixel rendering, but if you zoom in on the vector-based rendering you can clearly see the last disc overlap the first few.
I tried using different markers (. or |), but none of them go around the second issue.
Bottom line: can I draw a circle in Python/Matplotlib which is defined in the proper vector/Bezier curve way, and which has an edge color defined according to a colormap (or, failing that, an arbitrary color gradient)?
One way I have found is to produce a colormap and then project it onto a polar axis. Here is a working example - it includes a nasty hack, though (clearly commented). I'm sure there's a way to either adjust limits or (harder) write your own Transform to get around it, but I haven't quite managed that yet. I thought the bounds on the call to Normalize would do that, but apparently not.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
import matplotlib as mpl
fig = plt.figure()
display_axes = fig.add_axes([0.1,0.1,0.8,0.8], projection='polar')
display_axes._direction = 2*np.pi ## This is a nasty hack - using the hidden field to
## multiply the values such that 1 become 2*pi
## this field is supposed to take values 1 or -1 only!!
norm = mpl.colors.Normalize(0.0, 2*np.pi)
# Plot the colorbar onto the polar axis
# note - use orientation horizontal so that the gradient goes around
# the wheel rather than centre out
quant_steps = 2056
cb = mpl.colorbar.ColorbarBase(display_axes, cmap=cm.get_cmap('hsv',quant_steps),
norm=norm,
orientation='horizontal')
# aesthetics - get rid of border and axis labels
cb.outline.set_visible(False)
display_axes.set_axis_off()
plt.show() # Replace with plt.savefig if you want to save a file
This produces
If you want a ring rather than a wheel, use this before plt.show() or plt.savefig
display_axes.set_rlim([-1,1])
This gives
As per #EelkeSpaak in comments - if you save the graphic as an SVG as per the OP, here is a tip for working with the resulting graphic: The little elements of the resulting SVG image are touching and non-overlapping. This leads to faint grey lines in some renderers (Inkscape, Adobe Reader, probably not in print). A simple solution to this is to apply a small (e.g. 120%) scaling to each of the individual gradient elements, using e.g. Inkscape or Illustrator. Note you'll have to apply the transform to each element separately (the mentioned software provides functionality to do this automatically), rather than to the whole drawing, otherwise it has no effect.
I just needed to make a color wheel and decided to update rsnape's solution to be compatible with matplotlib 2.1. Rather than place a colorbar object on an axis, you can instead plot a polar colored mesh on a polar plot.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
import matplotlib as mpl
# If displaying in a Jupyter notebook:
# %matplotlib inline
# Generate a figure with a polar projection
fg = plt.figure(figsize=(8,8))
ax = fg.add_axes([0.1,0.1,0.8,0.8], projection='polar')
# Define colormap normalization for 0 to 2*pi
norm = mpl.colors.Normalize(0, 2*np.pi)
# Plot a color mesh on the polar plot
# with the color set by the angle
n = 200 #the number of secants for the mesh
t = np.linspace(0,2*np.pi,n) #theta values
r = np.linspace(.6,1,2) #radius values change 0.6 to 0 for full circle
rg, tg = np.meshgrid(r,t) #create a r,theta meshgrid
c = tg #define color values as theta value
im = ax.pcolormesh(t, r, c.T,norm=norm) #plot the colormesh on axis with colormap
ax.set_yticklabels([]) #turn of radial tick labels (yticks)
ax.tick_params(pad=15,labelsize=24) #cosmetic changes to tick labels
ax.spines['polar'].set_visible(False) #turn off the axis spine.
It gives this:
I have some satellite image data I would like to display using Cartopy. I have successfully followed the image example detailed here. Resulting in this code:
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig = plt.figure(figsize=(12, 12))
img_extent = (-77, -59, 9, 26)
ax = plt.axes(projection=ccrs.PlateCarree())
# image data coming from server, code not shown
ax.imshow(img, origin='upper', extent=img_extent)
ax.set_xmargin(0.05)
ax.set_ymargin(0.10)
# mark a known place to help us geo-locate ourselves
ax.plot(-117.1625, 32.715, 'bo', markersize=7)
ax.text(-117, 33, 'San Diego')
ax.coastlines()
ax.gridlines()
plt.show()
This code generates the following image
My problem is that the satellite image data is not in the PlateCarree projection, but the Mercator projection.
But when I get the axis object with
ax = plt.axes(projection=ccrs.Mercator())
I lose the coastlines.
I saw the issue reported here. But
ax.set_global()
results in this image:
The data is not present, and San Diego is in the wrong location. Also the lat/lon extents have changed. What am I doing wrong?
Post Discussion Update
The main problem is that I had not properly specified the image extents in the target projection with the transform_points method. I also had to be specific about the coordinate reference system in the imshow method as Phil suggests. Here is the correct code:
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
proj = ccrs.Mercator()
fig = plt.figure(figsize=(12, 12))
extents = proj.transform_points(ccrs.Geodetic(),
np.array([-77, -59]),
np.array([9, 26]))
img_extents = (extents[0][0], extents[1][0], extents[0][6], extents[1][7] )
ax = plt.axes(projection=proj)
# image data coming from server, code not shown
ax.imshow(img, origin='upper', extent=img_extents,transform=proj)
ax.set_xmargin(0.05)
ax.set_ymargin(0.10)
# mark a known place to help us geo-locate ourselves
ax.plot(-117.1625, 32.715, 'bo', markersize=7, transform=ccrs.Geodetic())
ax.text(-117, 33, 'San Diego', transform=ccrs.Geodetic())
ax.coastlines()
ax.gridlines()
plt.show()
Resulting in this correctly geoprojected satellite image:
Ideally, try to always be specific about the coordinate reference system your data are in when plotting with cartopy (via the transform keyword). This will mean you can just switch projections in your script and the data will automatically be put in the correct place.
So in your case, the plt.imshow should have a transform=ccrs.Mercator() keyword argument (you may need a a more specific parameterised Mercator instance). If your extents are in Geodetic (lats and lons) you will have to transform the bounding box into the mercator coordinates, but other than that, everything else should work as expected.
NOTE: I'm going to go and update the example to include the transform argument ;-) (PR: https://github.com/SciTools/cartopy/pull/343)
HTH
I am trying to create a simple stereographic sun path diagram similar to these:
http://wiki.naturalfrequency.com/wiki/Sun-Path_Diagram
I am able to rotate a polar plot and set the scale to 90. How do I go about reversing the y-axis?
Currently the axis goes from 0>90, how do I reverse the axis to 90>0 to represent the azimuth?
I have tried:
ax.invert_yaxis()
ax.yaxis_inverted()
Further, how would I go about creating a stereographic projection as opposed to a equidistant?
My code:
import matplotlib.pylab as plt
testFig = plt.figure(1, figsize=(8,8))
rect = [0.1,0.1,0.8,0.8]
testAx = testFig.add_axes(rect,polar=True)
testAx.invert_yaxis()
testAx.set_theta_zero_location('N')
testAx.set_theta_direction(-1)
Azi = [90,180,270]
Alt= [0,42,0]
testAx.plot(Azi,Alt)
plt.show()
Currently my code doesn't seem to even plot the lines correctly, do I need need to convert the angle or degrees into something else?
Any help is greatly appreciated.
I finally had time to play around with matplotlib. After much searching, the correct way as Joe Kington points out is to subclass the Axes. I found a much quicker way utilising the excellent basemap module.
Below is some code I have adapted for stackoverflow. The sun altitude and azimuth were calculated with Pysolar with a set of timeseries stamps created in pandas.
import matplotlib.pylab as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
winterAzi = datafomPySolarAzi
winterAlt = datafromPySolarAlt
# create instance of basemap, note we want a south polar projection to 90 = E
myMap = Basemap(projection='spstere',boundinglat=0,lon_0=180,resolution='l',round=True,suppress_ticks=True)
# set the grid up
gridX,gridY = 10.0,15.0
parallelGrid = np.arange(-90.0,90.0,gridX)
meridianGrid = np.arange(-180.0,180.0,gridY)
# draw parallel and meridian grid, not labels are off. We have to manually create these.
myMap.drawparallels(parallelGrid,labels=[False,False,False,False])
myMap.drawmeridians(meridianGrid,labels=[False,False,False,False],labelstyle='+/-',fmt='%i')
# we have to send our values through basemap to convert coordinates, note -winterAlt
winterX,winterY = myMap(winterAzi,-winterAlt)
# plot azimuth labels, with a North label.
ax = plt.gca()
ax.text(0.5,1.025,'N',transform=ax.transAxes,horizontalalignment='center',verticalalignment='bottom',size=25)
for para in np.arange(gridY,360,gridY):
x= (1.1*0.5*np.sin(np.deg2rad(para)))+0.5
y= (1.1*0.5*np.cos(np.deg2rad(para)))+0.5
ax.text(x,y,u'%i\N{DEGREE SIGN}'%para,transform=ax.transAxes,horizontalalignment='center',verticalalignment='center')
# plot the winter values
myMap.plot(winterX,winterY ,'bo')
Note that currently I am only plotting points, you will have to make sure that line points have a point at alt 0 at sunrise/sunset.