Draw perpendicular line of fixed length at a point of another line - python

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.
The value of C and D will be used for accomplishing another objective using the Shapely library.

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])
And the coordinates of CD:
>>> c.x, c.y
(12.316718427000252, 31.341640786499873)
>>> d.x, d.y
(17.683281572999746, 28.658359213500127)

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).

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)

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.

Related

Algorithm Improvement: Calculate signed change in heading

I am trying to implement an algorithm to calculate the signed change in direction between two vectors. Here, I define the signed change in heading to be the smallest angle that needs to be added or subtracted from the angle of a given vector to meet the angle of another vector.
I have a naive implementation, but I was wondering if there was a better way.
import math
def signum(value):
if value > 0:
return 1
if value < 0:
return -1
return 0
def angleOfVector(v):
return math.atan2(v[1], v[0])
def principalAngleOfVector(v):
theta = angleOfVector(v)
if theta < 0:
theta += 2 * math.pi
return theta
def signedChangeInHeading(src, dst):
theta_src = principalAngleOfVector(src)
theta_dst = principalAngleOfVector(dst)
initial_sign = signum(theta_dst-theta_src)
initial_guess_magnitude = abs(theta_dst-theta_src)
alternate_guess_magnitude = 2*math.pi-abs(theta_dst-theta_src)
ret_magnitude = initial_guess_magnitude
ret_sign = initial_sign
if alternate_guess_magnitude < ret_magnitude:
ret_sign = -initial_sign
ret_magnitude = alternate_guess_magnitude
return ret_sign * ret_magnitude
I would use atan2(Ax * By - Ay * Bx, Ax * Bx + Ay * By).

Given two coordinates, a and b, how do I find a point that is x distance from a heading towards b?

I am working on a python script and I am sure there is an easier way to approach this problem then my solution so far.
Give two coordinates, say (0,0) and (40,40) and a set distance to travel, say 5, how would I find a new coordinate pair for the point that is 5 units from (0,0) heading along the line that connects (0,0) and (40,40)?
I am doing the following given the distance_to_travel and points p0, p1:
ang = math.atan2(p1.y - p0.y, p1.x - p0.x)
xx = node0.x + (distance_to_travel * math.cos(ang))
yy = node0.y + (distance_to_travel * math.sin(ang))
Following the method outlined in my comment:
# find the vector a-b
ab.x, ab.y = p1.x - p0.x, p1.y - p0.y
# find the length of this vector
len_ab = (ab.x * ab.x + ab.y * ab.y) ** 0.5
# scale to length 5
ab5.x, ab5.y = ab.x *5 / len_ab, ab.y *5 / len_ab
# add to a (== p0)
fin.x, fin.y = p0.x + ab5.x, p0.y + ab5.y
You should find the slope of the line between a and b |ab|.
Then you can use 2D spherical (polar) coordinate system to get r mount of distance with a given slope (The slope you found earlier).
Now you can convert from spherical to Cartesian to find x, y coordinates of new point. Then you add this values to values of point a:
import math
def slope(p1, p2):
return math.atan2(
(p2[1] - p1[1]), (p2[0] - p1[0])
)
def spherical_to_cartesian(r, theta):
x = r * math.cos(theta)
y = r * math.sin(theta)
return x, y
def add_points(p1, p2):
return p1[0] + p2[0], p1[1] + p2[1]
if __name__ == '__main__':
a = [0, 0]
b = [40, 40]
slp = slope(a, b)
r = 5
new_p = spherical_to_cartesian(r, slp)
res = add_points(a, new_p)
print(res)

Implementing a custom datatype in Sympy

I want to perform calculations with a binary operation (Tensor) that takes two non-commutative arguments, converts them into something like a pair, and then does funny things when I multiply these pairs.
# a, b, c, d are non-commutative
Tensor(a, b) * Tensor(c, d) == Tensor(a*c, d*b) # yes, in this order
Furthermore, I want all integer constants to be taken modulo 2.
-Tensor(a, b) == Tensor(a, b)
2*Tensor(a, b) == 0
Tensor(2*a, b) == 0
My shot at doing this:
import sympy as sp
from sympy.core.expr import Expr
class Tensor(Expr):
__slots__ = ['is_commutative']
def __new__(cls, l, r):
l = sp.sympify(l)
r = sp.sympify(r)
obj = Expr.__new__(cls, l, r)
obj.is_commutative = False
return obj
def __neg__(self):
return self
def __mul__(self, other):
if isinstance(other, Tensor):
return Tensor(self.args[0] * other.args[0], other.args[1] * self.args[1])
elif other.is_number:
if other % 2 == 0:
return 0
else:
return self
else:
return sp.Mul(self, other)
x, y = sp.symbols('x, y', commutative=False)
Ym = Tensor(y, 1) - Tensor(1, y)
Yp = Tensor(y, 1) + Tensor(1, y)
Xm = Tensor(x, 1) - Tensor(1, x)
d1 = Ym * Yp + Xm * 0
print(d1)
print(sp.expand(d1))
d2 = Xm * Ym
print(d2)
print(sp.expand(d2))
Output:
(Tensor(1, y) + Tensor(y, 1))**2
Tensor(1, y**2) + 2*Tensor(y, y) + Tensor(y**2, 1)
(Tensor(1, x) + Tensor(x, 1))*(Tensor(1, y) + Tensor(y, 1))
Tensor(1, x)*Tensor(1, y) + Tensor(1, x)*Tensor(y, 1) + Tensor(x, 1)*Tensor(1, y) + Tensor(x, 1)*Tensor(y, 1)
Test #1 is correct.
Test #2 has a term 2*Tensor(y, y) which should be zero (since I'm doing all calculations modulo 2, and, 2 % 2 == 0). How do I enforce that?
Test #3 is correct.
Test #4 does not multiply different Tensors at all. Tensor(1, x)*Tensor(1, y) should be Tensor(1, y*x), for example. How do I enforce that?
Context (if you're interested in why I'm doing this):
This is for calculating a bimodule resolution of an algebra over a char=2 field. A bimodule resolution of an algebra R over a field K is a minimal projective resolution of the same algebra R over its enveloping algebra R⊗R^op. Here op means "multiplication works in the opposite direction". The enveloping algebra has both left and right action on the algebra:
(a⊗b)*r == a*r*b
r*(a⊗b) == b*r*a
There is a theorem that simplifies calculations in the case where a minimal projective resolution of the algebra over the field is known. Still, they are quite tedious and I want to stop doing them manually.

Area and perimeter of a figure made out of overlapping circles

I plan on writing this code in python, but this is more of a general algorithm-design problem than one having to do with any particular language. I'm writing code to simulate a hybrid rocket engine, and long story short, the problem involves finding both the perimeter and the area of a figure composed of many (possibly thousands) of overlapping circles.
I saw this on stackexchange:
Combined area of overlapping circles
except I need to find the perimeter also. Someone in that thread mentioned Monte Carlo (random point guess) methods, but can that be used to find the perimeter as well as the area?
Thanks in advance.
I am adding a second answer instead of expanding the first one, because I am quite positive that the other answer is correct, but I am not so sure if this answer is as correct. I have done some simple testing, and it seems to work, but please point out my errors.
This is basically a quick-n-dirty implementation of what I stated before:
from math import atan2, pi
def intersectLine (a, b):
s0, e0, s1, e1 = a [0], a [1], b [0], b [1]
s = max (s0, s1)
e = min (e0, e1)
if e <= s: return (0, 0)
return (s, e)
class SectorSet:
def __init__ (self, sectors):
self.sectors = sectors [:]
def __repr__ (self):
return repr (self.sectors)
def __iadd__ (self, other):
acc = []
for mine in self.sectors:
for others in other.sectors:
acc.append (intersectLine (mine, others) )
self.sectors = [x for x in acc if x [0] != x [1] ]
return self
class Circle:
CONTAINS = 0
CONTAINED = 1
DISJOINT = 2
INTERSECT = 3
def __init__ (self, x, y, r):
self.x = float (x)
self.y = float (y)
self.r = float (r)
def intersect (self, other):
a, b, c, d, r0, r1 = self.x, self.y, other.x, other.y, self.r, other.r
r0sq, r1sq = r0 ** 2, r1 ** 2
Dsq = (c - a) ** 2 + (d - b) ** 2
D = Dsq ** .5
if D >= r0 + r1:
return Circle.DISJOINT, None, None
if D <= abs (r0 - r1):
return Circle.CONTAINED if r0 < r1 else Circle.CONTAINS, None, None
dd = .25 * ( (D + r0 + r1) * (D + r0 - r1) * (D - r0 + r1) * (-D + r0 + r1) ) ** .5
x = (a + c) / 2. + (c - a) * (r0sq - r1sq) / 2. / Dsq
x1 = x + 2 * (b - d) / Dsq * dd
x2 = x - 2 * (b - d) / Dsq * dd
y = (b + d) / 2. + (d - b) * (r0sq - r1sq) / 2. / Dsq
y1 = y - 2 * (a - c) / Dsq * dd
y2 = y + 2 * (a - c) / Dsq * dd
return Circle.INTERSECT, (x1, y1), (x2, y2)
def angle (self, point):
x0, y0, x, y = self.x, self.y, point [0], point [1]
a = atan2 (y - y0, x - x0) + 1.5 * pi
if a >= 2 * pi: a -= 2 * pi
return a / pi * 180
def sector (self, other):
typ, i1, i2 = self.intersect (other)
if typ == Circle.DISJOINT: return SectorSet ( [ (0, 360) ] )
if typ == Circle.CONTAINS: return SectorSet ( [ (0, 360) ] )
if typ == Circle.CONTAINED: return SectorSet ( [] )
a1 = self.angle (i1)
a2 = self.angle (i2)
if a1 > a2: return SectorSet ( [ (0, a2), (a1, 360) ] )
return SectorSet ( [ (a1, a2) ] )
def outline (self, others):
sectors = SectorSet ( [ (0, 360) ] )
for other in others:
sectors += self.sector (other)
u = 2 * self.r * pi
total = 0
for start, end in sectors.sectors:
total += (end - start) / 360. * u
return total
def outline (circles):
total = 0
for circle in circles:
others = [other for other in circles if circle != other]
total += circle.outline (others)
return total
a = Circle (0, 0, 2)
b = Circle (-2, -1, 1)
c = Circle (2, -1, 1)
d = Circle (0, 2, 1)
print (outline ( [a, b, c, d] ) )
Formula for calculating the intersecting points of two circles shamelessly stolen from here.
Let's say your have the circles c1, c2, ..., cn.
Take c1 and intersect it with each other circle. Initialize an empty list of circle sectors for c1.
If the intersection is zero points or one point and c1 is outside the other circle, then add a 360 degree sector to the list.
If the intersection is zero points or one point and c1 is inside the other circle, then the effective outline of c1 is 0, stop processing c1 with an outline of 0 and take the next circle.
If the intersection is two points, add the sector of c1, which is not in the other circle to the list of circle sectors of c1.
The effective outline of c1, is the length of the intersection of all sectors within the list of sectors of c1. This intersection of sectors can consist of various disjoint sectors.
Rinse and repeat for all circles and then sum up the resulting values.

How can you determine a point is between two other points on a line segment?

Let's say you have a two dimensional plane with 2 points (called a and b) on it represented by an x integer and a y integer for each point.
How can you determine if another point c is on the line segment defined by a and b?
I use python most, but examples in any language would be helpful.
Check if the cross product of (b-a) and (c-a) is 0, as tells Darius Bacon, tells you if the points a, b and c are aligned.
But, as you want to know if c is between a and b, you also have to check that the dot product of (b-a) and (c-a) is positive and is less than the square of the distance between a and b.
In non-optimized pseudocode:
def isBetween(a, b, c):
crossproduct = (c.y - a.y) * (b.x - a.x) - (c.x - a.x) * (b.y - a.y)
# compare versus epsilon for floating point values, or != 0 if using integers
if abs(crossproduct) > epsilon:
return False
dotproduct = (c.x - a.x) * (b.x - a.x) + (c.y - a.y)*(b.y - a.y)
if dotproduct < 0:
return False
squaredlengthba = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
if dotproduct > squaredlengthba:
return False
return True
Here's how I'd do it:
def distance(a,b):
return sqrt((a.x - b.x)**2 + (a.y - b.y)**2)
def is_between(a,c,b):
return distance(a,c) + distance(c,b) == distance(a,b)
Check if the cross product of b-a and c-a is0: that means all the points are collinear. If they are, check if c's coordinates are between a's and b's. Use either the x or the y coordinates, as long as a and b are separate on that axis (or they're the same on both).
def is_on(a, b, c):
"Return true iff point c intersects the line segment from a to b."
# (or the degenerate case that all 3 points are coincident)
return (collinear(a, b, c)
and (within(a.x, c.x, b.x) if a.x != b.x else
within(a.y, c.y, b.y)))
def collinear(a, b, c):
"Return true iff a, b, and c all lie on the same line."
return (b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y)
def within(p, q, r):
"Return true iff q is between p and r (inclusive)."
return p <= q <= r or r <= q <= p
This answer used to be a mess of three updates. The worthwhile info from them: Brian Hayes's chapter in Beautiful Code covers the design space for a collinearity-test function -- useful background. Vincent's answer helped to improve this one. And it was Hayes who suggested testing only one of the x or the y coordinates; originally the code had and in place of if a.x != b.x else.
(This is coded for exact arithmetic with integers or rationals; if you pass in floating-point numbers instead, there will be problems with round-off errors. I'm not even sure what's a good way to define betweenness of 2-d points in float coordinates.)
Here's another approach:
Lets assume the two points be A (x1,y1) and B (x2,y2)
The equation of the line passing through those points is (x-x1)/(y-y1)=(x2-x1)/(y2-y1) .. (just making equating the slopes)
Point C (x3,y3) will lie between A & B if:
x3,y3 satisfies the above equation.
x3 lies between x1 & x2 and y3 lies between y1 & y2 (trivial check)
The length of the segment is not important, thus using a square root is not required and should be avoided since we could lose some precision.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Segment:
def __init__(self, a, b):
self.a = a
self.b = b
def is_between(self, c):
# Check if slope of a to c is the same as a to b ;
# that is, when moving from a.x to c.x, c.y must be proportionally
# increased than it takes to get from a.x to b.x .
# Then, c.x must be between a.x and b.x, and c.y must be between a.y and b.y.
# => c is after a and before b, or the opposite
# that is, the absolute value of cmp(a, b) + cmp(b, c) is either 0 ( 1 + -1 )
# or 1 ( c == a or c == b)
a, b = self.a, self.b
return ((b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y) and
abs(cmp(a.x, c.x) + cmp(b.x, c.x)) <= 1 and
abs(cmp(a.y, c.y) + cmp(b.y, c.y)) <= 1)
Some random example of usage :
a = Point(0,0)
b = Point(50,100)
c = Point(25,50)
d = Point(0,8)
print Segment(a,b).is_between(c)
print Segment(a,b).is_between(d)
You can use the wedge and dot product:
def dot(v,w): return v.x*w.x + v.y*w.y
def wedge(v,w): return v.x*w.y - v.y*w.x
def is_between(a,b,c):
v = a - b
w = b - c
return wedge(v,w) == 0 and dot(v,w) > 0
Using a more geometric approach, calculate the following distances:
ab = sqrt((a.x-b.x)**2 + (a.y-b.y)**2)
ac = sqrt((a.x-c.x)**2 + (a.y-c.y)**2)
bc = sqrt((b.x-c.x)**2 + (b.y-c.y)**2)
and test whether ac+bc equals ab:
is_on_segment = abs(ac + bc - ab) < EPSILON
That's because there are three possibilities:
The 3 points form a triangle => ac+bc > ab
They are collinear and c is outside the ab segment => ac+bc > ab
They are collinear and c is inside the ab segment => ac+bc = ab
Here's a different way to go about it, with code given in C++. Given two points, l1 and l2 it's trivial to express the line segment between them as
l1 + A(l2 - l1)
where 0 <= A <= 1. This is known as the vector representation of a line if you're interested any more beyond just using it for this problem. We can split out the x and y components of this, giving:
x = l1.x + A(l2.x - l1.x)
y = l1.y + A(l2.y - l1.y)
Take a point (x, y) and substitute its x and y components into these two expressions to solve for A. The point is on the line if the solutions for A in both expressions are equal and 0 <= A <= 1. Because solving for A requires division, there's special cases that need handling to stop division by zero when the line segment is horizontal or vertical. The final solution is as follows:
// Vec2 is a simple x/y struct - it could very well be named Point for this use
bool isBetween(double a, double b, double c) {
// return if c is between a and b
double larger = (a >= b) ? a : b;
double smaller = (a != larger) ? a : b;
return c <= larger && c >= smaller;
}
bool pointOnLine(Vec2<double> p, Vec2<double> l1, Vec2<double> l2) {
if(l2.x - l1.x == 0) return isBetween(l1.y, l2.y, p.y); // vertical line
if(l2.y - l1.y == 0) return isBetween(l1.x, l2.x, p.x); // horizontal line
double Ax = (p.x - l1.x) / (l2.x - l1.x);
double Ay = (p.y - l1.y) / (l2.y - l1.y);
// We want Ax == Ay, so check if the difference is very small (floating
// point comparison is fun!)
return fabs(Ax - Ay) < 0.000001 && Ax >= 0.0 && Ax <= 1.0;
}
The scalar product between (c-a) and (b-a) must be equal to the product of their lengths (this means that the vectors (c-a) and (b-a) are aligned and with the same direction). Moreover, the length of (c-a) must be less than or equal to that of (b-a). Pseudocode:
# epsilon = small constant
def isBetween(a, b, c):
lengthca2 = (c.x - a.x)*(c.x - a.x) + (c.y - a.y)*(c.y - a.y)
lengthba2 = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
if lengthca2 > lengthba2: return False
dotproduct = (c.x - a.x)*(b.x - a.x) + (c.y - a.y)*(b.y - a.y)
if dotproduct < 0.0: return False
if abs(dotproduct*dotproduct - lengthca2*lengthba2) > epsilon: return False
return True
I needed this for javascript for use in an html5 canvas for detecting if the users cursor was over or near a certain line. So I modified the answer given by Darius Bacon into coffeescript:
is_on = (a,b,c) ->
# "Return true if point c intersects the line segment from a to b."
# (or the degenerate case that all 3 points are coincident)
return (collinear(a,b,c) and withincheck(a,b,c))
withincheck = (a,b,c) ->
if a[0] != b[0]
within(a[0],c[0],b[0])
else
within(a[1],c[1],b[1])
collinear = (a,b,c) ->
# "Return true if a, b, and c all lie on the same line."
((b[0]-a[0])*(c[1]-a[1]) < (c[0]-a[0])*(b[1]-a[1]) + 1000) and ((b[0]-a[0])*(c[1]-a[1]) > (c[0]-a[0])*(b[1]-a[1]) - 1000)
within = (p,q,r) ->
# "Return true if q is between p and r (inclusive)."
p <= q <= r or r <= q <= p
Here's how I did it at school. I forgot why it is not a good idea.
EDIT:
#Darius Bacon: cites a "Beautiful Code" book which contains an explanation why the belowed code is not a good idea.
#!/usr/bin/env python
from __future__ import division
epsilon = 1e-6
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
class LineSegment:
"""
>>> ls = LineSegment(Point(0,0), Point(2,4))
>>> Point(1, 2) in ls
True
>>> Point(.5, 1) in ls
True
>>> Point(.5, 1.1) in ls
False
>>> Point(-1, -2) in ls
False
>>> Point(.1, 0.20000001) in ls
True
>>> Point(.1, 0.2001) in ls
False
>>> ls = LineSegment(Point(1, 1), Point(3, 5))
>>> Point(2, 3) in ls
True
>>> Point(1.5, 2) in ls
True
>>> Point(0, -1) in ls
False
>>> ls = LineSegment(Point(1, 2), Point(1, 10))
>>> Point(1, 6) in ls
True
>>> Point(1, 1) in ls
False
>>> Point(2, 6) in ls
False
>>> ls = LineSegment(Point(-1, 10), Point(5, 10))
>>> Point(3, 10) in ls
True
>>> Point(6, 10) in ls
False
>>> Point(5, 10) in ls
True
>>> Point(3, 11) in ls
False
"""
def __init__(self, a, b):
if a.x > b.x:
a, b = b, a
(self.x0, self.y0, self.x1, self.y1) = (a.x, a.y, b.x, b.y)
self.slope = (self.y1 - self.y0) / (self.x1 - self.x0) if self.x1 != self.x0 else None
def __contains__(self, c):
return (self.x0 <= c.x <= self.x1 and
min(self.y0, self.y1) <= c.y <= max(self.y0, self.y1) and
(not self.slope or -epsilon < (c.y - self.y(c.x)) < epsilon))
def y(self, x):
return self.slope * (x - self.x0) + self.y0
if __name__ == '__main__':
import doctest
doctest.testmod()
Ok, lots of mentions of linear algebra (cross product of vectors) and this works in a real (ie continuous or floating point) space but the question specifically stated that the two points were expressed as integers and thus a cross product is not the correct solution although it can give an approximate solution.
The correct solution is to use Bresenham's Line Algorithm between the two points and to see if the third point is one of the points on the line. If the points are sufficiently distant that calculating the algorithm is non-performant (and it'd have to be really large for that to be the case) I'm sure you could dig around and find optimisations.
Any point on the line segment (a, b) (where a and b are vectors) can be expressed as a linear combination of the two vectors a and b:
In other words, if c lies on the line segment (a, b):
c = ma + (1 - m)b, where 0 <= m <= 1
Solving for m, we get:
m = (c.x - b.x)/(a.x - b.x) = (c.y - b.y)/(a.y - b.y)
So, our test becomes (in Python):
def is_on(a, b, c):
"""Is c on the line segment ab?"""
def _is_zero( val ):
return -epsilon < val < epsilon
x1 = a.x - b.x
x2 = c.x - b.x
y1 = a.y - b.y
y2 = c.y - b.y
if _is_zero(x1) and _is_zero(y1):
# a and b are the same point:
# so check that c is the same as a and b
return _is_zero(x2) and _is_zero(y2)
if _is_zero(x1):
# a and b are on same vertical line
m2 = y2 * 1.0 / y1
return _is_zero(x2) and 0 <= m2 <= 1
elif _is_zero(y1):
# a and b are on same horizontal line
m1 = x2 * 1.0 / x1
return _is_zero(y2) and 0 <= m1 <= 1
else:
m1 = x2 * 1.0 / x1
if m1 < 0 or m1 > 1:
return False
m2 = y2 * 1.0 / y1
return _is_zero(m2 - m1)
c#
From http://www.faqs.org/faqs/graphics/algorithms-faq/
-> Subject 1.02: How do I find the distance from a point to a line?
Boolean Contains(PointF from, PointF to, PointF pt, double epsilon)
{
double segmentLengthSqr = (to.X - from.X) * (to.X - from.X) + (to.Y - from.Y) * (to.Y - from.Y);
double r = ((pt.X - from.X) * (to.X - from.X) + (pt.Y - from.Y) * (to.Y - from.Y)) / segmentLengthSqr;
if(r<0 || r>1) return false;
double sl = ((from.Y - pt.Y) * (to.X - from.X) - (from.X - pt.X) * (to.Y - from.Y)) / System.Math.Sqrt(segmentLengthSqr);
return -epsilon <= sl && sl <= epsilon;
}
An answer in C# using a Vector2D class
public static bool IsOnSegment(this Segment2D #this, Point2D c, double tolerance)
{
var distanceSquared = tolerance*tolerance;
// Start of segment to test point vector
var v = new Vector2D( #this.P0, c ).To3D();
// Segment vector
var s = new Vector2D( #this.P0, #this.P1 ).To3D();
// Dot product of s
var ss = s*s;
// k is the scalar we multiply s by to get the projection of c onto s
// where we assume s is an infinte line
var k = v*s/ss;
// Convert our tolerance to the units of the scalar quanity k
var kd = tolerance / Math.Sqrt( ss );
// Check that the projection is within the bounds
if (k <= -kd || k >= (1+kd))
{
return false;
}
// Find the projection point
var p = k*s;
// Find the vector between test point and it's projection
var vp = (v - p);
// Check the distance is within tolerance.
return vp * vp < distanceSquared;
}
Note that
s * s
is the dot product of the segment vector via operator overloading in C#
The key is taking advantage of the projection of the point onto the infinite line and observing that the scalar quantity of the projection tells us trivially if the projection is on the segment or not. We can adjust the bounds of the scalar quantity to use a fuzzy tolerance.
If the projection is within bounds we just test if the distance from the point to the projection is within bounds.
The benefit over the cross product approach is that the tolerance has a meaningful value.
C# version of Jules' answer:
public static double CalcDistanceBetween2Points(double x1, double y1, double x2, double y2)
{
return Math.Sqrt(Math.Pow (x1-x2, 2) + Math.Pow (y1-y2, 2));
}
public static bool PointLinesOnLine (double x, double y, double x1, double y1, double x2, double y2, double allowedDistanceDifference)
{
double dist1 = CalcDistanceBetween2Points(x, y, x1, y1);
double dist2 = CalcDistanceBetween2Points(x, y, x2, y2);
double dist3 = CalcDistanceBetween2Points(x1, y1, x2, y2);
return Math.Abs(dist3 - (dist1 + dist2)) <= allowedDistanceDifference;
}
Here is some Java code that worked for me:
boolean liesOnSegment(Coordinate a, Coordinate b, Coordinate c) {
double dotProduct = (c.x - a.x) * (c.x - b.x) + (c.y - a.y) * (c.y - b.y);
return (dotProduct < 0);
}
You could also use the very convenient scikit-spatial library.
For instance, you could create a Line object defined by the two points a and b:
>>> point_a = [0, 0]
>>> point_b = [1, 0]
>>> line = Line.from_points(point_a, point_b)
then you can use the side_point method of the Line class to check whether point c lies on line or not.
>>> line.side_point([0.5, 0])
0
If the output is 0, then point c lies on line.
how about just ensuring that the slope is the same and the point is between the others?
given points (x1, y1) and (x2, y2) ( with x2 > x1)
and candidate point (a,b)
if (b-y1) / (a-x1) = (y2-y2) / (x2-x1) And x1 < a < x2
Then (a,b) must be on line between (x1,y1) and (x2, y2)
Here is my solution with C# in Unity.
private bool _isPointOnLine( Vector2 ptLineStart, Vector2 ptLineEnd, Vector2 ptPoint )
{
bool bRes = false;
if((Mathf.Approximately(ptPoint.x, ptLineStart.x) || Mathf.Approximately(ptPoint.x, ptLineEnd.x)))
{
if(ptPoint.y > ptLineStart.y && ptPoint.y < ptLineEnd.y)
{
bRes = true;
}
}
else if((Mathf.Approximately(ptPoint.y, ptLineStart.y) || Mathf.Approximately(ptPoint.y, ptLineEnd.y)))
{
if(ptPoint.x > ptLineStart.x && ptPoint.x < ptLineEnd.x)
{
bRes = true;
}
}
return bRes;
}
You can do it by solving the line equation for that line segment with the point coordinates you will know whether that point is on the line and then checking the bounds of the segment to know whether it is inside or outside of it. You can apply some threshold because well it is somewhere in space mostl likely defined by a floating point value and you must not hit the exact one.
Example in php
function getLineDefinition($p1=array(0,0), $p2=array(0,0)){
$k = ($p1[1]-$p2[1])/($p1[0]-$p2[0]);
$q = $p1[1]-$k*$p1[0];
return array($k, $q);
}
function isPointOnLineSegment($line=array(array(0,0),array(0,0)), $pt=array(0,0)){
// GET THE LINE DEFINITION y = k.x + q AS array(k, q)
$def = getLineDefinition($line[0], $line[1]);
// use the line definition to find y for the x of your point
$y = $def[0]*$pt[0]+$def[1];
$yMin = min($line[0][1], $line[1][1]);
$yMax = max($line[0][1], $line[1][1]);
// exclude y values that are outside this segments bounds
if($y>$yMax || $y<$yMin) return false;
// calculate the difference of your points y value from the reference value calculated from lines definition
// in ideal cases this would equal 0 but we are dealing with floating point values so we need some threshold value not to lose results
// this is up to you to fine tune
$diff = abs($pt[1]-$y);
$thr = 0.000001;
return $diff<=$thr;
}

Categories

Resources