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...
Related
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 an algorithm problem. I would like to do the following.
I have a polar plot in theta, r coords as below:
phis = np.linspace(0.01,63,100) # 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)
rangeMap = orbits.range(orbits.h_mission, Y * u.deg)[0].value
fig, ax = plt.subplots(figsize=(8,7),subplot_kw=dict(projection='polar'))
X, Y = np.meshgrid(thetas, phis) # Create a grid over the range of bins for the plot
im = (ax.pcolormesh(thetas,phis, rangeMap, cmap=mpl.cm.jet_r, alpha=0.95,shading='auto') )
ax.set_theta_direction(-1)
ax.set_theta_offset(np.pi / 2.0)
plt.thetagrids([theta * 15 for theta in range(360//15)])
ax.grid(True)
# ax.set_xlabel("")
# ax.set_ylabel("")
# ax.set_xticklabels([])
# ax.set_yticklabels([])
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
## Add colorbar
gc.collect()
Notice that I plot with theta and phis as opposed to X,Y ... this seems to work (I don't have to use X,Y).
The theta and r correspond to pointing vectors from a spacecraft that intersect the earth. As such, I can transform those coordinates, (theta,r), into lon/lat on the globe for cartopy.
However, pcolormesh, obviously uses polar coordinates. And although I can translate each PAIR of theta,r into lon/lat, it doesn't help. I thought I could just substitute the theta, phi for lon,lat but that doesn't seem to work (keeping my Z = rangeMap values unchanged). i.e. - This doesn't work
resolution = '110m'
lls = [orbits.sphere_intersect(SV_pos_vec, SV_vel_vec, az << u.deg, el << u.deg, lonlat=True)[:2] for az in thetas for el in phis] # This returns a long/lat array for az/el
gd = Geodesic() # from cartopy
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.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_0_countries', resolution, edgecolor='black', facecolor='none'))
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'lakes', resolution, edgecolor='none', facecolor=cfeature.COLORS['water']), alpha=0.5)
im = (ax.pcolormesh(thetas, phis, rangeMap, cmap=mpl.cm.jet_r, alpha=0.95,shading='auto') )
fig.tight_layout()
# plt.savefig('PlotBeamInfo.pdf', dpi=96)
gc.collect()
The approach gives me something like:
Here's my goal: project a theta,r polar plot onto a cartopy map. Anyone have ideas on how to do this? How do I project a polar plot onto the globe?
You can make up any data you like for the rangeMap ... it doesn't matter... its the x,y for pcolormesh that I can't figure out.
I am trying to plot a map of a sphere with an orthographic projection of the Northern (0-40N) and Southern (0-40S) hemispheres, and a Mollweide projection of the central latitudes (60N-60S). I get the following plot:
which shows a problem: there is a square bounding box with cut corners around the hemispherical plots. Note that the extent of the colours is the same for all three plots (-90 to 90).
When I plot a hemisphere without limiting its extent, however, I get a round bounding box, as expected from an orthographic projection:
Using plt.xlim(-90,-50) results in a vertical stripe, and plt.ylim(-90,-50) in a horizontal stripe, so that is no solution either.
How can I limit the latitudinal extent of my orthographic projection, whilst maintaining the circular bounding box?
The code to produce above graphs:
import numpy as np
from matplotlib import pyplot as plt
import cartopy.crs as ccrs
# Create dummy data, latitude from -90(S) to 90 (N), lon from -180 to 180
theta, phi = np.meshgrid(np.arange(0,180),np.arange(0,360));
theta = -1*(theta.ravel()-90)
phi = phi.ravel()-180
radii = theta
# Make masks for hemispheres and central
mask_central = np.abs(theta) < 60
mask_north = theta > 40
mask_south = theta < -40
data_crs= ccrs.PlateCarree() # Data CRS
# Grab map projections for various plots
map_proj = ccrs.Mollweide(central_longitude=0)
map_proj_N = ccrs.Orthographic(central_longitude=0, central_latitude=90)
map_proj_S = ccrs.Orthographic(central_longitude=0, central_latitude=-90)
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 2,projection=map_proj)
im1 = ax1.scatter(phi[mask_central],
theta[mask_central],
c = radii[mask_central],
transform=data_crs,
vmin = -90,
vmax = 90,
)
ax1.set_title('Central latitudes')
ax_N = fig.add_subplot(2, 2, 1, projection=map_proj_N)
ax_N.scatter(phi[mask_north],
theta[mask_north],
c = radii[mask_north],
transform=data_crs,
vmin = -90,
vmax = 90,
)
ax_N.set_title('Northern hemisphere')
ax_S = fig.add_subplot(2, 2, 2, projection=map_proj_S)
ax_S.scatter(phi[mask_south],
theta[mask_south],
c = radii[mask_south],
transform=data_crs,
vmin = -90,
vmax = 90,
)
ax_S.set_title('Southern hemisphere')
fig = plt.figure()
ax = fig.add_subplot(111,projection = map_proj_N)
ax.scatter(phi,
theta,
c = radii,
transform=data_crs,
vmin = -90,
vmax = 90,
)
ax.set_title('Northern hemisphere')
plt.show()
(1). In all of your plots, when you use scatter(), the size of the scatter points should be defined with proper s=value, otherwise the default value is used. I use s=0.2 and the resulting plots look better.
(2). For 'Central latitudes' case, you need to specify correct y-limits with set_ylim(). This involves the computation of them. The use of transform_point() is applied here.
(3). For the remaining plots that require elimination of unneeded features, proper circular clip paths can be used. Their perimeters are also used to plot as map boundaries in both cases. Their existence may cause trouble plotting other map features (such as coastlines) as I demonstrate with the code and its output.
# original is modified and extended
import numpy as np
from matplotlib import pyplot as plt
import cartopy.crs as ccrs
import matplotlib.patches as mpatches # need it to create clip-path
# Create dummy data, latitude from -90(S) to 90 (N), lon from -180 to 180
theta, phi = np.meshgrid(np.arange(0,180),np.arange(0,360));
theta = -1*(theta.ravel()-90)
phi = phi.ravel()-180
radii = theta
# Make masks for hemispheres and central
mask_central = np.abs(theta) < 60
mask_north = theta > 40
mask_south = theta < -40
data_crs= ccrs.PlateCarree() # Data CRS
# Grab map projections for various plots
map_proj = ccrs.Mollweide(central_longitude=0)
map_proj_N = ccrs.Orthographic(central_longitude=0, central_latitude=90)
map_proj_S = ccrs.Orthographic(central_longitude=0, central_latitude=-90)
# 'Central latitudes' plot
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 2, projection=map_proj)
# Note: Limits of plot depends on plotting data, but not exact!
im1 = ax1.scatter(phi[mask_central],
theta[mask_central],
s = 0.2,
c = radii[mask_central],
transform=data_crs,
vmin = -90,
vmax = 90,
)
# compute y limits
_, y_btm = map_proj.transform_point(0, -60, ccrs.Geodetic())
_, y_top = map_proj.transform_point(0, 60, ccrs.Geodetic())
# apply y limits
ax1.set_ylim(y_btm, y_top)
ax1.coastlines(color='k', lw=0.35)
ax1.set_title('Central latitudes')
ax_N = fig.add_subplot(2, 2, 1, projection=map_proj_N)
ax_N.scatter(phi[mask_north],
theta[mask_north],
s = 0.1, # not mandatory
c = radii[mask_north],
transform=data_crs,
vmin = -90,
vmax = 90,
)
# use a circular path as map boundary
clip_circle = mpatches.Circle(xy=[0,0], radius=4950000, facecolor='none', edgecolor='k')
ax_N.add_patch(clip_circle)
ax_N.set_boundary(clip_circle.get_path(), transform=None, use_as_clip_path=True)
# with `use_as_clip_path=True` the coastlines do not appear
ax_N.coastlines(color='k', lw=0.75, zorder=13) # not plotted!
ax_N.set_title('Northern hemisphere1')
# 'Southern hemisphere' plot
ax_S = fig.add_subplot(2, 2, 2, projection=map_proj_S)
ax_S.scatter(phi[mask_south],
theta[mask_south],
s = 0.02,
c = radii[mask_south],
transform=data_crs,
vmin = -90,
vmax = 90,
)
clip_circle = mpatches.Circle(xy=[0,0], radius=4950000, facecolor='none', edgecolor='k')
ax_S.add_patch(clip_circle)
# applying the clip-circle as boundary, but not use as clip-path
ax_S.set_boundary(clip_circle.get_path(), transform=None, use_as_clip_path=False)
# with `use_as_clip_path=False` the coastlines is plotted, but goes beyond clip-path
ax_S.coastlines(color='k', lw=0.75, zorder=13)
ax_S.set_title('Southern hemisphere')
# 'Northern hemisphere2' plot, has nice circular limit
fig = plt.figure()
ax = fig.add_subplot(111,projection = map_proj_N)
ax.scatter(phi,
theta,
s = 0.2,
c = radii,
transform=data_crs,
vmin = -90,
vmax = 90,
)
ax.coastlines(color='k', lw=0.5, zorder=13)
ax.set_title('Northern hemisphere2')
ax.set_global()
plt.show()
The output plot:
The usual axes in matplotlib are rectangular. For some projections in cartopy however, it does not make sense to show a rectangle where part of it isn't even defined. Those regions are encircled. This way it is ensured that the axes content always stays within the border.
If you do not want this, but instead use a circular border, even if part of the plot would potentially lie outside the circle, you would define that circle manually:
import numpy as np
from matplotlib import pyplot as plt
import cartopy.crs as ccrs
# Create dummy data, latitude from -90(S) to 90 (N), lon from -180 to 180
theta, phi = np.meshgrid(np.arange(0,180),np.arange(0,360));
theta = -1*(theta.ravel()-90)
phi = phi.ravel()-180
# Make mask for hemisphere
mask_north = theta > 40
data_crs= ccrs.PlateCarree() # Data CRS
# Grab map projections for various plots
map_proj_N = ccrs.Orthographic(central_longitude=0, central_latitude=90)
fig = plt.figure()
ax_N = fig.add_subplot(121, projection=map_proj_N)
ax_N.scatter(phi[mask_north], theta[mask_north],
c = theta[mask_north], transform=data_crs,
vmin = -90, vmax = 90)
ax_N.set_title('Northern hemisphere')
### Remove undesired patch
ax_N.patches[0].remove()
### Create new circle around the axes:
circ = plt.Circle((.5,.5), .5, edgecolor="k", facecolor="none",
transform=ax_N.transAxes, clip_on=False)
ax_N.add_patch(circ)
#### For comparisson, plot the full data in the right subplot:
ax = fig.add_subplot(122,projection = map_proj_N)
ax.scatter(phi, theta, c = theta,
transform=data_crs, vmin = -90, vmax = 90)
ax.set_title('Northern hemisphere')
plt.show()
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 need to plot a square map using Cartopy. I currently use the following code for my map:
plt.figure(figsize = (15, 15))
img = cimgt.GoogleTiles()
ax = plt.axes(projection = img.crs)
ax.set_extent((d['longitude'].min() - 0.05, d['longitude'].max() + 0.05,
d['latitude'].min() - 0.05, d['latitude'].max() + 0.05))
ax.add_image(img, 10, interpolation = 'bicubic')
plt.scatter(d['longitude'], d['latitude'], transform = ccrs.PlateCarree(),
c = '#E8175D', s = 14)
This works fine, except for the fact that the map isn't square. Instead, it's just fitted into the (15, 15) square of the plot.
I would like to add a bit more map to the left and to the right to make the plot perfectly square without distorting it. Simply setting the extent to the same difference on latitude and longitude doesn't do the job, because latitude and longitude cover different distances in Google's (and most other) map projections. I also found this post, but from what I get, the intent here is to distort the map.
I hope someone has an idea how to do this. It seems that Cartopy is not very intuitive in this regard.
To get square extent you need to specify it in map projection coordinates. That involves some coordinate transformation. Here is the code snippet that you need.
# crs of your choice
crg = cimgt.StamenTerrain().crs # or cimgt.GoogleTiles().crs
# set map limits, in degrees
lonmin, lonmax = -22, -15
latmin, latmax = 63, 65
# do coordinate transformation
LL = crg.transform_point(lonmin, latmin, ccrs.Geodetic())
UR = crg.transform_point(lonmax, latmax, ccrs.Geodetic())
EW = UR[0] - LL[0]
SN = UR[1] - LL[1]
# get side of the square extent (in map units, usually meters)
side = max(EW, SN) # larger value is in effect
mid_x, mid_y = LL[0]+EW/2.0, LL[1]+SN/2.0 # center location
# the extent preserves the center location
extent = [mid_x-side/2.0, mid_x+side/2.0, mid_y-side/2.0, mid_y+side/2.0]
# this sets square extent
# crs=crg signifies that projection coordinates is used in extent
ax.set_extent(extent, crs=crg)
Hope it helps.
Edit
Here is a complete working code and its resulting map.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import cartopy.io.img_tiles as cimgt
def make_map(projection=ccrs.PlateCarree()):
fig, ax = plt.subplots(figsize=(10, 10),
subplot_kw=dict(projection=projection))
gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
return fig, ax
request = cimgt.StamenTerrain() # very responsive
crg = request.crs #crs of the projection
fig, ax = make_map(projection = crg)
# specify map extent here
lonmin, lonmax = -22, -15
latmin, latmax = 63, 65
LL = crg.transform_point(lonmin, latmin, ccrs.Geodetic())
UR = crg.transform_point(lonmax, latmax, ccrs.Geodetic())
EW = UR[0] - LL[0]
SN = UR[1] - LL[1]
side = max(EW,SN)
mid_x, mid_y = LL[0]+EW/2.0, LL[1]+SN/2.0 #center location
extent = [mid_x-side/2.0, mid_x+side/2.0, mid_y-side/2.0, mid_y+side/2.0] # map coordinates, meters
ax.set_extent(extent, crs=crg)
ax.add_image(request, 8)
# add a marker at center of the map
plt.plot(mid_x, mid_y, marker='o', \
color='red', markersize=10, \
alpha=0.7, transform = crg)
plt.show()