How to add elevation to a buffer polygon in geopandas - geopandas

How would I add a zero elevation value to each xy component of a buffer polygon, i.e.
Transform POLYGON ((0.20000 0.00000, 0.19904 -0.01960, ...)) into POLYGON ((0.20000 0.00000 0, 0.19904 -0.01960 0, ...))

I think you're asking how you can add a third dimension to your shapely objects and have them set to zero by default.
Here's a way to do so:
import shapely.ops
def add_zero_z(geom):
def _add_zero_z(x, y):
return x, y, [0 for _ in x]
return shapely.ops.transform(_add_zero_z, geom)
Here's an example of how to use it:
import shapely.geometry
x_2D = shapely.geometry.Point((0,0))
print(x_2D.wkt)
# POINT (0 0)
x_3D = add_zero_z(x_2D)
print(x_3D.wkt)
# POINT (0 0 0)
You can also apply the function to a whole GeoDataFrame:
import geopandas as gpd
gdf = gpd.GeoDataFrame({'id':[1,2],
'geometry':[shapely.geometry.Point((0,0)),
shapely.geometry.Point((1,1))]},
geometry='geometry')
# Applying the function to the "geometry" column and replacing it
gdf['geometry'] = gdf['geometry'].apply(lambda geom: add_zero_z(geom))
print(gdf)
# id geometry
# 0 1 POINT Z (0.00000 0.00000 0.00000)
# 1 2 POINT Z (1.00000 1.00000 0.00000)
Credit
My idea for this answer came from this post by user mikewatt.

Related

convert hued displot of X to plot of hue vs mode(X given hue)?

I have a Seaborn displot with a hued variable:
For each hued variable, I want to extract the mode of the density estimate and then plot each hue variable versus its mode, like so:
How do I do this?
You can use scipy.stats.gaussian_kde to create the density estimation function. And then call that function on an array of x-values to calculate its maximum.
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
df = pd.DataFrame({'x': np.random.normal(0.001, 1, 1300).cumsum() + 30,
'hue': np.repeat(np.arange(0.08, 0.20001, 0.01), 100).round(2)})
g = sns.displot(df, x='x', hue='hue', palette='turbo', kind='kde', fill=True, height=6, aspect=1.5)
plt.show()
from scipy.stats import gaussian_kde
from matplotlib.cm import ScalarMappable
fig, ax = plt.subplots(figsize=(10, 6))
hues = df['hue'].unique()
num_hues = len(hues)
colors = sns.color_palette('turbo', num_hues)
xmin, xmax = df['x'].min(), df['x'].max()
xs = np.linspace(xmin, xmax, 500)
for hue, color in zip(hues, colors):
data = df[df['hue'] == hue]['x'].values
kde = gaussian_kde(data)
mode_index = np.argmax(kde(xs))
mode_x = xs[mode_index]
sns.scatterplot(x=[hue], y=[mode_x], color=color, s=50, ax=ax)
cmap = sns.color_palette('turbo', as_cmap=True)
norm = plt.Normalize(hues.min(), hues.max())
plt.colorbar(ScalarMappable(cmap=cmap, norm=norm), ax=ax, ticks=hues)
plt.show()
Here is another approach, extracting the kde curves. It uses the legend of the kde plot to get the correspondence between the curves and the hue values. sns.kdeplot is the axes-level function used by sns.displot(kind='kde'). fill=False creates lines instead of filled polygons for the curves, for which the values are easier to extract. (ax1.fill_between can fill the curves during a second pass). The x and y axes of the second plot are switched to align the x-axes of both plots.
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
df = pd.DataFrame({'x': np.random.normal(0.007, 0.1, 1300).cumsum() + 30,
'hue': np.repeat(np.arange(0.08, 0.20001, 0.01), 100).round(2)})
fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(12, 10), sharex=True)
sns.kdeplot(data=df, x='x', hue='hue', palette='turbo', fill=False, ax=ax1)
hues = [float(txt.get_text()) for txt in ax1.legend_.get_texts()]
ax2.set_yticks(hues)
ax2.set_ylabel('hue')
for hue, line in zip(hues, ax1.lines[::-1]):
color = line.get_color()
x = line.get_xdata()
y = line.get_ydata()
ax1.fill_between(x, y, color=color, alpha=0.3)
mode_ind = np.argmax(y)
mode_x = x[mode_ind]
sns.scatterplot(x=[mode_x], y=hue, color=color, s=50, ax=ax2)
sns.despine()
plt.tight_layout()
plt.show()

Plot LINESTRING Z from GeoDataFrame using pydeck's PathLayer (or TripLayer)

I have a geodataframe with LINESTRING Z geometries:
TimeUTC
Latitude
Longitude
AGL
geometry
0
2021-06-16 00:34:04+00:00
42.8354
-70.9196
82.2
LINESTRING Z (42.83541343273769 -70.91961015378617 82.2, 42.83541343273769 -70.91961015378617 82.2)
1
2021-06-14 13:32:18+00:00
42.8467
-70.8192
66.3
LINESTRING Z (42.84674080836037 -70.81919357049679 66.3, 42.84674080836037 -70.81919357049679 66.3)
2
2021-06-18 23:56:05+00:00
43.0788
-70.7541
0.9
LINESTRING Z (43.07882882269921 -70.75414567194126 0.9, 43.07884601143309 -70.75416286067514 0, 43.07885174101104 -70.75416286067514 0, 43.07884028185512 -70.75415713109717 0, 43.07884601143309 -70.75414567194126 0, 43.07884601143309 -70.75414567194126 0)
I can plot the component points using pydeck's ScatterplotLayer using the raw
(not geo) dataframe but I need to also plot the full, smooth, track.
I've tried this:
layers = [
pdk.Layer(
type = "PathLayer",
data=tracks,
get_path="geometry",
width_scale=20,
width_min_pixels=5,
get_width=5,
get_color=[180, 0, 200, 140],
pickable=True,
),
]
view_state = pdk.ViewState(
latitude=gdf_polygon.centroid.x,
longitude=gdf_polygon.centroid.y,
zoom=6,
min_zoom=5,
max_zoom=15,
pitch=40.5,
bearing=-27.36)
r = pdk.Deck(layers=[layers], initial_view_state=view_state)
return(r)
Which silently fails. Try as I might, I cannot find a way to convert the
LINESTRING Z's (and I can do without the Z component if need be) to an object
that pydeck will accept.
I found a way to extract the info needed from GeoPandas and make it work in pydeck. You just need to apply a function that extracts the coordinates from the shapely geometries as a list. Here is a fully reproducible example:
import shapely
import numpy as np
import pandas as pd
import pydeck as pdk
import geopandas as gpd
linestring_a = shapely.geometry.LineString([[0,1,2],
[3,4,5],
[6,7,8]])
linestring_b = shapely.geometry.LineString([[7,15,1],
[8,14,2],
[9,13,3]])
multilinestring = shapely.geometry.MultiLineString([[[10,11,2],
[13,14,5],
[16,17,8]],
[[19,10,11],
[12,15,4],
[10,13,0]]])
gdf = gpd.GeoDataFrame({'id':[1,2,3],
'geometry':[linestring_a,
linestring_b,
multilinestring],
'color_hex':['#ed1c24',
'#faa61a',
'#ffe800']})
# Function that transforms a hex string into an RGB tuple.
def hex_to_rgb(h):
h = h.lstrip("#")
return tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))
# Applying the HEX-to-RGB function above
gdf['color_rgb'] = gdf['color_hex'].apply(hex_to_rgb)
# Function that extracts the 2d list of coordinates from an input geometry
def my_geom_coord_extractor(input_geom):
if (input_geom is None) or (input_geom is np.nan):
return []
else:
if input_geom.type[:len('multi')].lower() == 'multi':
full_coord_list = []
for geom_part in input_geom.geoms:
geom_part_2d_coords = [[coord[0],coord[1]] for coord in list(geom_part.coords)]
full_coord_list.append(geom_part_2d_coords)
else:
full_coord_list = [[coord[0],coord[1]] for coord in list(input_geom.coords)]
return full_coord_list
# Applying the coordinate list extractor to the dataframe
gdf['coord_list'] = gdf['geometry'].apply(my_geom_coord_extractor)
gdf_polygon = gdf.unary_union.convex_hull
# Establishing the default view for the pydeck output
view_state = pdk.ViewState(latitude=gdf_polygon.centroid.coords[0][1],
longitude=gdf_polygon.centroid.coords[0][0],
zoom=4)
# Creating the pydeck layer
layer = pdk.Layer(
type="PathLayer",
data=gdf,
pickable=True,
get_color='color_rgb',
width_scale=20,
width_min_pixels=2,
get_path="coord_list",
get_width=5,
)
# Finalizing the pydeck output
r = pdk.Deck(layers=[layer], initial_view_state=view_state, tooltip={"text": "{id}"})
r.to_html("path_layer.html")
Here's the output it yields:
Big caveat
It seems like pydeck isn't able to deal with MultiLineString geometries. Notice how, in the example above, my original dataframe had 3 geometries, but only 2 lines were drawn in the screenshot.

Using an image as marker on a map in Geopandas

I am trying to make a map of a wind farm. I would like to use an image of a wind turbine that I found online as the marker to show the locations of the turbines on the map. I have two problems:
although i was able to import the image using the following code below, I cannot visualize it...
I do not know how to define it as the marker I would like to use (which by the way, will require that I resize it...)
Here is my attempt:
from IPython.display import Image
im = Image('path/turb.png')
display (im)
Out:
But it should be this:
As for the mapping, I tried the following... without success:
fig, ax = plt.subplots(figsize = (10,10))
turb.plot(ax=ax, marker = im)
plt.show()
I got this error:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-53-d26dd941b982> in <module>()
1 minx, miny, maxx, maxy = ri.geometry.total_bounds
2 fig, ax = plt.subplots(figsize = (10,10))
----> 3 turb.plot(ax=ax, marker = im)
4 ri.plot(ax=ax)
5 #ax.set_xlim(minx, maxx) # added/substracted value is to give some margin around total bounds
11 frames
/usr/local/lib/python3.7/dist-packages/numpy/core/_asarray.py in asarray(a, dtype, order)
81
82 """
---> 83 return array(a, dtype, copy=False, order=order)
84
85
TypeError: float() argument must be a string or a number, not 'Image'
Here is a demo code and its resulting plot for the question.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
path = "./images/YCcBa.png"
image = plt.imread(path)[10:10+128, 10:10+108]
def plot_images(x, y, image, ax):
for xi, yi in zip(x,y):
im = OffsetImage(image, zoom=72/ax.figure.dpi)
im.image.axes = ax
# create bbox for the images
ab = AnnotationBbox(im, (xi,yi), frameon=False, pad=0.0)
ax.add_artist(ab)
x = np.arange(10)
y = np.random.rand(10)
fig, ax = plt.subplots(figsize = (8,8))
plot_images(x, y, image, ax)
ax.plot(x, y) # plot lines connecting (x,y)'s
plt.show()

Matplotlib animate space vs time plot

I'm currently working on traffic jams analysis and was wondering if there's a way to animate the generation of a plot of such jams.
A plot of this things grow from up to the lower end of the figure, each 'row' is a time instance. The horizontal axis is just the road indicating at each point the position of each vehicle and, with a certain numeric value, the velocity of it. So applying different colors to different velocities, you get a plot that shows how a jam evolves through time in a given road.
My question is, how can I use matplotlib to generate an animation of each instance of the road in time to get such a plot?
The plot is something like this:
I'm simulating a road with vehicles with certain velocities through time, so I wish to animate a plot showing how the traffic jams evolve...
EDIT:
I add some code to make clear what I'm already doing
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation, rc
plt.rcParams['animation.ffmpeg_path'] = u'/usr/bin/ffmpeg'
# model params
vmax = 5
lenroad = 50
prob = 0.4
# sim params
numiters = 10
# traffic model
def nasch():
gaps = np.full(road.shape, -1)
road_r4 = np.full(road.shape, -1)
for n,x in enumerate(road):
if x > -1:
d = 1
while road[(n+d) % len(road)] < 0:
d += 1
d -= 1
gaps[n] = d
road_r1 = np.where(road!=-1, np.minimum(road+1, vmax), -1)
road_r2 = np.where(road_r1!=-1, np.minimum(road_r1, gaps), -1)
road_r3 = np.where(road_r2!=-1, np.where(np.random.rand() < prob, np.maximum(road-1, 0), road), -1)
for n,x in enumerate(road_r3):
if x > -1:
road_r4[(n+x) % len(road_r3)] = x
return road_r4
def plot_nasch(*args):
road = nasch()
plot.set_array([road])
return plot,
# init road
road = np.random.randint(-10, vmax+1, [lenroad])
road = np.where(road>-1, road, -1)
# simulate
fig = plt.figure()
plot = plt.imshow([road], cmap='Pastel2', interpolation='nearest')
for i in range(numiters):
ani = animation.FuncAnimation(fig, plot_nasch, frames=100, interval=500, blit=True)
plt.show()
And I get the following figure, just one road instead of each road painted at the bottom of the previous one:
This is possibly what you want, although I'm not sure why you want to animate the time, since time is already one of the axes in the plot.
The idea here is to store the simulation results of a time-step row by row in an array and replot this array. Thereby previous simulation results are not lost.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation, rc
# model params
vmax = 5
lenroad = 50
prob = 0.4
# sim params
numiters = 25
# traffic model
def nasch():
global road
gaps = np.full(road.shape, -1)
road_r4 = np.full(road.shape, -1)
for n,x in enumerate(road):
if x > -1:
d = 1
while road[(n+d) % len(road)] < 0:
d += 1
d -= 1
gaps[n] = d
road_r1 = np.where(road!=-1, np.minimum(road+1, vmax), -1)
road_r2 = np.where(road_r1!=-1, np.minimum(road_r1, gaps), -1)
road_r3 = np.where(road_r2!=-1, np.where(np.random.rand() < prob, np.maximum(road-1, 0), road), -1)
for n,x in enumerate(road_r3):
if x > -1:
road_r4[(n+x) % len(road_r3)] = x
return road_r4
def plot_nasch(i):
print i
global road
road = nasch()
#store result in array
road_over_time[i+1,:] = road
# plot complete array
plot.set_array(road_over_time)
# init road
road = np.random.randint(-10, vmax+1, [lenroad])
road = np.where(road>-1, road, -1)
# initiate array
road_over_time = np.zeros((numiters+1, lenroad))*np.nan
road_over_time[0,:] = road
fig = plt.figure()
plot = plt.imshow(road_over_time, cmap='Pastel2', interpolation='nearest', vmin=-1.5, vmax=6.5)
plt.colorbar()
ani = animation.FuncAnimation(fig, plot_nasch, frames=numiters, init_func=lambda : 1, interval=400, blit=False, repeat=False)
plt.show()

How do I plot two animations in a single plot with matplotlib?

In the following code I have two separate animations and I have plotted them in a two separate subplots. I want both of them to run in a single plot instead of this. I have tried the approach explained below but it is giving me issues as explained below. Please help
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time as t
x = np.linspace(0,5,100)
fig = plt.figure()
p1 = fig.add_subplot(2,1,1)
p2 = fig.add_subplot(2,1,2)
def gen1():
i = 0.5
while(True):
yield i
i += 0.1
def gen2():
j = 0
while(True):
yield j
j += 1
def run1(c):
p1.clear()
p1.set_xlim([0,15])
p1.set_ylim([0,100])
y = c*x
p1.plot(x,y,'b')
def run2(c):
p2.clear()
p2.set_xlim([0,15])
p2.set_ylim([0,100])
y = c*x
p2.plot(x,y,'r')
ani1 = animation.FuncAnimation(fig,run1,gen1,interval=1)
ani2 = animation.FuncAnimation(fig,run2,gen2,interval=1)
fig.show()
I tried creating a single subplot instead of p1 and p2 and have both the plots graphed in that single subplot. That is just plotting one graph and not both of them. As far as I can say it is because one of them is getting cleared right after it is plotted.
How do I get around this problem?
As you do not show the code that is actually producing the problem, it's hard to tell where the problem lies.
But to answer the question of how to animate two lines in the same axes (subplot), we can just get rid of the clear() command and update the lines, instead of producing a new plot for every frame (which is more efficient anyways).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x = np.linspace(0,15,100)
fig = plt.figure()
p1 = fig.add_subplot(111)
p1.set_xlim([0,15])
p1.set_ylim([0,100])
# set up empty lines to be updates later on
l1, = p1.plot([],[],'b')
l2, = p1.plot([],[],'r')
def gen1():
i = 0.5
while(True):
yield i
i += 0.1
def gen2():
j = 0
while(True):
yield j
j += 1
def run1(c):
y = c*x
l1.set_data(x,y)
def run2(c):
y = c*x
l2.set_data(x,y)
ani1 = animation.FuncAnimation(fig,run1,gen1,interval=1)
ani2 = animation.FuncAnimation(fig,run2,gen2,interval=1)
plt.show()

Resources