5

I have a Polygon and a MultiLineString in Shapely. I would like to extend the LineString segment that does not reach the polygon boundary so that it does indeed reach the polygon boundary. It's okay if it extends past because I can easily clip it to the boundary afterwards.

Ideally, it would continue at the same angle, but I'd imagine that is significantly more difficult than extending it up straight to the boundary.

Does anyone have any suggestions on how I could go about doing this?

I generated the geometries using the following code (as a simplified example of what I actually need to do):

import shapely
from shapely.geometry import *
Line=MultiLineString([((3,0),(3,5)),((3,5),(7,9.5))])
Box=Polygon([(0,0),(0,10),(10,10),(10,0)])

Polygon and Polyline

Georgy
  • 12,464
  • 7
  • 65
  • 73
AJG519
  • 3,249
  • 10
  • 36
  • 62
  • Shapely cannot extrapolate lines, so you would need to do some customised work to get this result. – Mike T Oct 19 '15 at 22:54

1 Answers1

6

In the example, you could just do the math and find the intersection between the lines generated by the segments (the MultiLineString last segment and the segment of the polygon boundary), without relaying on any shapely library computation.

(I don't understand why you are using a MultiLineString when you should be using a simple LineString, since all line segments are consecutive).

A little more general solution would be the following:

from shapely.geometry import *

def getExtrapoledLine(p1,p2):
    'Creates a line extrapoled in p1->p2 direction'
    EXTRAPOL_RATIO = 10
    a = p1
    b = (p1[0]+EXTRAPOL_RATIO*(p2[0]-p1[0]), p1[1]+EXTRAPOL_RATIO*(p2[1]-p1[1]) )
    return LineString([a,b])

line=LineString([(3,0),(3,5),(7,9.5)])
box=Polygon([(0,0),(0,10),(10,10),(10,0)])

box_ext = LinearRing(box.exterior.coords) #we only care about the boundary intersection
l_coords = list(line.coords)
long_line = getExtrapoledLine(*l_coords[-2:]) #we use the last two points

if box_ext.intersects(long_line):
    intersection_points = box_ext.intersection(long_line)
    new_point_coords = list(intersection_points.coords)[0] #
else:
    raise Exception("Something went really wrong")

l_coords.append(new_point_coords)
new_extended_line = LineString(l_coords) 

# To see the problem:
import pylab
x, y = box.exterior.xy
pylab.plot(x,y)
l_coords = list(line.coords)
x = [p[0] for p in l_coords]
y = [p[1] for p in l_coords]
pylab.plot(x,y)
longl_coords = list(long_line.coords)
x = [p[0] for p in longl_coords]
y = [p[1] for p in longl_coords]
pylab.plot(x,y)
pylab.plot(new_point_coords[0], new_point_coords[1], 'o')
pylab.show()

# To see the solution:
x, y = box.exterior.xy
pylab.plot(x,y)
l_coords = list(new_extended_line.coords)
x = [p[0] for p in l_coords]
y = [p[1] for p in l_coords]
pylab.plot(x,y)
pylab.show()

In this solution, we extrapolate the last segment of the Line and intersect it with the boundary line of the polygon to get the intersection point. A few remarks:

  1. Notice the EXTRAPOL_RATIO constant. Depending on the polygon complexity or dimensions, it could make the line to intersect the polygon boundary in more than one point, or in not point at all.
  2. You depend on knowing that it is the last segment of the line you want to extend.

I think that there are a lot of solution to this problem. Depending on how general is the solution you want, it can turn very complicated. I imagine you can adapt the code to more general settings.

eguaio
  • 3,754
  • 1
  • 24
  • 38
  • 6
    The `getExtrapoledLine` can also be implemented using [`scale`](https://shapely.readthedocs.io/en/latest/manual.html#shapely.affinity.scale) function of Shapely. – Georgy Jul 29 '19 at 08:55
  • Hi @Georgy. Another great catch from you. I'll change the answer as soon as I have the time for it. Your knowledge of shapely API and functions is awesome! Are you one of the manteiners or you just red all the docs? – eguaio Jul 30 '19 at 10:32
  • No, I'm not one of the maintainers, at least for now :) It's just I'm working on a polygon decomposition problem for my research, and my code uses lots of Shapely functionality, so I have to refer to the docs every now and then. – Georgy Jul 30 '19 at 10:45
  • @Georgy please explain scale method, this doesn't work `line = LineString([(3, 0), (3, 5), (7, 9.5)]); s = scale(line, xfact=10.0, yfact=10.0, zfact=1.0)` – Yu Da Chi Dec 02 '20 at 14:47
  • 2
    @Oleg If a `LineString` has several segments and you want to extrapolate only the last one, you will have to extract it first (`last_segment = LineString(line.coords[-2:])`), scale it using its penultimate point as an origin (`scaled_last_segment = scale(last_segment, xfact=10, yfact=10, origin=last_segment.boundary[0])`), and then reconstruct the initial `LineString` with the new extrapolated segment (`new_line = LineString([*line.coords[:-2], *scaled_last_segment.coords])`). – Georgy Dec 02 '20 at 14:59
  • Shapely Scale on line has serious issues. – ar-siddiqui Mar 19 '21 at 01:34