Draw perpendicular line of fixed length at a point of another line
Asked Answered
L

4

13

I have two points A (10,20) and B (15,30). The points generate a line AB. I need to draw a perpendicular line, CD, on point B with a length of 6 (each direction 3 units) in Python.

I already have some properties of line AB using the following code:

from scipy import stats
x = [10,15]
y = [20,30]
slope, intercept, r_value, p_value, std_err = stats.linregress(x,y)

How can I calculate the location of C and D. I need their X and Y value. enter image description here

The value of C and D will be used for accomplishing another objective using the Shapely library.

Lobar answered 16/7, 2019 at 20:37 Comment(2)
Are you asking a maths question: how to calculate points C and D?Unhair
python code would be good but I can also use the math equations.Lobar
K
7

If slope is the slope of AB, then the slope of CD is -1/slope. This is equal to vertical change over horizontal change: dy/dx = -1/slope. This gives that dx = -slope*dx. And by Pythagorean Theorem, you have 3**2 = dy**2+dx**2. Substitute for dx, and you get

3**2 = (-slope*dy)**2+dy**2
3**2 = (slope**2 + 1)*dy**2
dy**2 = 3**2/(slope**2+1)
dy = math.sqrt(3**2/(slope**2+1))

Then you can get dx = -slope*dy. Finally, you can use dx and dy to get C and D. So the code would be:

import math
dy = math.sqrt(3**2/(slope**2+1))
dx = -slope*dy
C[0] = B[0] + dx
C[1] = B[1] + dy
D[0] = B[0] - dx
D[1] = B[1] - dy

(Note that although math.sqrt returns only one number, in general there is a positive and negative square root. C corresponds to the positive square root, and D to the negative).

Kass answered 16/7, 2019 at 20:57 Comment(0)
F
19

Since you are interested in using Shapely, the easiest way to get the perpendicular line that I can think of, is to use parallel_offset method to get two parallel lines to AB, and connect their endpoints:

from shapely.geometry import LineString

a = (10, 20)
b = (15, 30)
cd_length = 6

ab = LineString([a, b])
left = ab.parallel_offset(cd_length / 2, 'left')
right = ab.parallel_offset(cd_length / 2, 'right')
c = left.boundary[1]
d = right.boundary[0]  # note the different orientation for right offset
cd = LineString([c, d])

enter image description here

And the coordinates of CD:

>>> c.x, c.y
(12.316718427000252, 31.341640786499873)
>>> d.x, d.y
(17.683281572999746, 28.658359213500127)
Filippa answered 17/7, 2019 at 9:29 Comment(2)
I don't know how fast it is, but I like the simplicity of that solution.Pacify
aweeeeeesomme!)Incisure
K
7

If slope is the slope of AB, then the slope of CD is -1/slope. This is equal to vertical change over horizontal change: dy/dx = -1/slope. This gives that dx = -slope*dx. And by Pythagorean Theorem, you have 3**2 = dy**2+dx**2. Substitute for dx, and you get

3**2 = (-slope*dy)**2+dy**2
3**2 = (slope**2 + 1)*dy**2
dy**2 = 3**2/(slope**2+1)
dy = math.sqrt(3**2/(slope**2+1))

Then you can get dx = -slope*dy. Finally, you can use dx and dy to get C and D. So the code would be:

import math
dy = math.sqrt(3**2/(slope**2+1))
dx = -slope*dy
C[0] = B[0] + dx
C[1] = B[1] + dy
D[0] = B[0] - dx
D[1] = B[1] - dy

(Note that although math.sqrt returns only one number, in general there is a positive and negative square root. C corresponds to the positive square root, and D to the negative).

Kass answered 16/7, 2019 at 20:57 Comment(0)
L
2

You probably should use vectors to calculate the position of the points.

  • create the vector AB
  • calculate its normalized perpendicular
  • add or subtract 3 times this to B

with the help of a simple, reusable Vector class, the calculation is trivial, and reads like English:

Find the points perpendicular to AB at distance 3 from point B:
P1 = B + (B-A).perp().normalized() * 3 P2 = B + (B-A).perp().normalized() * 3

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    def dot(self, other):
        return self.x * other.x + self.y * other.y
    def norm(self):
        return self.dot(self)**0.5
    def normalized(self):
        norm = self.norm()
        return Vector(self.x / norm, self.y / norm)
    def perp(self):
        return Vector(1, -self.x / self.y)
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)
    def __str__(self):
        return f'({self.x}, {self.y})'


A = Vector(10, 20)
B = Vector(15, 30)

AB = B - A  
AB_perp_normed = AB.perp().normalized()
P1 = B + AB_perp_normed * 3
P2 = B - AB_perp_normed * 3

print(f'Point{P1}, and Point{P2}')

output:

Point(17.683281572999746, 28.658359213500127), and Point(12.316718427000252, 31.341640786499873)
Lockman answered 16/7, 2019 at 23:17 Comment(1)
What happens for A = Vector(10, 20) B = Vector(15, 20) your AB.perf() gives {...,y:Nan} in javascript.How to handle when y does not change?Killebrew
B
0

If we have a line parallel to x-axis or y-axis, then we can add the following two methods to the class Vector.

def perptoy(self):
    return Vector(1, 0)
def perptox(self):
    return Vector(1, -10000) // any big number will do instead of -inf

AB = B - A  
if (A.y == B.y):
   AB_perp_normed = AB.perptox().normalized();
elif (A.x == B.x):
   AB_perp_normed = AB.perptoy().normalized();
else:
   AB_perp_normed = AB.perp().normalized();

And the rest follows the same as in the earlier post.

Bev answered 14/10, 2022 at 4:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.