Related
To simplify, as much as possible, a question I already asked, how would you OVERLAY or PROJECT a polar plot onto a cartopy map.
phis = np.linspace(1e-5,10,10) # SV half cone ang, measured up from nadir
thetas = np.linspace(0,2*np.pi,361)# SV azimuth, 0 coincides with the vel vector
X,Y = np.meshgrid(thetas,phis)
Z = np.sin(X)**10 + np.cos(10 + Y*X) * np.cos(X)
fig, ax = plt.subplots(figsize=(4,4),subplot_kw=dict(projection='polar'))
im = ax.pcolormesh(X,Y,Z, cmap=mpl.cm.jet_r,shading='auto')
ax.set_theta_direction(-1)
ax.set_theta_offset(np.pi / 2.0)
ax.grid(True)
that results in
Over a cartopy map like this...
flatMap = ccrs.PlateCarree()
resolution = '110m'
fig = plt.figure(figsize=(12,6), dpi=96)
ax = fig.add_subplot(111, projection=flatMap)
ax.imshow(np.tile(np.array([[cfeature.COLORS['water'] * 255]], dtype=np.uint8), [2, 2, 1]), origin='upper', transform=ccrs.PlateCarree(), extent=[-180, 180, -180, 180])
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
ax.pcolormesh(X,Y,Z, cmap=mpl.cm.jet_r,shading='auto')
gc.collect()
I'd like to project this polar plot over an arbitrary lon/lat... I can convert the polar theta/phi into lon/lat, but lon/lat coords (used on the map) are more 'cartesian like' than polar, hence you cannot just substitute lon/lat for theta/phi ... This is a conceptual problem. How would you tackle it?
Firstly, the data must be prepared/transformed into certain projection coordinates for use as input. And the instruction/option of the data's CRS must be specified correctly when used in the plot statement.
In your specific case, you need to transform your data into (long,lat) values.
XX = X/np.pi*180 # wrap around data in EW direction
YY = Y*9 # spread across N hemisphere
And plot it with an instruction transform=ccrs.PlateCarree().
ax.pcolormesh(XX,YY,Z, cmap=mpl.cm.jet_r,shading='auto',
transform=ccrs.PlateCarree())
The same (XX,YY,Z) data set can be plotted on orthographic projection.
Edit1
Update of the code and plots.
Part 1 (Data)
import matplotlib.colors
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import numpy as np
import matplotlib.pyplot as mpl
import cartopy.feature as cfeature
#
# Part 1
#
phis = np.linspace(1e-5,10,10) # SV half cone ang, measured up from nadir
thetas = np.linspace(0,2*np.pi,361)# SV azimuth, 0 coincides with the vel vector
X,Y = np.meshgrid(thetas,phis)
Z = np.sin(X)**10 + np.cos(10 + Y*X) * np.cos(X)
fig, ax = plt.subplots(figsize=(4,4),subplot_kw=dict(projection='polar'))
im = ax.pcolormesh(X,Y,Z, cmap=mpl.cm.jet_r,shading='auto')
ax.set_theta_direction(-1)
ax.set_theta_offset(np.pi / 2.0)
ax.grid(True)
Part 2 The required code and output.
#
# Part 2
#
flatMap = ccrs.PlateCarree()
resolution = '110m'
fig = plt.figure(figsize=(12,6), dpi=96)
ax = fig.add_subplot(111, projection=flatMap)
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land',
resolution, edgecolor='black', alpha=0.7,
facecolor=cfeature.COLORS['land']))
ax.set_extent([-180, 180, -90, 90], crs=ccrs.PlateCarree())
def scale_position(lat_deg, lon_deg, rad_deg):
# Two operations:
# 1. manipulates X,Y data and get (XX,YY)
# 2. create proper projection of (XX,YY), `rotpole_proj`
# Returns: XX,YY,rotpole_proj
# For X data
XX = X/np.pi*180 #always wrap around EW direction
# For Y data
# The cone data: min=0, max=10 --> (90-rad),90
# rad_deg = radius of the display area
top = 90
btm = top-rad_deg
YY = btm + (Y/Y.max())*rad_deg
# The proper coordinate system
rotpole_proj = ccrs.RotatedPole(pole_latitude=lat_deg, pole_longitude=lon_deg)
# Finally,
return XX,YY,rotpole_proj
# Location 1 (Asia)
XX1, YY1, rotpole_proj1 = scale_position(20, 100, 20)
ax.pcolormesh(XX1, YY1, Z, cmap=mpl.cm.jet_r,
transform=rotpole_proj1)
# Location 2 (Europe)
XX2, YY2, rotpole_proj2 = scale_position(62, -6, 8)
ax.pcolormesh(XX2, YY2, Z, cmap=mpl.cm.jet_r,
transform=rotpole_proj2)
# Location 3 (N America)
XX3, YY3, rotpole_proj3 = scale_position(29, -75, 30)
ax.pcolormesh(XX3, YY3, Z, cmap=mpl.cm.jet_r,shading='auto',
transform=rotpole_proj3)
#gc.collect()
plt.show()
This solution does NOT account for the projection point being at some altitude above the globe... I can do that part, so I really have trouble mapping the meshgrid to lon/lat so the work with the PREVIOUSLY GENERATES values of Z.
Here's a simple mapping directly from polar to cart:
X_cart = np.array([[p*np.sin(t) for p in phis] for t in thetas]).T
Y_cart = np.array([[p*np.cos(t) for p in phis] for t in thetas]).T
# Need to map cartesian XY to Z that is compatbile with above...
Z_cart = np.sin(X)**10 + np.cos(10 + Y*X) * np.cos(X) # This Z does NOT map to cartesian X,Y
print(X_cart.shape,Y_cart.shape,Z_cart.shape)
flatMap = ccrs.PlateCarree()
resolution = '110m'
fig = plt.figure(figsize=(12,6), dpi=96)
ax = fig.add_subplot(111, projection=flatMap)
ax.imshow(np.tile(np.array([[cfeature.COLORS['water'] * 255]], dtype=np.uint8), [2, 2, 1]), origin='upper', transform=ccrs.PlateCarree(), extent=[-180, 180, -180, 180])
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
im = ax.pcolormesh(X_cart*2,Y_cart*2, Z_cart, cmap=mpl.cm.jet_r, shading='auto') # c=mapper.to_rgba(Z_cart), cmap=mpl.cm.jet_r)
gc.collect()
Which maps the polar plot center to lon/lat (0,0):
I'm close... I somehow need to move my cartesian coords to the proper lon/lat (the satellite sub-point) and then scale it appropriately. Have the set of lon/lat but I'm screwing up the meshgrid somehow... ???
The sphere_intersect() routine returns lon/lat for projection of theta/phi on the globe (that works)... The bit that doesn't work is building the meshgrid that replaces X,Y:
lons = np.array([orbits.sphere_intersect(SV_pos_vec, SV_vel_vec, az << u.deg, el << u.deg,
lonlat=True)[0] for az in thetas for el in phis], dtype='object')
lats = np.array([orbits.sphere_intersect(SV_pos_vec, SV_vel_vec, az << u.deg, el << u.deg,
lonlat=True)[1] for az in thetas for el in phis], dtype='object')
long, latg = np.meshgrid(lons,lats) # THIS IS A PROBLEM I BELIEVE...
and the pcolormesh makes a mess...
I'm having trouble using cartopy ...
I have some locations (mainly changing in lat) and I want to draw some circles along the this great circle path. Here's the code
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import cartopy
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
points = np.array([[-145.624, 14.8853],
[-145.636, 10.6289],
[-145.647, 6.3713]])
proj2 = ccrs.Orthographic(central_longitude= points[1,0], central_latitude= points[1,1]) # Spherical map
pad_radius = compute_radius(proj2, points[1,0],points[1,1], 35)
resolution = '50m'
fig = plt.figure(figsize=(112,6), dpi=96)
ax = fig.add_subplot(1, 1, 1, projection=proj2)
ax.set_xlim([-pad_radius, pad_radius])
ax.set_ylim([-pad_radius, pad_radius])
ax.imshow(np.tile(np.array([[cfeature.COLORS['water'] * 255]], dtype=np.uint8), [2, 2, 1]), origin='upper', transform=ccrs.PlateCarree(), extent=[-180, 180, -180, 180])
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_0_countries', resolution, edgecolor='black', facecolor='none'))
# Loop over the points
# Compute the projected circle at that point
# Plot it!
for i in range(len(points)):
thePt = points[i,0], points[i,1]
r_or = compute_radius(proj2, points[i,0], points[i,1], 10)
print(thePt, r_or)
c= mpatches.Circle(xy=thePt, radius=r_or, color='red', alpha=0.3, transform=proj2, zorder=30)
# print(c.contains_point(points[i,0], points[i,1]))
ax.add_patch(c)
fig.tight_layout()
plt.show()
Compute radius is:
def compute_radius(ortho, lon, lat, radius_degrees):
'''
Compute a earth central angle around lat, lon
Return phi in terms of projection desired
This only seems to work for non-PlateCaree projections
'''
phi1 = lat + radius_degrees if lat <= 0 else lat - radius_degrees
_, y1 = ortho.transform_point(lon, phi1, ccrs.PlateCarree()) # From lon/lat in PlateCaree to ortho
return abs(y1)
And what I get for output:
(-145.624, 14.8853) 638304.2929446043 (-145.636, 10.6289)
1107551.8669600221 (-145.647, 6.3713) 1570819.3871025692
You can see the interpolated points going down in lat (lon is almost constant), but the radius is growing smaller with lat and the location isn't changing at all???
Thanks for rewriting the example, that clears things up!
I think the key point is that you need to convert the x/y coordinates that you use for the Circle as well, or vice versa keep the radius also in lat/lon (probably close but not identical). Now you mix and match, where the radius is based on the Orthographic projection, but the x/y are lat/lon. Because of that, the points do move along the path you want, but it's just incredibly close to the origin of the plot due to the incorrect units.
Something like this might help you along:
points = np.array([
[-145.624, 14.8853],
[-145.636, 10.6289],
[-145.647, 6.3713]],
)
proj2 = ccrs.Orthographic(
central_longitude= points[1,0],
central_latitude= points[1,1],
)
pad_radius = compute_radius(proj2, points[1,0],points[1,1], 35)
resolution = '50m'
fig, ax = plt.subplots(
figsize=(12,6), dpi=96, subplot_kw=dict(projection=map_proj, facecolor=cfeature.COLORS['water']),
)
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_0_countries', resolution, edgecolor='black', facecolor='none'))
ax.set_extent((-pad_radius, pad_radius, -pad_radius, pad_radius), crs=proj2)
for lon, lat in points:
r_or = compute_radius(proj2, lon, lat, 10)
### I think this is what you intended!
mapx, mapy = proj2.transform_point(lon, lat, ccrs.PlateCarree())
###
c= mpatches.Circle(xy=(mapx, mapy), radius=r_or, color='red', alpha=0.3, transform=proj2, zorder=30)
ax.add_patch(c)
Here's the complete answer. I was not converting the lat/lon into the orthographic projection coords... and apparently it wants the size of the circle to be in meters -- hence I use a function to change degrees (which I know for my circle) into meters:
def deg2m(val_degree):
"""
Compute surface distance in meters for a given angular value in degrees
Uses the definition of a degree on the equator...
"""
geod84 = Geod(ellps='WGS84')
lat0, lon0 = 0, 90
_, _, dist_m = geod84.inv(lon0, lat0, lon0+val_degree, lat0)
return dist_m
# Data points where I wish to draw circles
points = np.array([[-111.624, 30.0],
[-111.636, 35.0],
[-111.647, 40.0]])
proj2 = ccrs.Orthographic(central_longitude= points[1,0], central_latitude= points[1,1]) # Spherical map
pad_radius = compute_radius(proj2, points[1,0],points[1,1], 45) # Generate a region bigger than our circles
resolution = '50m'
fig = plt.figure(figsize=(112,6), dpi=96)
ax = fig.add_subplot(1, 1, 1, projection=proj2)
# Bound our plot/map
ax.set_xlim([-pad_radius, pad_radius])
ax.set_ylim([-pad_radius, pad_radius])
ax.imshow(np.tile(np.array([[cfeature.COLORS['water'] * 255]], dtype=np.uint8), [2, 2, 1]), origin='upper', transform=ccrs.PlateCarree(), extent=[-180, 180, -180, 180])
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_0_countries', resolution, edgecolor='black', facecolor='none'))
r_or = deg2m(17) # Compute the size of circle with a radius of 17 deg
# Loop over the points
# Compute the projected circle at that point
# Plot it!
for i in range(len(points)):
thePt = points[i,0], points[i,1]
# Goes from lat/lon to coordinates in our Orthographic projection
mapx, mapy = proj2.transform_point(points[i,0], points[i,1], ccrs.PlateCarree())
c= mpatches.Circle(xy=(mapx, mapy), radius=r_or, color='red', alpha=0.3, transform=proj2, zorder=30)
ax.add_patch(c)
fig.tight_layout()
plt.show()
I've verified that the deg2m routine works since it's about 17 deg of lat from Phoenix AZ to the Canadian border (approx) which is nearby these test points.
I have plotted my 8 corner points with center of a cuboid as in figure .
I have tried with the scatter but now i want the surface plot connecting these 8 points.
when i have tried with the surface plot i am unable to attend to that. Can you please suggest any solution for that
l = 0.3
w = 0.4
h = 0.1
center =
[2.10737, -0.100085, 0.716869]
F=
[[array([[1.]]) array([[-0.001]]) array([[-0.017]])]
[array([[0.]]) array([[-0.999]]) array([[0.037]])]
[array([[0.017]]) array([[0.037]]) array([[0.999]])]]
def cuboid(center, size):
ox, oy, oz = center
l, w, h = size
ax = fig.gca(projection='3d') ##plot the project cuboid
X=[ox-l/2,ox-l/2,ox-l/2,ox-l/2,ox+l/2,ox+l/2,ox+l/2,ox+l/2]
Y=[oy+w/2,oy-w/2,oy-w/2,oy+w/2,oy+w/2,oy-w/2,oy-w/2,oy+w/2]
Z=[oz-h/2,oz-h/2,oz+h/2,oz+h/2,oz+h/2,oz+h/2,oz-h/2,oz-h/2]
X_new = ([])
Y_new = ([])
Z_new = ([])
for i in range(0,8):
c=np.matrix([[X[i]],
[Y[i]],
[Z[i]]])
u=F*c
X_new = np.append(X_new, u.item(0))
Y_new = np.append(Y_new, u.item(1))
Z_new = np.append(Z_new, u.item(2))
ax.scatter(X_new,Y_new,Z_new,c='darkred',marker='o') #the plot of points after rotated
ax.scatter(ox,oy,oz,c='crimson',marker='o') #the previous plot of points before rotated
## Add title
plt.title('Plot_for_PSM', fontsize=20)
plt.gca().invert_yaxis()
##labelling the axes
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
Here a solution.
###Added import
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
###Addded size and center in this format in order to manipulate them easily
size = [0.3, 0.4, 0.1]
center = [2.10737, -0.100085, 0.716869]
###This numpy vector will be used to store the position of the sides
side = np.zeros((8,3))
###Just re-ordered your matrix in some np.arrays
F = [[np.array([1., -0.001, -0.017])],
[np.array([0., -0.999, 0.037])],
[np.array([0.017, 0.037, 0.999])]]
def cuboid(center, size):
ox, oy, oz = center
l, w, h = size
###Added the fig in order to be able to plot it later
fig = plt.figure()
ax = fig.gca(projection='3d') ##plot the project cuboid
X=[ox-l/2,ox-l/2,ox-l/2,ox-l/2,ox+l/2,ox+l/2,ox+l/2,ox+l/2]
Y=[oy+w/2,oy-w/2,oy-w/2,oy+w/2,oy+w/2,oy-w/2,oy-w/2,oy+w/2]
Z=[oz-h/2,oz-h/2,oz+h/2,oz+h/2,oz+h/2,oz+h/2,oz-h/2,oz-h/2]
X_new = ([])
Y_new = ([])
Z_new = ([])
for i in range(0,8):
c=np.matrix([[X[i]],
[Y[i]],
[Z[i]]])
u=F*c
X_new = np.append(X_new, u.item(0))
Y_new = np.append(Y_new, u.item(1))
Z_new = np.append(Z_new, u.item(2))
###Doing a dot product between F and c like you did earlier but using np.dot as we're now working with Numpy format
side[i,:] = np.dot(F, c)
###Storing the position of every points
sides = [[side[0],side[1],side[2],side[3]],
[side[4],side[5],side[6],side[7]],
[side[0],side[1],side[4],side[5]],
[side[2],side[3],side[4],side[5]],
[side[1],side[2],side[5],side[6]],
[side[4],side[7],side[0],side[3]]]
###Scatter plot
ax.scatter(X_new,Y_new,Z_new,c='darkred',marker='o') #the plot of points after rotated
ax.scatter(ox,oy,oz,c='crimson',marker='o') #the previous plot of points before rotated
### Add title
plt.title('Plot_for_PSM', fontsize=20)
plt.gca().invert_yaxis()
##labelling the axes
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
###This draw the plane sides as you wanted
ax.add_collection3d(Poly3DCollection(sides, facecolors='blue', linewidths=1, edgecolors='r', alpha=.25))
cuboid(center, size)
###Mandatory to plot the cube
plt.show()
It uses Poly3DCollection, Line3DCollection from mpl_toolkits to draw 6 plane square, representing the sides of the cube.
The first step is to find the 4 coords of every side. Then you need to use Poly3DCollection to plot it.
I am trying to understand why a hexbin plot in a north or south polar stereo projection shows squashed hexagons, even though the area of the grid is square and the projection is approximately equal area.
I've tried both north and south polar stereo projections using basemap.
import numpy as np
from numpy.random import uniform
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
fig = plt.figure(figsize=(12,10)) # width, height in inches
ax =fig.add_axes([-0.02,0.1,0.74,0.74])
m = Basemap(epsg='3413',lon_0=0.,resolution='l',width=6000000,height=6000000)
m.drawcoastlines()
m.drawmapscale(0.,90.,0.,90.,1000)
npts=2000
lats = uniform(60.,80.,size=npts)
lons = uniform(0.,360.,size=npts)
data = uniform(0.,4800.,size=npts)
x,y=m(lons, lats)
thiscmap=plt.cm.get_cmap('viridis')
p=m.hexbin(x,y,C=data,gridsize=[10,10],cmap=thiscmap)
plt.show()
I don't know why you get squashed hexagons. But you can adjust the hexagon shape by setting appropriate values of gridsize. Here I modify your code and get better plot.
import numpy as np
from numpy.random import uniform
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
fig = plt.figure(figsize=(12,10)) # width, height in inches
ax =fig.add_axes([-0.02, 0.1, 0.74, 0.74])
# North polar stereographic projection epsg='3413'; ***large areal distortion***
#m = Basemap(epsg='3413', lon_0=0., resolution='c', width=6000000, height=6000000)
# 'laea': Lambert Azimuthal Equal Area
# Thematic mapping with ground surface data should be plotted on 'equal-area' projection
m = Basemap(projection='laea', lon_0=0., lat_0=90, resolution='l', width=6000000, height=6000000)
m.drawcoastlines(linewidth=0.5)
m.drawmapscale(0.,90.,0.,90.,1000) # 1000 km?
npts = 2000
lats = uniform(60.,80.,size=npts) # not cover N pole
lons = uniform(0.,360.,size=npts) # around W to E
data = uniform(0.,4800.,size=npts)
x,y = m(lons, lats)
thiscmap = plt.cm.get_cmap('viridis')
# To get 'rounded' hexagons, gridsize should be specified appropriately
# need some trial and error to get them right
#p=m.hexbin(x, y, C=data, gridsize=[10,10], cmap=thiscmap) # original code
m.hexbin(x, y, C=data, gridsize=[16,11], cmap=thiscmap) # better
plt.colorbar() # useful on thematic map
plt.show()
The projection you use (epsg:3413) is stereographic projection which has large areal distortion. More appropriate projection for thematic mapping in this case is Lambert Azimuthal Equal Area.
The resulting plot:
There are two points on the stereographic projection as shown in the figure:
These points are supposed to be on the end points of a dimeter of a circle. How to draw a circle passing through these two points?
Code for the above plot:
import matplotlib.pylab as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
from scipy.interpolate import splev, splrep
# 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')
# 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')
summerAzi = np.array([0, 360])
summerAlt = np.array([40, 4])
summerX, summerY = myMap(summerAzi, -summerAlt)
summerX_new = np.linspace(summerX.min(), summerX.max(),30)
summerY_smooth = splev(summerX_new, splrep(summerX, summerY, k=1))
myMap.plot(summerX_new, summerY_smooth, 'g')
myMap.plot(summerX, summerY, 'go')
plt.show()
Inbuilt tissot() function is good enough to plot circles on a conformal projections (as in this case). On non-conformal projections, it plots ellipses.
Here the mid point of the tissot indicatrix is (0, -22) in degrees.
Its radius = (40-4)/2 = 18 in degrees.
Number of points = 36 is fine.
The relevant code is:
myMap.tissot(0, -22, 18, 36, \
facecolor='none', \
edgecolor='#ff0000', \
linewidth=1, \
alpha=1)
The circle in this polar representation will not look like a circle on a rectangular grid (i.e. "round"). Apart from that you can draw a circle just as you would on the cartesian plane, starting in polar coordinates, transforming to cartesian coordinates, offset the center and use the plot function.
summerAzi = np.array([0, 360])
summerAlt = -np.array([40, 4])
summerX, summerY = myMap(summerAzi, summerAlt)
phi = np.linspace(0,2.*np.pi)
r = np.abs(np.diff(summerAlt))/2.
x = r*np.cos(phi)
y = -r*np.sin(phi)+summerAlt.mean()
X,Y= myMap(x,y)
myMap.plot(X,Y, color="crimson")
myMap.plot(summerX, summerY, color="gold", marker="o")