Cartopy figure for high latitude with edges parallel to latitude and longitude, e.g., not rectangular - projection

I'm trying to create a Cartopy map for the sub-polar region around Iceland. What I would like is a non-rectangular figure where the edges are parallel to the lines of longitude and latitude, like this figure created using PyGMT:
I've tried various Cartopy projections, but all result in a rectangular figure, e.g.,
import matplotlib.pyplot as plt
import cartopy
import cartopy.crs as ccrs
fig = plt.figure()
proj = ccrs.LambertConformal(central_longitude=-25, central_latitude=58.0)
ax = plt.axes(projection = proj)
ax.set_extent((-45, -5, 45, 70))
ax.gridlines()
ax.add_feature(cartopy.feature.LAND, zorder=1, edgecolor='black')
There are reasons for not using PyGMT (I want to plot surface velocities using quiver, plus the extensive learning curve), so I'd like to know if it's possible to achieve the same result in cartopy.
Thanks

You can use the set_boundary method of an axes for this. When specifying it as lon/lat, for a different projection, you should sample some points accross the boundary to approximate the true curvature of the projection (compared to lon/lat). The example below takes 20 points on each edge.
Note that the shape of this boundary can be anything you want, it doesn't have to match the projection or lon/lat lines etc.
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import cartopy
import cartopy.crs as ccrs
import numpy as np
proj = ccrs.LambertConformal(central_longitude=-25, central_latitude=58.0)
fig, axs = plt.subplots(
1,2, figsize=(8, 3), facecolor="w",
subplot_kw=dict(projection=proj),
)
n = 20
aoi = mpath.Path(
list(zip(np.linspace(-45,-5, n), np.full(n, 70))) + \
list(zip(np.full(n, -5), np.linspace(70, 45, n))) + \
list(zip(np.linspace(-5, -45, n), np.full(n, 45))) + \
list(zip(np.full(n, -45), np.linspace(45, 70, n)))
)
axs[1].set_boundary(aoi, transform=ccrs.PlateCarree())
for ax in axs:
ax.set_extent((-45, -5, 45, 70))
ax.add_feature(cartopy.feature.LAND, zorder=1, edgecolor='k')
gl = ax.gridlines(
draw_labels=True, rotate_labels=False,
x_inline=False, y_inline=False,
)

The answer by #Rutger_Kassies is great. However there is an alternative that the readers should consider if he/she wants to try a different approach.
import cartopy.crs as ccrs
import cartopy
import matplotlib.pyplot as plt
import matplotlib.path as mpath
# The lat-long proj
noProj = ccrs.PlateCarree()
# The projection of the map
myProj = ccrs.LambertConformal(central_longitude=-25, central_latitude=58.0)
myProj._threshold = myProj._threshold/20. # Set for higher precision of the projection
ax = plt.axes(projection=myProj)
# This plots parallel and meridian arcs around a target area that will be used ...
# as the map boundary
[ax_hdl] = ax.plot([-45, -5, -5, -45, -45], [45, 45, 70, 70, 45],
color='black', linewidth=0.5, marker='none',
transform=noProj)
# Get the `Path` of the plot
tx_path = ax_hdl._get_transformed_path()
path_in_data_coords, _ = tx_path.get_transformed_path_and_affine()
# Use the path's vertices to create a polygon
polygon = mpath.Path( path_in_data_coords.vertices )
ax.set_boundary(polygon) #This masks-out unwanted part of the plot
ax.gridlines(draw_labels=True, x_inline=False, y_inline=False)
ax.add_feature(cartopy.feature.OCEAN, linewidth=.3, color='lightblue')
ax.add_feature(cartopy.feature.LAND, zorder=1, edgecolor='black')
ax.title.set_text("Meridians and Parallels as Boundary")
plt.show()
You can change some parameters in the code, for example, the type of arcs that are used as the map's boundary.
The second plot is obtained by changing these parts of code:
1. `transform=noProj` to
`transform=ccrs.Geodetic()`
2. `ax.title.set_text("Meridians and Parallels as Boundary")` to
`ax.title.set_text("Great-circle Arcs as Boundary")`
I believe that when the top edge of the map reaches high latitude, using parallel of latitude as the boundary is not optimum. Straight line may be better, but in some situation great circle arc should be considered.

Related

How to get the length of lines representing edges in the plot of graph after layout out using networkx

For a graph in networkx, I have made a layout to draw a network graph using code below:
data = pd.read_csv('data\\email-dept3.csv')
edges = [edge for edge in zip(data['source'],data['target'])]
print(len(edges))
G = nx.Graph()
G.add_edges_from(edges)
node_pos = nx.kamada_kawai_layout(G)
#I want to get the edge length as one attributes, but I don't know how to code this function
edge_length = calculate_edge_length()
nx.draw_networkx_nodes(G,node_pos,**options)#draw nodes
[nx.draw_networkx_edges(G,node_pos,edgelist=[key],alpha=np.amin([1,value*100]),width=2) for key,value in cent.items()]
plt.show()
And the result is:
What I want to do is get the every edge's length in this graph. Because after layout, every node has a position in screen, and the edge has its length according to its two nodes' position. But in networkx's API, I can't find the method to get the edge's length. And I also don't know how to calculate this value.
If you need more information, please contact me.
I am trying all kinds of methods to adjust the transparency of edges. The length of line is one of my consideration.
Interesting idea! Seems like a worthwhile experiment; I'll let you decide if it works well or not. :-)
But in networkx's API, I can't find the method to get the edge's length
I think you have to compute them yourself. Fortunately, that's not too hard. Here's an example.
import numpy as np
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = (10,10)
def example_graph():
"""
Return the classic Karate Club network, but give text labels to the nodes.
"""
labels = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJZKLMNOPQRSTUVWXYZ'
kg = nx.karate_club_graph()
edges = [(labels[i], labels[j]) for i,j in kg.edges()]
G = nx.Graph()
G.add_edges_from(edges)
return G
# Test network
G = example_graph()
# Determine layout node positions
node_pos = nx.kamada_kawai_layout(G)
# Determine edge distances (from the node positions)
node_pos_df = pd.DataFrame(node_pos.values(), columns=['x', 'y'], index=node_pos.keys())
node_pos_df = node_pos_df.rename_axis('label').sort_index()
edges = np.array(G.edges())
u_pos = node_pos_df.loc[edges[:, 0]].values
v_pos = node_pos_df.loc[edges[:, 1]].values
distances = np.linalg.norm(u_pos - v_pos, axis=1)
## Optional: Add the distances as edge attributes
#edge_distances = {(u,v): d for (u,v), d in zip(G.edges(), distances)}
#nx.set_edge_attributes(G, edge_distances, "layout_distance")
# Compute alpha: Set 0.15 as minimum alpha, 1.0 as maximum alpha
d_min, d_max = distances.min(), distances.max()
alphas = 1.0 - 0.85 * (distances - d_min) / (d_max - d_min)
# Draw graph
nx.draw_networkx_nodes(G, node_pos)
nx.draw_networkx_edges(G, node_pos, edgelist=G.edges(), alpha=alphas, width=2)
plt.show()

Networkx color graph edges based on weight value

I have a dataframe with some columns. I've created a Networkx undirected graph and I want to draw the corresponding network. I need also to change the color of the edges based on the weights of those edges, so I used a LinearColorMap. This is the code:
#my other stuff
cmap = LinearSegmentedColormap.from_list('RwG',['red','white','green'])
nx.draw(G, poss, node_size=1500, node_color='lightgrey', edgelist=edges,edge_color=weights,
width=list(map(lambda number: number * 16 / m, x)), edge_cmap=cmap)
However, I need to normalize my color map so that the white color is centered on a specific value (e.g. -76). The weights are in the [-60,-100] range.
How can I achieve that ?
Visually:
If you pass in a matplotlib colormap to networkx, networkx will normalize your numerical color argument linearly between the minimum and maximum value.
Personally, I think this is a somewhat shortsighted design decision but it is what it is: your non-linear mapping of weights to color is simply not possible.
You can, however, pre-compute the colors and pass those in instead (similarly how you are precomputing edge widths):
#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
def weight_to_color(w, threshold):
if w < threshold:
return 'green'
elif np.isclose(w , threshold):
return 'white'
else: # x > threshold
return 'red'
weight_matrix = 40 * np.random.rand(10, 10) - 100
g = nx.from_numpy_array(weight_matrix)
weights = [g.edges[edge]['weight'] for edge in g.edges]
nx.draw(g, node_color='lightgray', edgelist=g.edges, edge_color=[weight_to_color(w, -76) for w in weights])
plt.show()

3-dimensional graph LightGraphs/GraphPlot/Julia or Networkx/Python

I am wondering whether it is possible to plot a vertical bar over a 2-dimensional representation of a graph. Say I have a tree and I want to associate with any node a "potential" which can be represented as a vertical bar.
NetworkX can do that using the matplotlib drawing tools because the result is a matplotlib figure on which you can use matplotlib to draw anything else you'd like on top of the networkx drawing of the graph.
nx.draw(G)
mpl.plot([xpt, xpt], [ymin, ymax], '--b')
mpl.show()
This is a minimal example which does what I was looking for (in Python):
import networkx as nx
import random
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
degree = 3
N = 10
g = nx.random_regular_graph(degree, N)
fig = plt.figure(figsize=(10,7))
ax = Axes3D(fig)
for i,j in enumerate(g.edges()):
x = np.array((positions[j[0]][0], positions[j[1]][0]))
y = np.array((positions[j[0]][1], positions[j[1]][1]))
ax.plot(x, y, c='black', alpha=0.5)
for key, value in positions.items():
xi = value[0]
yi = value[1]
# Scatter plot
ax.scatter(xi, yi, c= 'red')
ax.bar3d(xi, yi, 0, 0.01, 0, random.random(), shade=False)
ax.set_axis_off()
It generates this kind of plot, which can be useful to represent additional information on a graph

Digitize a filled contour plot

I have an RGB bitmap. Actually it is the contour plot of some scalar field plotted with the 'jet' colormap. I need to reverse the bitmap and obtain the source data. Is there a ready-to-use ond open-source tool for that? Python module is OK too.
Well, as nobody did it, this is a lasy algorithm that do the job:
import numpy as np
import scipy.misc
import matplotlib.pyplot as plt
## The digitized field will be scaled to range (0,1)
scale = np.linspace(0.0, 1.0, 300)
## Palette is a curve in RGB space
jet = plt.cm.get_cmap('jet')
palette = 255.0 * np.array([ jet(s)[:3] for s in scale ])
## Read the field as RGB image
field_0 = scipy.misc.imread('field.png')[:,:,:3]
ny, nx, _ = field_0.shape
## Use Euclidian norm to find a closest point in the palette
dist = lambda v : np.array([ np.linalg.norm(p - v) for p in palette ])
field = np.array([ [ scale[np.argmin(dist(field_0[i,j]))]
for j in range(nx) ]
for i in range(ny)[::-1] ])
## Plot
fig, ax = plt.subplots(1, 2)
ax[0].imshow(field_0)
ax[1].contourf(field, cmap='gray')
plt.show()
Thanks to everyone who cared.

griddata and polar plots

I am using matplotlib to fit some data to a grid and plot it as a polar projection. Something like in the example below. However, I want it to be smooth where the edges of the plot meet at 0/360 degrees. Anyone know how I do this??
from pylab import *
import random
x = linspace(0, 360, 361).astype(int)
x = x*pi/180
y = linspace(0.05, 0.5, 800)
xgrid, ygrid = meshgrid(x, y)
baz = []
for c in range(2000): baz.append(random.randint(0,360))
freq = rand(len(baz))
pwr = rand(len(baz))
zgrid = griddata(baz,freq,pwr, xgrid, ygrid)
subplot(111, polar=True)
pcolormesh(xgrid, ygrid, zgrid)
show()
Also the data I am working with has a gap due to the mask created by griddata (I use griddata as above but then sum many grids in a loop). I would like to fill the missing segment (see attached fig), does anyone know how to do this?
thanks
Dave
If you know which grids come together at the 0/360 degree position you could just concatenate them and do a spline interpolation on it (scipy interpolation).
For your second problem I am not sure but how about creating your grids in polar coordinates? Would this solve your problem?
Kind regards

Resources