Combine and sum values of overlapping Polygons in GeoPandas - fragment

I have a GeoPandas dataframe with a.o. polygons as 'geometry' column and a corresponding value in a different column. E.g. like such:
import numpy as np
import geopandas as gpd
xmin, xmax, ymin, ymax = 900000, 1080000, 120000, 280000
xc = (xmax - xmin) * np.random.random(2000) + xmin
yc = (ymax - ymin) * np.random.random(2000) + ymin
gdf = gpd.GeoDataFrame(geometry=gpd.points_from_xy(xc,yc),crs=3857)
gdf['geometry'] = gdf['geometry'].buffer(5000)
gdf['value'] = np.random.random(2000)
gdf.head()
geometry value
0 POLYGON ((1058397.021 237842.681, 1058372.944 ... 0.792110
1 POLYGON ((907562.671 276549.404, 907538.595 27... 0.124436
2 POLYGON ((953685.557 229083.936, 953661.481 22... 0.264356
3 POLYGON ((1052030.845 242915.680, 1052006.769 ... 0.161183
4 POLYGON ((1063942.060 263330.293, 1063917.983 ... 0.972290
What I would like to do is create new polygons describing the overlap of all polygons and sum the values of these overlapping polygons, as in the picture below:
I've looked at dissolve and at unary_union, but these do not seem to do the trick. Any suggestions are appreciated.

For anyone intersted in a solution for this, here is an example that solves a similar problem.
First create the geodataframe:
from shapely.geometry import Polygon
import geopandas
from shapely.ops import linemerge, unary_union, polygonize
dftest = geopandas.GeoDataFrame({'geometry': geopandas.GeoSeries([Polygon([(0,0), (3,0), (3,3), (0,3)]),
Polygon([(0,0), (2,0), (2,2), (0,2)]),
Polygon([(2,2), (4,2), (4,4), (2,4)]),
Polygon([(1,1), (3,1), (3,3), (1,3)]),
Polygon([(3,3), (5,3), (5,5), (3,5)])])}
).reset_index()
dftest['values'] = [4,3,2,1,0]
Plot this:
dftest.plot('values', legend=True)
initial geodataframe
convert all polygons to lines and perform union:
lines = unary_union(linemerge([geom.exterior for geom
in dftest.geometry]))
Convert again to (smaller) intersecting polygons and to geodataframe:
polygons = list(polygonize(lines))
intersects = geopandas.GeoDataFrame({'geometry': polygons})
Perform sjoin with original geoframe to get overlapping polygons. Afterwards group per intersecting polygon to perform (arbitrary) aggregation.
intersects['sum_overlaps'] = (intersects
.sjoin(dftest, predicate='within')
.reset_index()
.groupby(['level_0', 'index_right'])
.head(1)
.groupby('level_0')
.values.sum())
plot result
intersects.plot('sum_overlaps', legend=True)
overlap result

Related

annulus with scipy Delaunay

i try to draw a 3d solid that represents an annulus. I have used the scipy module and Delaunay to do the calculation.
Unfortunately the plot shows a 3d cylinder and not an annulus. Has somebody an idea how to modify the code? Is scipy the right module? Can i use Delaunay with retangular shapes?
thanks in advance!
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial import Delaunay
points = 50
theta = np.linspace(0,2*np.pi,points)
radius_middle = 7.5
radius_inner = 7
radius_outer = 8
x_m_cartesian = radius_middle * np.cos(theta)
y_m_cartesian = radius_middle * np.sin(theta)
z_m_cartesian = np.zeros(points)
M_m = np.c_[x_m_cartesian,y_m_cartesian,z_m_cartesian]
x_i_cartesian = radius_inner * np.cos(theta)
y_i_cartesian = radius_inner * np.sin(theta)
z_i_cartesian = np.zeros(points)
M_i = np.c_[x_i_cartesian,y_i_cartesian,z_i_cartesian]
x1_m_cartesian = radius_middle * np.cos(theta)
y1_m_cartesian = radius_middle * np.sin(theta)
z1_m_cartesian = np.ones(points)
M1_m = np.c_[x1_m_cartesian,y1_m_cartesian,z1_m_cartesian]
x2_i_cartesian = radius_inner * np.cos(theta)
y2_i_cartesian = radius_inner * np.sin(theta)
z2_i_cartesian = np.ones(points)
M2_i = np.c_[x2_i_cartesian,y2_i_cartesian,z2_i_cartesian]
M = np.vstack((M_m,M_i,M1_m,M2_i))
# Delaunay
CH = Delaunay(M).convex_hull
x,y,z = M[:,0],M[:,1],M[:,2]
fig = plt.figure(figsize=(12,8))
ax = fig.add_subplot(111,projection='3d')
#ax.scatter(x[:,0],y[:,1],z[:,2])
ax.plot_trisurf(x,y,z,triangles=CH, shade=False, color='lightblue',lw=1, edgecolor='k')
plt.show()
As noted in the comments the convex hull is a convex shape and therefore cannot represent an annulus. However, the concept of the concave hull (also known as the alpha-shape) is probably appropriate for your needs. Basically, the alpha-shape removes from the Delaunay triangulation the triangles (tetrahedra in your 3D case) that have a circumradius greater than some value (defined by the alpha parameter).
This answer provides an implementation of the alpha-shape surface (i.e., the outer boundary) for 3D points. Using the alpha_shape_3D function from that answer, with an alpha value of 3, resulted in the figure below.
The following two lines in the code (replacing the assignment to CH and the plot function) do the job.
vertices, edges, facets = alpha_shape_3D(pos=M, alpha=3.)
ax.plot_trisurf(x,y,z,triangles=facets, shade=False, color='lightblue',lw=1, edgecolor='k')

Get Distance Between Two Points in GeoPandas

I have two points as below. I need to get the distance between them in meters.
POINT (80.99456 7.86795)
POINT (80.97454 7.872174)
How can this be done via GeoPandas?
Your points are in a lon, lat coordinate system (EPSG:4326 or WGS 84). To calculate a distance in meters, you would need to either use the Great-circle distance or project them in a local coordinate system to approximate the distance with a good precision.
For Sri Lanka, you can use EPSG:5234 and in GeoPandas, you can use the distance function between two GeoDataFrames.
from shapely.geometry import Point
import geopandas as gpd
pnt1 = Point(80.99456, 7.86795)
pnt2 = Point(80.97454, 7.872174)
points_df = gpd.GeoDataFrame({'geometry': [pnt1, pnt2]}, crs='EPSG:4326')
points_df = points_df.to_crs('EPSG:5234')
points_df2 = points_df.shift() #We shift the dataframe by 1 to align pnt1 with pnt2
points_df.distance(points_df2)
The result should be 2261.92843 m

networkx ego_graph apply to geopandas series speed-up

Having GeoSeries of around 100000 locations, I have a working code for calculating Polygons of walking accessibility from each location as a center.
The code does calculations over networkx graph, obtained from OpenStreeMaps via osmnx by apply to GeoDataFrame.
I am trying to speed up calculation, as that is incredibly slow.
G - is a networkx graph
# create graph walking_time edge property
walking_speed = 4.5 #km/h
walking_speed_m_minute = walking_speed * 1000 / 60 #km/h to m/min
for u, v, k, data in G.edges(data=True, keys=True):
data['walking_time'] = data['length'] / walking_speed_m_minute
This is the func I apply to GeoDataFrame:
def calculate_time_accessibility_polygon(row, trip_time):
# location x,y
y = row['latitude']
x = row['longitude']
# find nearest node on the graph
center_node = ox.get_nearest_node(G, (y, x))
subgraph = nx.ego_graph(G, center_node, radius=trip_time,
distance='time')
node_points = [Point((data['x'], data['y'])) for node, data in
subgraph.nodes(data=True)]
bounding_poly = gpd.GeoSeries(node_points).unary_union.convex_hull
return(bounding_poly)
I apply with the following line of code:
gdf.apply(calculate_time_accessibility_polygon, args=(15,), axis=1))
Thank you!!!

Why do the inverse function from both cartopy and basemap have different results to calculate distance?

I want to calculate the distance between two points on surface of earth in meteres
I have tried with both basemap and cartopy but both result in different numbers.
Basemap:
import mpl_toolkits.basemap.pyproj as pyproj
k = pyproj.Geod(ellps="WGS84")
distance = k.inv(c0[1], c0[0], c1[1], c1[0])[-1]/1000.
Cartopy:
import cartopy.geodesic as gd
k = gd.Geodesic() // defaults to WGS84
distance = k.inverse(c0, c1).base[0,0]/1000
where both coord0 and coord1 are numpy arrays of size 2 having lat and lon of a coordinate.
c0 = numpy.array([77.343750, 22.593726])
c1 = numpy.array([86.945801, 23.684774])
Cartopy Output: 990.6094719605074
Basemap Output: 1072.3456344712142
With Basemap, you must use proper order of (long, lat):
distance = k.inv(c0[0], c0[1], c1[0], c1[1])[-1]/1000.
and the result will agree with Cartopy's, which is the correct result:
990.6094719605074

How can I align the y-axis (latitudes) of a map plot and a plot in python

I'm trying plot two panels in a plot.
The first one (left) is a data with latitude values in its y-axis. The second panel is a map.
I wanna that the latitude values of both panels coinciding, but I don't know how get it.
I have a code like this:
fig_mapa= plt.figure()
'''Mapa'''
ax1=fig_mapa.add_subplot(122)
map = Basemap(llcrnrlon=-90,llcrnrlat=-58.1,urcrnrlon=-32,urcrnrlat=12.6,
resolution='f',projection='merc',lon_0=-58,lat_0=-25, ax=ax1)
map.drawparallels(np.arange(-90,90.,5), labels=[0,1,0,0], linewidth=0.5)
map.drawmeridians(np.arange(-180.,180.,5), labels=[0,0,0,1], linewidth=0.5)
map.readshapefile("./Fases_tectonicas/Shapefiles/Unidades_Fi", 'Unidades_Fi', linewidth=0.1)
#map.warpimage(image='./Geotiffs/NE1_HR_LC_SR_W_DR/NE1_HR_LC_SR_W_DR.tif', zorder=1)
map.drawcoastlines(linewidth=0.5, color='k')
Nombre_Unidad= []
for elemento in map.Unidades_Fi_info:
Nombre_Unidad.append(elemento['NAME'])
for i in range(len(Nombre_Unidad)):
draw=map.Unidades_Fi[i]
poly=Polygon(draw, facecolor=color[Nombre_Unidad[i]],edgecolor='k', alpha=0.5,linewidth=0.1, zorder=2)
plt.gca().add_patch(poly)
'''Gráfico Eventos Compresivos'''
ax2= fig_mapa.add_subplot(121)
ax2.set_ylim(-58.1,12.6)
ax2.set_xlim(120,0)
ax2.set_xlabel('Tiempo [Ma]')
ax2.set_ylabel('Latitud[°]')
ax2.grid()
The simplest way to align two axes is with the sharex or sharey keyword for plt.subplots. However, the coordinates that Basemap shows and the coordinates that it uses for the Axes instance are two different things, so you will have to convert between the two if you want to have understandable ytick labels and some meaningful graph in your second Axes instance. Below I show how you can align the two y-axes, set the yticks properly and transform your data to the data coordinates of your Basemap. I left the creation of the Basemap untouched.
from matplotlib import pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
##figure with two subplots and shared y-axis
fig,(ax2,ax1) = plt.subplots(nrows=1, ncols=2, sharey='row')
m1 = Basemap(llcrnrlon=-90,llcrnrlat=-58.1,urcrnrlon=-32,urcrnrlat=12.6,
#resolution='f',
projection='merc',lon_0=-58,lat_0=-25, ax=ax1)
m1.drawparallels(np.arange(-90,90.,5), labels=[0,1,0,0], linewidth=0.5)
m1.drawmeridians(np.arange(-180.,180.,5), labels=[0,0,0,1], linewidth=0.5)
m1.drawcoastlines(linewidth=0.5, color='k')
##turning off yticks at basemap
ax1.yaxis.set_ticks_position('none')
##setting yticks:
yticks = np.arange(-55,12.6,5)
##transform yticks:
_,yticks_data = m1(0*yticks,yticks)
ax2.set_yticks(yticks_data)
ax2.set_yticklabels(['{: >3}$^\circ${}'.format(
abs(int(y)), 'N' if y>0 else 'S' if y<0 else ' '
) for y in yticks])
ax2.set_xlim(120,0)
ax2.set_xlabel('Tiempo [Ma]')
ax2.set_ylabel('Latitud[$^\circ$]')
ax2.grid()
#some fake data for testing plotting
yrange = np.linspace(-60,20,100)
temp = (np.sin(10*np.deg2rad(yrange))+1)*50
##transform yrange
_,yrange_data = m1(0*yrange, yrange)
ax2.plot(temp,yrange_data)
plt.show()
The result of the above code looks like this:
Hope this helps.