Search code examples
pythongeometrysympy

Sympy extend the length of a Line Segment


I'm trying to use Sympy to calculate a point along an extended line segment.

So first I'm setting up a line Segment between point_a and point_b:

from sympy import Point, Segment

point_a = (4, 1)
point_b = (10, 3)

p1, p2 = Point(point_a), Point(point_b)
seg = Segment(p1, p2)

In this example the length of the segment is approx 6.32455532034

I want to be able to keep point_a where it is but extend point_b out so that the length of the segment is 20 (instead of 6.32)

seg.length = 20
new_end_point = seg.points[1]

However it does not allow the length of the line to be changed like this. Is there a simple way I can do this?

Note 1: that I'm trying to use Sympy for this in order to keep the code readable for non-math people (such as myself)

Note 2: 20 is an arbrary length not related to the starting distance between p1 and p2

Thanks to @smichr for the answer, I have added a simplified version of what he said:

from sympy import Point, Ray, Circle

point_a = (4, 1)
point_b = (10, 3)
extend = 20

p1, p2 = Point(point_a), Point(point_b)
ray = Ray(p1, p2)
c = Circle(p1, extend).intersection(ray)

# The resulting point
result = c[0].evalf().coordinates


Solution

  • Take a segment defined by points

    >>> p1=(0,0)
    >>> p2=(1,0)
    >>> s=Segment(p1,p2)
    

    Cast it to a Ray and see where it intersects the Circle of desired radius, centered on p1. It will only intersect at one point, so use that point for your new segment:

    >>> from sympy.abc import t
    >>> from sympy import Ray, Circle
    >>> Circle(p1, 20).intersection(Ray(*s.args))[0]
    Point(20, 0)
    >>> new_segment = Segment(p1, _)
    

    You could wrap this into a function like this:

    >>> def directed_segment(line, length):
    ...     from sympy import Circle, Ray
    ...     p3 = Ray(*line.args).intersection(Circle(line.p1, length))[0]
    ...     return line.func(line.p1, p3)
    ...
    >>> directed_segment(s,20)
    Segment2D(Point2D(0, 0), Point2D(20, 0))
    

    The simplest approach is to just scale the segment in both directions wrt the desired point (first arg, in your case). The scale factor will be your desired length, l, divided by the current length. A helper function named new_seg is defined below and used to lengthen a 3,4,5 hypotenuse to a length of 10 while maintaining the initial point of the segment:

    new_seg=lambda s,l : s.scale(*[l/s.length]*2, s.args[0])
    >>> new_seg(Segment((1,2),(4,6)), 10)
    Segment2D(Point2D(1, 2), Point2D(7, 10))
    >>> assert _.length == 10
    

    If you used s.midpoint instead of s.args[0] then the midpoint of the segment would remain the same. For example, to double the length while maintaining the midpoint,

    >>> t=s.scale(2,2,s.midpoint)
    >>> assert t.length = 2*s.length and t.midpoint == s.midpoint