Search code examples
pythongeometrylineshapely

Extract segment from shapely line interersecting polygon


I have several lines (shapely.geometry.LineString) and patches (shapely.geometry.Polygon). My goal is to find lines intersecting a set polygon (a patch). For the moment this works well but I get the entire list of coordinates of the line. Instead I would like to get the two endpoints of the segment that effectively intersect the patch.

The lines are indeed composed of two set of coordinates x and y that are lists of floats. They represent [x1, x2, x3, ...] and [y1, y2, y3, ...]. Below is a minimal working example to generate two lines and a patch that reproduces the data structure I use in my code (a big class too complex to copy and paste here).

from shapely.geometry import LineString, Polygon
import matplotlib.pyplot as plt


intersections_list = []

x_line_patch =  [0, 4, -2, 8, 3, 10, 5]
y_line_patch =  [21, 17, 14, 11, 9, 6, 1]

x_line_patch2 =  [x + 20 for x in x_line_patch]
y_line_patch2 =  y_line_patch

# Create a patch
    
start_x = 5
end_x = 8
start_y = 5
end_y = 8

polygon = Polygon([(start_x, start_y), (end_x, start_y), (end_x, end_y), (start_x, end_y)])
lines = [LineString(zip(x_line_patch, y_line_patch)), LineString(zip(x_line_patch2, y_line_patch2))]

# Find line intersecting patches
patch_intersecting_lines = [line.xy for line in lines if line.intersects(polygon)]

# Extract the x and y coordinates of the polygon exterior
x, y = polygon.exterior.xy

# Plot the polygon
plt.plot(x, y)
plt.fill(x, y, alpha=0.3)  # Fill the polygon with color (optional)
# Plotting the line
x, y = lines[0].xy
x2, y2 = lines[1].xy
plt.plot(x, y)
plt.plot(x2, y2)
plt.tight_layout()
plt.show()

Additionally, creating individual LineString for each set of (x1, x2), (y1, y2) would add complexity in a program that already uses a lot of loops and I don't have access to these lists of coordinates in reality. *They're defined here to construct a LineString object but are created later in the program when the lines are selected if they cross a patch. Otherwise I would just do:

line1 = [LineString(zip(x_line_patch[i:i+2], y_line_patch[i:i+2])) for i in range(0,len(x_line_patch)-1)]

to select coordinates 2 by 2 and create small segments for each line. But in my actual code I do patch_intersecting_lines = [line.xy for line in lines if line.intersects(patch_shape)] to find intersecting line given a list of lines called lines.


Solution

  • Alright, I ended up doing several loop:

    • First I grab all of the lines in my diagram
    • Second, I generate a list of the lines intersecting my patch [line for line in lines if line.intersects(patch_shape)] and keep them as LineString objects
    • Third, I iterate in this list to find all the segments of the line
                        for line in patch_intersecting_lines:
                            x_line, y_line = line.xy
                            segments = [LineString(zip(x_line[i:i+2], y_line[i:i+2])) for i in range(0, len(x_line) - 1)]
    
    • I then add the segment to lines_intersecting list if the segment is intersecting the patch or not (it can give a list of segments)
    segments_intersecting = [([self.voltage_to_coord_x(x) - patch_x for x in segment.xy[0]],
                                                      [self.voltage_to_coord_x(y) - patch_y for y in segment.xy[1]])
                                                     for segment in segments if segment.intersects(patch_shape)]
    

    I also calculate the coordinates accordingly because the patch and the line don't have same origin on the diagram (plot vs imshow problem).

    It feels overkill since I grab the line get the x and y but also re-use them to create the individual segments to then iterate again through the segments to finally return the x and y of said segment.

    If you have a better suggestion that involves less loop I will gladly accept it.