I'm trying to recreate this projection using matplotlib Fundamental Lune Plot. The reference material associated with this specific projection is here, Carl Tape Moment Tensors
The geophysics behind the plot isn't crucial, but essentially its a projection between longitudes of -30 and 30 degrees and latitudes -90 to 90. I've thought that Basemap might be a good way of creating the projection, but I cannot seem to figure out how to only show this fundamental lune section. Here is what I've been playing around with,but it still shows the entire globe:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
m = Basemap(
resolution='l', # coastline resolution, can be 'l' (low), 'h'
projection='hammer', # Hammer projection
lat_ts=0, # latitude of true scale
lon_0=0, # longitude of the plotting domain center
lat_0=0) # latitude of the plotting domain center
# draw parallels and meridians.
m.drawparallels(np.arange(-90.,90.,10.))
m.drawmeridians(np.arange(-30.,31.,10.))
ax = plt.gca()
plt.show()
Can anybody offer some guidance or suggestions?
In Basemap, I believe the Hammer projection is "global", meaning that it doesn't take extent inputs so it makes sense the entire globe would always show. As of 2022 Basemap is heavily deprecated / not supported.
I was able to make the plot you want using Cartopy instead. The following code produces image below and on the left, with some demo data:
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
import matplotlib.path as mpath
# The Sinusoidal projection was added to Cartopy in version 0.14
fig = plt.figure(figsize=(3, 5))
ax = fig.add_subplot(111, projection=ccrs.Sinusoidal())
# Here I define a matplotlib Path object to use as the boundary
outlinex = np.concatenate([[-30],np.tile(-30,180), np.tile(30,180),[-30]])
outliney = np.concatenate([[-90],np.arange(-90,90),np.arange(89,-91,-1),[-90]])
outlinecodes = np.array([mpath.Path.MOVETO]+[mpath.Path.LINETO]*360+[mpath.Path.MOVETO])
outlinepath = mpath.Path(np.column_stack([outlinex[::-1], outliney[::-1]]), outlinecodes[::-1])
# For good measure, plot some data
ax.plot(np.arange(-10,25), np.linspace(80,45,35), transform=ccrs.Geodetic())
ax.plot(np.tile(25,91),np.arange(45,-46,-1), transform=ccrs.Geodetic())
# Plot gridlines and set the boundary
ax.gridlines(xlocs=np.arange(-30,31,10), ylocs=np.arange(-90,91,45))
ax.set_boundary(outlinepath, transform=ccrs.Geodetic())
# The plotting will have automatically set the extents, so set them to what we want
ax.set_extent((-30,30,-90,90))
plt.show()
Note, that if you omit the set_boundary elements and just use the set_extent, you'll get the image on the right, rather than the image on the left.
Related
I have a cartopy GeoAxesSubplot with some points, and potentially lines or polygons. The projection could be any that is supported by cartopy, including orthographic.
I can plot using different transformations, as explained here:
from matplotlib import pyplot as plt
import cartopy.crs as ccrs
# Projection could be any, e.g. globe or Arctic Stereopolar...
ax = plt.axes(projection=ccrs.Mollweide())
ax.coastlines()
# Plot using the coordinate system of the Axes
a = ax.plot(0.45, 0.5, transform=ax.transAxes, marker='o', ms=10)
# Plot using the projected coordinates using cartopy.crs
b = ax.plot(0, 0, transform=ccrs.PlateCarree() , marker='o', ms=10)
I would like to transform geographical coordinates to get the cartesian coordinates of the object in the axis (e.g. a subplot). That is, the coordinates in the range [0,1] in the axes of the figure, with (0,0) in the lower-left corner, and (1,1) in the upper-right.
In the case above, b should be converted to (0.5, 0, 5) as it is in the center of the map.
Something similar can be done using transform_points, however, I have not been able to transpose to axes-coords.
A number of parameters are defined in matplotlib and cartopy to control where the object is on the map (extent, projection, center meridian, view elevation etc). Hence, introduce another library might be awkward.
Answer given e.g. here explains how the reverse is achievable, however, the example does not give the right answer for how to generate axes coords.
Keep in mind that "geographical coordinates" is not that well defined, since you're mixing two projections (Mollweide & PlateCarree) which both use "geographical coordinates". Also be careful with using the exact center, since that might accidentally look correct, even if you use incorrect coordinates.
So you might first need to convert your data to the projection of the map (projection).
Other than that the Matplotlib transformation tutorial you link to provides all the information necessary to do the transforms.
Setting up the inputs:
from matplotlib import pyplot as plt
import cartopy.crs as ccrs
# sample point coordinates in Plate-Carree
x_pc = -110.0 # longitude
y_pc = 45.0 # latitude
map_proj = ccrs.Mollweide()
data_proj = ccrs.PlateCarree()
The conversion depends on the xlim and ylim of the axes, so it's important to set use ax.set_global() first. That gives a proper mapping from the projection to the display coordinates (and subsequent axes coordinates).
fig, ax = plt.subplots(subplot_kw=dict(projection=map_proj), facecolor='w')
ax.set_global()
ax.coastlines()
b = ax.plot(x_pc, y_pc, 'go', transform=data_proj, ms=5)
# convert to map-coordinates (Mollweide)
x_mollw, y_mollw = ax.projection.transform_point(x_pc, y_pc, data_proj)
# convert to display coordinates
x_disp, y_disp = ax.transData.transform((x_mollw, y_mollw))
# convert to axes coordinates
x_axes, y_axes = ax.transAxes.inverted().transform((x_disp, y_disp))
# plot same point but using axes coordinates
ax.plot(x_axes, y_axes, 'ro', transform=ax.transAxes, ms=10, mfc='none', mew=2)
I am trying to obtain an ortographic projection of the celestial sphere, with equatorial coordinates, as seen from a certain latitude, as in the following picture:
(Grid obtained from Skychart/Cartes du ciel)
This image is a print of Skychart/Cartes du ciel, showing the equatorial grid for an observer at 23°S latitude. I want to be able to reproduce the exact same image in Python (apart from the dark blue background). My first attempt was to use CartoPy, setting the central latitude as -23, as follows:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.Orthographic(central_latitude=-23))
ax.gridlines()
plt.show()
but the resulting picture looks like this:
From the position of the south pole, I believe setting the central latitude to the observer's latitude in CartoPy does not solve my problem. Is there another way, either with CartoPy or another package (maybe AstroPy? - I have never used it) to obtain the same plot as Skychart (Image 1) in python?
First of all, your first image is Azimuthal Equidistant Projection. So that, it is quite different from your second plot (Orthographic projection). To get the plot (first image) like that using Cartopy requires some steps that are interesting to follow. Here is the code with comments that produces the output plot that I consider a good result.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.path as mpath
import numpy as np
r_limit = 20037508 #from: ax.get_ylim() of full extent
# this makes circle for clipping the plot
pts = [] #unit circle vertices
cds = [] #path codes
numps = 32
for ix,ea in enumerate(np.linspace(0, 2*np.pi, numps)):
#print(ea)
xi = np.cos(ea)
yi = np.sin(ea)
pts.append([xi,yi])
if (ix==0):
# start
cds.append(1)
elif (ix==numps-1):
# close
cds.append(79)
else:
cds.append(4)
# make them np.array for easy uses
vertices = np.array(pts)
codes = np.array(cds)
# manipulate them to create a required clip_path
scale = r_limit*0.5
big_ccl = mpath.Path(vertices*scale, codes)
clippat = plt.Polygon(big_ccl.vertices[:, :], visible=True, fill=False, ec='red')
# create axis to plot `AzimuthalEquidistant` projection
# this uses specific `central_latitude`
ax = plt.axes(projection=ccrs.AzimuthalEquidistant(central_latitude=-23))
# add the clip_path
ax.add_patch(clippat)
# draw graticule (of meridian and parallel lines)
# applying clip_path to get only required extents plotted
ax.gridlines(draw_labels=False, crs=ccrs.PlateCarree(),
xlocs=range(-180,180,30), ylocs=range(-80,90,20), clip_path=clippat)
# specify radial extents, use them to set limits of plot
r_extent = r_limit/(2-0.05) # special to the question
ax.set_xlim(-r_extent, r_extent)
ax.set_ylim(-r_extent, r_extent)
ax.set_frame_on(False) #hide the rectangle frame
plt.show()
I am trying to plot map points using Cartopy with Anaconda Python but am getting some strange failures with the transform. In my simple example, I am trying to plot 3 points but they are getting doubled.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
lons = [214.5, 2.7, 197.5]
lats = [35, 36, 37.]
ax = plt.axes(projection=ccrs.Orthographic(
central_longitude=0,
central_latitude=90))
# plot lat/lon points
ax.plot(lons, lats, 'ro',
transform=ccrs.Geodetic())
# plot north pole for reference
ax.plot([0], [90], 'b^',
transform=ccrs.Geodetic())
# add coastlines for reference
ax.coastlines(resolution='50m')
ax.set_global()
plt.show()
Tested with:
cartopy==0.16.0 and Cartopy-0.16.1.dev179-
proj4==4.9.3, proj4==5.0.1, proj4==5.0.2
My only hint was that with Cartopy-0.16.1.dev179- and proj4==5.0.1, I got this UserWarning:
/Users/***/anaconda3/lib/python3.6/site-packages/cartopy/crs.py:1476: UserWarning: The Orthographic projection in Proj between 5.0.0 and 5.1.0 incorrectly transforms points. Use this projection with caution.
I opened an issue on https://github.com/SciTools/cartopy/issues/1172 but the issue was closed. Anyone know how to get cartopy working correctly with Orthographic projections?
As far as I know, there are a couple of approaches that you could use to get the result that you expect.
Firstly, explicitly transform the points to be in the native projection...
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
# create the lat/lon points
lons = np.array([214.5, 2.7, 197.5])
lats = np.array([35, 36, 37.])
# create the projections
ortho = ccrs.Orthographic(central_longitude=0, central_latitude=90)
geo = ccrs.Geodetic()
# create the geoaxes for an orthographic projection
ax = plt.axes(projection=ortho)
# transform lat/lons points to othographic points
points = ortho.transform_points(geo, lons, lats)
# plot native orthographic points
ax.plot(points[:, 0], points[:, 1], 'ro')
# plot north pole for reference (with a projection transform)
ax.plot([0], [90], 'b^', transform=geo)
# add coastlines for reference
ax.coastlines(resolution='50m')
ax.set_global()
This plots as expected...
Expected Orthographic projection plot
The original issue that you're seeing is that cartopy is attempting interpret the sequence of points as a bounded geometry (or path), but is getting a little confused. Explicitly converting the lat/lon points to be native orthographic points dodges this bullet.
Knowing this nugget of information, we could alternatively call the appropriate method that respects the list of points as individual points (and avoid cartopy making assumptions that don't meet our expectations) by using scatter instead of plot...
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
# create the lat/lon points
lons = np.array([214.5, 2.7, 197.5])
lats = np.array([35, 36, 37.])
# create the projections
ortho = ccrs.Orthographic(central_longitude=0, central_latitude=90)
geo = ccrs.Geodetic()
# create the geoaxes for an orthographic projection
ax = plt.axes(projection=ortho)
# plot native orthographic scatter points
ax.scatter(lons, lats, marker='o', c='r', transform=geo)
# plot north pole for reference
ax.plot([0], [90], 'b^', transform=geo)
# add coastlines for reference
ax.coastlines(resolution='50m')
ax.set_global()
This also works for me.
HTH
I am trying to focus my map on a specific area of Antarctica using 'spstere' projection from the matplotlib package in Python. I am able to plot the whole of Antarctica but this time I want to 'zoom' in and have a closer look at a specific area of the continent.
Similar examples using other projections (Pyplot contour plot - clabel spacing; http://matplotlib.org/basemap/api/basemap_api.html; https://matplotlib.org/basemap/users/examples.html) are available online but I have not been able to apply those to the 'spstere' projection over Antarctica.
I basically want to focus my map on the region of the Antarctic Peninsula, which spans roughly from
llcrnrlon=-100,urcrnrlon=-30,llcrnrlat=-90,urcrnrlat=-55.0
I have tried to use this code with the 'spstere' proj but python only takes into account boundinglat and lon_0. I've tried to change the values for boundinglat and lon_0 but it does not work either.
Any idea how I could go about? I have also tried using other projections such as 'cyl' but instead of getting a nice square like the 'spstere' proj, I get a horizontal rectangle.
m = Basemap(projection='cyl',lon_0=0,lat_0=0,\
llcrnrlon=-180,urcrnrlon=180,llcrnrlat=-90,urcrnrlat=-55.0,resolution='c')
Any help would be highly appreciated!
Using the Polar Stereographic Projection 'spstere', you can get the antarctic region by using e.g. boundinglat=-60:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
m = Basemap(projection='spstere',boundinglat=-60,lon_0=180,resolution='c')
m.drawcoastlines()
plt.show()
Note that 'spstere' is always centered at the south pole.
In order to have a map, which is not centered at the south pole, you need to use the "stere" projection. Setting the corners for the "stere" projection is not straigt forward.
One may therefore use a plot in 'spstere' projection and find some points which would enclose the region of interest. In this case e.g.
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
m = Basemap(projection='spstere',boundinglat=-50,
lon_0=180+(-100+-30)/2.,resolution='c')
m.drawmeridians(np.arange(0,360,30),labels=[1,1,1,0])
m.drawparallels(np.arange(-90,90,5))
m.drawcoastlines()
xll, yll = m(-150,-70) # <-- find those points by looking at meridians and parallels
xur, yur = m(-30,-55)
m.scatter([xll,xur], [yll, yur], c="crimson")
plt.show()
Using those points, (-150,-70, -30,-55), as the corners of the map, you can then plot a map using 'stere' projection.
m = Basemap(projection='stere',resolution='c',
lat_0=-90, lon_0=(-100+-30)/2., lat_ts=(-90.+-55.)/2.,
llcrnrlon=-150,urcrnrlon=-30,llcrnrlat=-70,urcrnrlat=-55)
If this heuristic method is not wanted, you may automate this procedure by creating a dummy map in 'spstere' projection, calculate the coordinates from the rectangle in question (llcrnrlon=-100,urcrnrlon=-30,llcrnrlat=-90,urcrnrlat=-55.0) and create a new basemap in stere projection with them. The function below is taken from the ActiveState site (author PG).
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
def polar_stere(lon_w, lon_e, lat_s, lat_n, **kwargs):
'''Returns a Basemap object (NPS/SPS) focused in a region.
lon_w, lon_e, lat_s, lat_n -- Graphic limits in geographical coordinates.
W and S directions are negative.
**kwargs -- Aditional arguments for Basemap object.
'''
lon_0 = lon_w + (lon_e - lon_w) / 2.
ref = lat_s if abs(lat_s) > abs(lat_n) else lat_n
lat_0 = np.copysign(90., ref)
proj = 'npstere' if lat_0 > 0 else 'spstere'
prj = Basemap(projection=proj, lon_0=lon_0, lat_0=lat_0,
boundinglat=0, resolution='c')
lons = [lon_w, lon_e, lon_w, lon_e, lon_0, lon_0]
lats = [lat_s, lat_s, lat_n, lat_n, lat_s, lat_n]
x, y = prj(lons, lats)
ll_lon, ll_lat = prj(min(x), min(y), inverse=True)
ur_lon, ur_lat = prj(max(x), max(y), inverse=True)
return Basemap(projection='stere', lat_0=lat_0, lon_0=lon_0,
llcrnrlon=ll_lon, llcrnrlat=ll_lat,
urcrnrlon=ur_lon, urcrnrlat=ur_lat, **kwargs)
llcrnrlon=-100
urcrnrlon=-30
llcrnrlat=-90
urcrnrlat=-55.0
m = polar_stere(llcrnrlon, urcrnrlon, llcrnrlat, urcrnrlat)
m.drawmeridians(np.arange(0,360,30),labels=[1,1,1,0])
m.drawparallels(np.arange(-90,90,30),labels=[1,1,1,1])
m.drawcoastlines()
plt.show()
Basically I want to make the body of the Earth "transparent" so that an object (a point or a patch, etc) and its antipodal image can be displayed simultaneously on the same orthographic map. Is this possible to do with matplotlib and basemap?
How about just plotting two projections? It's a bit of a cheat, but here you go:
import pylab as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
front_central_lon = 70
m1 = Basemap(projection='ortho', lat_0=0, lon_0=front_central_lon)
m2 = Basemap(projection='ortho', lat_0=0, lon_0=front_central_lon + 180)
# m2.drawcoastlines(color='gray')
# m1.drawcoastlines()
m2.fillcontinents(color='coral', alpha=0.3)
m1.fillcontinents(color='coral', alpha=0.8)
m1.drawparallels(np.arange(-90.,91.,30.))
m1.drawmeridians(np.arange(-180.,181.,60.))
plt.show()