Предположим, у вас есть двухмерная плоскость с 2 точками (называемыми a и b), представленная целым числом x и целым числом y для каждой точки.
Как вы можете определить, находится ли другая точка c на отрезке, определяемом элементами a и b?
Я чаще всего использую python, но мне бы пригодились примеры на любом языке.
Ответы:
Убедитесь, что перекрестное произведение (ba) и (ca) равно 0, как говорит Дариус Бэкон, говорит вам, выровнены ли точки a, b и c.
Но, поскольку вы хотите знать, находится ли c между a и b, вы также должны проверить, что скалярное произведение (ba) и (ca) положительно и меньше квадрата расстояния между a и b.
В неоптимизированном псевдокоде:
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
источник
-epsilon < crossproduct < epsilon and min(a.x, b.x) <= c.x <= max(a.x, b.x) and min(a.y, b.y) <= c.y <= max(a.y, b.y)
достаточно, не так ли?Вот как бы я это сделал:
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)
источник
-epsilon < (distance(a, c) + distance(c, b) - distance(a, b)) < epsilon
==
в большинстве случаев не подходит для поплавков .math.isclose()
можно было бы использовать вместо этого. Вmath.isclose()
2008 году не было, поэтому я указал явное неравенство сepsilon
(abs_tol
вmath.isclose()
разговоре).Проверьте, равно ли перекрестное произведение
b-a
и : это означает, что все точки коллинеарны. Если да, проверьте, находятся ли координаты между точками «s» и «s». Используйте координаты x или y, если и являются отдельными на этой оси (или они одинаковы для обеих).c-a
0
c
a
b
a
b
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
Раньше этот ответ состоял из трех обновлений. Полезная информация от них: глава Брайана Хейса в « Beautiful Code» описывает пространство дизайна для функции проверки коллинеарности - полезный фон. Ответ Винсента помог улучшить это. И именно Хейс предложил проверить только одну из координат x или y; изначально код имел
and
место вместоif a.x != b.x else
.источник
Вот еще один подход:
Точка C (x3, y3) будет находиться между A и B, если:
источник
Длина сегмента не важна, поэтому использование квадратного корня не требуется, и его следует избегать, поскольку мы можем потерять некоторую точность.
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)
Случайный пример использования:
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)
источник
==
inis_between()
может не сработать (кстати, это замаскированный перекрестный продукт).is_between()
:a, b = self.a, self.b
Вот другой способ сделать это с кодом на C ++. Учитывая две точки, l1 и l2, легко выразить отрезок прямой между ними как
где 0 <= A <= 1. Это известно как векторное представление линии, если вас интересует не только его использование для этой задачи. Мы можем разделить его на компоненты x и y, получив:
Возьмите точку (x, y) и подставьте ее компоненты x и y в эти два выражения, чтобы найти A. Точка находится на линии, если решения для A в обоих выражениях равны и 0 <= A <= 1. Поскольку решение для A требует деления, есть особые случаи, когда требуется обработка, чтобы остановить деление на ноль, когда сегмент линии является горизонтальным или вертикальным. Окончательное решение выглядит следующим образом:
// 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; }
источник
Используя более геометрический подход, рассчитайте следующие расстояния:
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)
и проверьте, равно ли ac + bc ab :
Это потому, что есть три возможности:
источник
Хорошо, много упоминаний линейной алгебры (перекрестное произведение векторов), и это работает в реальном (т.е. непрерывном или с плавающей запятой) пространстве, но в вопросе конкретно говорилось, что две точки были выражены как целые числа, и, таким образом, перекрестное произведение не является правильным решение, хотя оно может дать приблизительное решение.
Правильное решение - использовать алгоритм линии Брезенхема между двумя точками и посмотреть, является ли третья точка одной из точек на линии. Если точки находятся на достаточно большом расстоянии, что вычисление алгоритма неэффективно (а для этого он должен быть действительно большим), я уверен, что вы могли бы покопаться и найти оптимизацию.
источник
Скалярное произведение между (ca) и (ba) должно быть равно произведению их длин (это означает, что векторы (ca) и (ba) выровнены и имеют одинаковое направление). Более того, длина (ca) должна быть меньше или равна длине (ba). Псевдокод:
# 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
источник
Мне это нужно для javascript для использования в холсте html5 для определения, находится ли курсор пользователя над определенной строкой или рядом с ней. Поэтому я преобразовал ответ Дариуса Бэкона в 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
источник
Вы можете использовать клин и точечное произведение:
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
источник
Вот как я делал это в школе. Я забыл, почему это плохая идея.
РЕДАКТИРОВАТЬ:
@Darius Bacon: цитирует книгу «Красивый код», в которой объясняется, почему приведенный ниже код не является хорошей идеей.
#!/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()
источник
Любая точка на отрезке ( a , b ) (где a и b - векторы) может быть выражена как линейная комбинация двух векторов a и b :
Другими словами, если c лежит на отрезке ( a , b ):
c = ma + (1 - m)b, where 0 <= m <= 1
Решая относительно m , получаем:
Итак, наш тест выглядит (на 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 # Из http://www.faqs.org/faqs/graphics/algorithms-faq/ -> Тема 1.02: Как найти расстояние от точки до линии?
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; }
источник
Вот код Java, который у меня сработал:
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); if (dotProduct < 0) return true; return false; }
источник
как насчет того, чтобы просто убедиться, что угол наклона такой же, а точка находится между остальными?
заданные точки (x1, y1) и (x2, y2) (с x2> x1) и точка-кандидат (a, b)
если (b-y1) / (a-x1) = (y2-y2) / (x2-x1) и x1 <a <x2
Тогда (a, b) должен быть на линии между (x1, y1) и (x2, y2)
источник
Ответ на C # с использованием класса Vector2D
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; }
Обратите внимание, что
- скалярное произведение вектора сегмента через перегрузку оператора в C #
Ключ состоит в том, чтобы воспользоваться преимуществом проекции точки на бесконечную линию и заметить, что скалярная величина проекции тривиально говорит нам, находится ли проекция на отрезке или нет. Мы можем настроить границы скалярной величины, чтобы использовать допуск нечеткости.
Если проекция находится в пределах границ, мы просто проверяем, находится ли расстояние от точки до проекции в пределах границ.
Преимущество метода перекрестного произведения в том, что допуск имеет значимое значение.
источник
Вот мое решение с C # в 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; }
источник
С # версия ответа Жюля:
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; }
источник
Вы можете сделать это, решив линейное уравнение для этого линейного сегмента с координатами точки, вы будете знать, находится ли эта точка на линии, а затем проверив границы сегмента, чтобы узнать, находится ли он внутри или снаружи. Вы можете применить некоторый порог, потому что он где-то в пространстве, скорее всего, определяется значением с плавающей запятой, и вы не должны попадать в точное значение. Пример на 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; }
источник