How can I plot function values on a sphere? - numpy

I have a Nx2 matrix of lat lon coordinate pairs, spatial_data, and I have an array of measurements at these coordinates.
I would like to plot this data on a globe, and I understand that Basemap can do this. I found this link which shows how to plot data if you have cartesian coordinates. Does there exist functionality to convert lat,lon to cartesian coordinates? Alternatively, is there a way to plot this data with only the lat,lon information?

You can use cartopy:
import numpy as np
import matplotlib.pyplot as plt
from cartopy import crs
# a grid for the longitudes and latitudes
lats = np.linspace(-90, 90, 50)
longs = np.linspace(-180, 180, 50)
lats, longs = np.meshgrid(lats, longs)
# some data
data = lats[1:] ** 2 + longs[1:] ** 2
fig = plt.figure()
# create a new axes with a cartopy.crs projection instance
ax = fig.add_subplot(1, 1, 1, projection=crs.Mollweide())
# plot the date
ax.pcolormesh(
longs, lats, data,
cmap='hot',
transform=crs.PlateCarree(), # this means that x, y are given as longitude and latitude in degrees
)
fig.tight_layout()
fig.savefig('cartopy.png', dpi=300)
Result:

Related

How to plot a map of a semi-sphere (eg northern hemisphere) using matplotlib cartopy

How to plot a map of a semi-sphere (eg northern hemisphere) using cartopy.
I'm trying to plot a map of the northern hemisphere using cartopy. But I don't understand how should I define the extent of the map so that only this region of interest is plotted. I would like the map to be cut off at 0° latitude. I would like to have code where I could easily define any subset of the glob using the ccrs.NearsidePerspective projection, or the ccrs.Orthographic projection.
Below I leave a code for reproduction.
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
# Creating fake data
x = np.linspace(-180, 180, 361)
y = np.linspace(-90, 90, 181)
lon, lat = np.meshgrid(x, y)
values = np.random.random(lon.shape)*20
fig = plt.figure(figsize=(15, 10))
proj = ccrs.NearsidePerspective(central_longitude=-45, central_latitude=21)
ax = fig.add_subplot(121, projection=proj)
ax.set_extent([-120, 40, 0, 60])
ax.pcolormesh(lon, lat, values, transform=ccrs.PlateCarree())
ax.coastlines(linewidth=2)
gl = ax.gridlines(draw_labels=True, linestyle='--')
The code generates the following figure:
Thank you very much in advance.
Robson
To plot only the upper hemisphere part of the map projection, a polygon of that part is needed to use as the projection boundary.
That polygon is created as a matplotlib-path object. It vertices' coordinates are data coordinates in my code, so that, no transformation is required when applied to the final plot.
This is a complete code:-
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import matplotlib.path as mpath
import numpy as np
from geographiclib.geodesic import Geodesic
fig = plt.figure(figsize=[12, 12])
proj = ccrs.NearsidePerspective(central_longitude=-45, central_latitude=21, satellite_height=35785831)
ax = plt.subplot(projection=proj)
# The value of r is obtained by previous run of this code ...
# with the line .. #print(ax.get_xlim()) uncommented
r = 5476336.098
ax.set_xlim(-r, r)
ax.set_ylim(-r, r)
ax.stock_img()
ax.coastlines(lw=1, color="darkblue")
# Find the locations of points along the equatorial arc
# start location
lon_fr, lat_fr = 30, 0
# end location
lon_to, lat_to = -120, 0
# This gets geodesic between the two points, WGS84 ellipsoid is used
geodl = Geodesic.WGS84.InverseLine(lat_fr, lon_fr, lat_to, lon_to)
lonlist, latlist = [], []
num_points = 32 #for series of points on geodesic/equator
for ea in np.linspace(0, geodl.s13, num_points):
g = geodl.Position(ea, Geodesic.STANDARD | Geodesic.LONG_UNROLL)
#print("{:.0f} {:.5f} {:.5f} {:.5f}".format(g['s12'], g['lat2'], g['lon2'], g['azi2']))
lon2, lat2 = g['lon2'], g['lat2']
lonlist.append( g['lon2'] )
latlist.append( g['lat2'] )
# Get data-coords from (lonlist, latlist)
# .. as points along equatorial arc
dataxy = proj.transform_points(ccrs.PlateCarree(), np.array(lonlist), np.array(latlist))
# (Uncomment to) Plot equator line
#ax.plot(dataxy[:, 0:1], dataxy[:, 1:2], "go-", linewidth=2, markersize=5, zorder=10)
# Top semi-circle arc for map extent
theta = np.linspace(-0.5*np.pi, 0.5*np.pi, 64)
center, radius = [0, 0], r
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
# Combine vertices of the semi-circle and equatorial arcs
# These points are in data coordinates, ready to plot on the axes.
verts = np.vstack([verts*r, dataxy[:, 0:2]])
polygon = mpath.Path(verts + center)
ax.set_boundary(polygon) #This masks-out unwanted part of the plot
gl = ax.gridlines(draw_labels=True, xlocs=range(-150,180,30), ylocs=range(0, 90, 15),
y_inline=True, linestyle='--', lw= 5, color= "w", )
# Get limits, the values are the radius of the circular map extent
# The values is then used as r = 5476336.09797 on top of the code
#print(ax.get_xlim())
#print(ax.get_ylim())
plt.show()

Scatterplot with Cartopy and Matplotlib produces cut-off map

I am trying to do a scatterplot on a map with Robinson Projection. However, the produced map is cut off at the side and I cannot figure out why. I did not have any problems when doing contour plots on similar maps. The longitude and latitude for the points, I want to plot, are stored in two seperate lists with floats (lon, lat), like this:
lon = [2.906250000000000000e+02, 2.906250000000000000e+02, 2.906250000000000000e+02, ...]
lat = [-5.315959537001968016e+01, -5.129437713895114825e+01,-4.942915369712304852e+01, ...]
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
fig_scale = 2
fig = plt.figure(figsize=(4*fig_scale,3*fig_scale))
gs1 = plt.GridSpec(2, 1,height_ratios=[1, 0.05])
axes = plt.subplot(gs1[0,0], projection=ccrs.Robinson(central_longitude=0.0))
mappab = plt.scatter(x=lon, y=lat,
transform=ccrs.PlateCarree())
axes.coastlines(color='grey')
axes.gridlines()
plt.show()

Lat/long transformed to OSGB has an offset when plotted

If I take a set of lat/long points, convert them to OSGB crs using cartopy's "transform_points" method and then plot them, they are offset compared to plotting the lat/long directly. If I transform them back to lat/long, they plot ok. If I convert them to UTM coords they plot ok. Am I missing something?
Example code:
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.io.img_tiles import OSM
# set up figure and background map tile
fig = plt.figure()
ax = plt.axes(projection=OSM().crs)
ax.set_extent((-2.473911, -2.410147, 50.567006, 50.605896))
ax.add_image(imagery, 14)
# Plot some test points using lat/long (PlateCarree) crs
test_lonlat = np.array([[-2.464482, -2.432523, -2.437892], [50.593243, 50.596390, 50.573177]])
plt.plot(test_lonlat[0], test_lonlat[1], 'r:+', transform=ccrs.PlateCarree())
# Transform to OSGB coords and plot
test_OS = ccrs.OSGB().transform_points(ccrs.PlateCarree(), test_lonlat[0], test_lonlat[1])
plt.plot(test_OS[:, 0], test_OS[:, 1], 'kx-', transform=ccrs.OSGB())
plt.show()
Thanks for any advice.
Simpler example using single point
fig = plt.figure(figsize=[7, 7])
ax = plt.axes(projection=OSM().crs)
ax.set_extent((-2.435, -2.405, 50.575, 50.595))
ax.add_image(OSM(), 14)
# Simpler test of OSGB crs
# Take point at end of outer breakwater: OS (370783, 76226) or lat/long (-2.414343, 50.584978)
point_os = [370774.0, 76221.0]
point_lonlat = [-2.414278, 50.584971]
# Plot each on OSM tile - both look ok but small error
plt.plot(point_os[0], point_os[1], 'r+', transform=ccrs.OSGB(), markersize=15)
plt.plot(point_lonlat[0], point_lonlat[1], 'kx', transform=ccrs.PlateCarree(), markersize=10)
# Convert lat/long to OSGB and plot - now offset by ~50 m to NW
point_os_new = ccrs.OSGB().transform_point(point_lonlat[0], point_lonlat[1], ccrs.PlateCarree())
plt.plot(point_os_new[0], point_os_new[1], 'm^', transform=ccrs.OSGB(), markersize=10)
# Print both sets of OS coords
print(f'Original point: {point_os}')
print(f'Transformed point: {point_os_new}')
When you create an axes to plot with this line of code
ax = plt.axes(projection=OSM().crs)
you are expected to use cartopy.crs.Mercator projection coordinates to plot on it as OSM().crs is Mercator.
This part of your code:
# Transform to OSGB coords and plot
test_OS = ccrs.OSGB().transform_points(ccrs.PlateCarree(), \
test_lonlat[0], test_lonlat[1])
plt.plot(test_OS[:, 0], test_OS[:, 1], 'kx-', transform=ccrs.OSGB())
uses OSGB coordinates to plot on Mercator projection. That's the wrong part.
The correct lines of code should be
test2_OS = OSM().crs.transform_points(ccrs.PlateCarree(), \
test_lonlat[0], test_lonlat[1])
ax.plot(test2_OS[:, 0], test2_OS[:, 1], 'kx-', transform=OSM().crs)
Edit1
The tile images in my plot above is OSM(), via ax.add_image(OSM(), 14).
The problems the OP faces is not using matching coordinate systems between the data and the axes, or wrong values.

Plotting Poly3DCollection using add_collection3d

I have tree arrays of the same size representing the spherical coordinates of points in space. I want to plot them transformed in cartesian coordinates. I am trying to produce a surface and I need to use the add_collection3d method instead of the plot_surface because of the dimensions of my arrays. The original arrays have different lengths in spherical coordinates and the transformation into cartesian is not linear.
A simplified example follows:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LightSource
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from mpl_toolkits.mplot3d import Axes3D
phi_rad = np.linspace(0,360, 10)/180.0*np.pi
theta_rad = np.linspace(0,360, 10)/180.0*np.pi # cos(theta)
counts_str = np.linspace(0, 100, 10) # counts
# convertion to cartesian coordinates 1D arrays
x = counts_str * np.sin(theta_rad) * np.cos(phi_rad)
y = counts_str * np.sin(theta_rad) * np.sin(phi_rad)
z_str = counts_str * np.cos(theta_rad)
verts = [list(zip(x, y, z_str))]
fig = plt.figure()
ax = Axes3D(fig)
ax.add_collection3d(Poly3DCollection(verts, cmap="hot", alpha=0.9))
ls = LightSource(azdeg=225.0, altdeg=45.0)
ax.set_xlim3d(x.min(), x.max())
ax.set_ylim3d(y.min(), y.max())
ax.set_zlim3d(z_str.min(), z_str.max())
plt.show()
I would like to apply a cmap and a LightSource (don't affect the plot), as well as an antialiased because in my real data z is an array with 20000 elements.
Looking forward to hearing from your collective intelligence!
Solution: reshape all the three vectors and use surface plot!
Creating a 3D surface plot from three 1D arrays

How to plot a tissot with cartopy and matplotlib?

For plotting skymaps I just switched from Basemap to cartopy, I like it a lot more
.
(The main reason was segfaulting of Basemap on some computers, which I could not fix).
The only thing I struggle with, is getting a tissot circle (used to show the view cone of our telescope.)
This is some example code plotting random stars (I use a catalogue for the real thing):
import matplotlib.pyplot as plt
from cartopy import crs
import numpy as np
# create some random stars:
n_stars = 100
azimuth = np.random.uniform(0, 360, n_stars)
altitude = np.random.uniform(75, 90, n_stars)
brightness = np.random.normal(8, 2, n_stars)
fig = plt.figure()
ax = fig.add_subplot(1,1,1, projection=crs.NorthPolarStereo())
ax.background_patch.set_facecolor('black')
ax.set_extent([-180, 180, 75, 90], crs.PlateCarree())
plot = ax.scatter(
azimuth,
altitude,
c=brightness,
s=0.5*(-brightness + brightness.max())**2,
transform=crs.PlateCarree(),
cmap='gray_r',
)
plt.show()
How would I add a tissot circle with a certain radius in degrees to that image?
https://en.wikipedia.org/wiki/Tissot%27s_indicatrix
I keep meaning to go back and add the two functions from GeographicLib which provide the forward and inverse geodesic calculations, with this it is simply a matter of computing a geodetic circle by sampling at appropriate azimuths for a given lat/lon/radius. Alas, I haven't yet done that, but there is a fairly primitive (but effective) wrapper in pyproj for the functionality.
To implement a tissot indicatrix then, the code might look something like:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import numpy as np
from pyproj import Geod
import shapely.geometry as sgeom
def circle(geod, lon, lat, radius, n_samples=360):
"""
Return the coordinates of a geodetic circle of a given
radius about a lon/lat point.
Radius is in meters in the geodetic's coordinate system.
"""
lons, lats, back_azim = geod.fwd(np.repeat(lon, n_samples),
np.repeat(lat, n_samples),
np.linspace(360, 0, n_samples),
np.repeat(radius, n_samples),
radians=False,
)
return lons, lats
def main():
ax = plt.axes(projection=ccrs.Robinson())
ax.coastlines()
geod = Geod(ellps='WGS84')
radius_km = 500
n_samples = 80
geoms = []
for lat in np.linspace(-80, 80, 10):
for lon in np.linspace(-180, 180, 7, endpoint=False):
lons, lats = circle(geod, lon, lat, radius_km * 1e3, n_samples)
geoms.append(sgeom.Polygon(zip(lons, lats)))
ax.add_geometries(geoms, ccrs.Geodetic(), facecolor='blue', alpha=0.7)
plt.show()
if __name__ == '__main__':
main()