I want to create a buffer (20km) from a line, and then separate them into two polygons with the line is boundary between two polygons (picture demo below. The desired output would be two polygons such as polygon 1 and 2). I tried to use geopandas but it does not work as I expected.
Here is sample code and data.
import geopandas as gpd
# the line vector data
data=gpd.read_file("https://raw.githubusercontent.com/tuyenhavan/test_data/main/Thailand_Cambodia_Line.json")
# make buffer. Data are already in UTM 48N system
data_explode=data.explode(index_parts=True)
# buffer 20km
buffer_20km=data_explode.buffer(20000)
geo_df=gpd.GeoDataFrame(geometry=buffer_20km)
dissolve=geo_df.dissolve()
3 Answers 3
You can use shapelys split:
import geopandas as gpd
from shapely.ops import split
df = gpd.read_file(r"C:/TEMP/a_line.shp")
crs = df.crs #Save crs, it gets lost in the splitting
#When buffering the buffer gets slighty longer than the line and the split doesnt work.
#I want to buffer by 700, so first +705 then -5 to make the line extend the buffer ends
#or the splitting doesnt work.
df["buff"] = df.geometry.buffer(705, cap_style=2).buffer(-5) #Create a buffer geometry column
#Split each buffer by the line, and create a list of the split parts
df["split_parts"] = df.apply(lambda x: [part for part in split(x["buff"], x["geometry"]).geoms], axis=1)
df = df.explode(column="split_parts") #Explode to rows
df = df.set_geometry("split_parts") #Set the geometry of df to the split
df = df.drop(["geometry","buff"], axis="columns").rename_geometry("geometry") #Drop the old geometries or they can cause problems when saving to file
df.crs = crs
df.to_file(r"C:/TEMP/a_line_buffered_split.shp")
-
I had a solution as you offer but OP uses the round cap type in the example. You use flat-type cap.Kadir Şahbaz– Kadir Şahbaz2023年03月30日 21:39:11 +00:00Commented Mar 30, 2023 at 21:39
we also suggest this solution based on two one-sided buffers. See also here: Buffering one side of a line
import shapely
import geopandas as gpd
# Create line
line = gpd.GeoSeries(shapely.geometry.LineString([(0, 2), (4, 2)]))
# First create a buffer for only one side (positive)
polygon1 = line.buffer(1, single_sided=True)
# Then create a buffer for the other side (negative)
polygon2 = line.buffer(-1, single_sided=True)
ax = polygon1.plot(facecolor="lightblue")
polygon2.plot(ax=ax, facecolor="darkblue")
line.plot(ax=ax, edgecolor="red", linewidth=5)
Result:
-
I thought using
singlesided=True
option, but the source line in the question has 12000 segments and this option takes a very long time. I don't know why. However standard buffering takes only 1-2 second on the same line.Kadir Şahbaz– Kadir Şahbaz2023年03月30日 21:31:06 +00:00Commented Mar 30, 2023 at 21:31
Maybe just take the difference of the original line and the buffer, to get the polygon you wanted and seperate them afterwards, like:
import geopandas as gpd
# read in the line data
line_data = gpd.read_file("https://raw.githubusercontent.com/tuyenhavan/test_data/main/Thailand_Cambodia_Line.json")
# buffer the line
buffered_line = line_data.buffer(20000)
# calculate the difference between the buffered line and the original line
difference = buffered_line.difference(line_data.unary_union)
# convert the difference to a GeoDataFrame
difference_df = gpd.GeoDataFrame(geometry=difference)
# split the difference into two polygons by calculating the centroid of the line
centroid = line_data.unary_union.centroid
difference_df["side"] = difference_df.geometry.centroid.x.apply(lambda x: "left" if x < centroid.x else "right")
# separate the difference into two GeoDataFrames based on the "side" column
left_polygon = difference_df[difference_df.side == "left"]
right_polygon = difference_df[difference_df.side == "right"]