Как определить точку между двумя другими точками на отрезке линии?

Допустим, у вас есть двухмерная плоскость с 2 точками (называемыми a и b), представленная целым числом x и целым числом y для каждой точки.

Как определить, находится ли другая точка c на отрезке, определяемом элементами a и b?

Я чаще всего использую python, но примеры на любом языке были бы полезны.

Я вижу, что в этих ответах происходит ОЧЕНЬ много материала length = sqrt (x); они могут работать, но они не быстрые. Рассмотрите возможность использования квадрата длины; если вы просто сравниваете значения квадратов длины друг с другом, нет потери точности и вы сохраняете медленные вызовы sqrt ().

ojrac 30.11.2008 03:34

Представлена ​​ли точка c двумя целыми числами? Если да, то хотите ли вы знать, находится ли c точно вдоль реальной прямой линии между a и b или лежит на растровой аппроксимации прямой линии между a и b? Это важное уточнение.

RobS 02.12.2008 12:34

Здесь задан аналогичный вопрос: stackoverflow.com/q/31346862/1914034 с решением, когда необходимо буферное расстояние от линии

Below the Radar 16.07.2015 15:09

Предупреждение для будущих читателей: изрядное количество ответов неверны или неполны. Несколько крайних случаев, которые часто не работают, - это горизонтальные и вертикальные линии.

Stefnotch 04.08.2019 21:57
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
100
5
124 935
20
Перейти к ответу Данный вопрос помечен как решенный

Ответы 20

Проверьте, является ли перекрестное произведение b-a и c-a0: это означает, что все точки коллинеарны. Если да, проверьте, находятся ли координаты c между a и b. Используйте координаты x или y, если 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

Раньше этот ответ состоял из трех обновлений. Полезная информация от них: глава Брайана Хейса в Красивый код охватывает пространство дизайна для функции проверки коллинеарности - полезный фон. Винсент ответ помог улучшить это. И именно Хейс предложил проверить только одну из координат x или y; изначально код имел and вместо if a.x != b.x else.

Поскольку проверка диапазона выполняется быстрее, было бы лучше сначала проверить диапазон, а затем проверить коллинеарность в ограничивающей рамке.

Grant M 17.01.2013 18:53

Функция is_on (a, b, c) неверна для случая, когда a == b! = C. В таком случае он вернет true, даже если c не пересекает отрезок прямой от a до b.

Mikko Virkkilä 20.10.2015 16:35

@SuperFlux, я просто попробовал запустить это, и получил False.

Darius Bacon 22.10.2015 04:54

Я думаю, что этот ответ явно превосходит текущий принятый ответ.

Rick supports Monica 08.06.2016 17:27

поэтому в основном, если (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y) == 0, то они коллинеарны. Но что означают отрицательные и положительные значения?

John Demetriou 27.04.2017 14:19

@JohnDemetriou, это ориентированная область треугольника через a, b и c. «Область» может быть положительной или отрицательной, в зависимости от того, идет ли трассировка в этом порядке по часовой стрелке или против часовой стрелки (но сейчас мне лень говорить вам, в каком направлении будет положительный результат, а какой - отрицательный).

Darius Bacon 28.04.2017 02:36

@ MikkoVirkkilä: return (p <= q <= r or r <= q <= p) and (p != r or p == q == r)

Paulo Neves 22.08.2017 16:23

collinear (a, b, c) проверяет числа с плавающей запятой на равенство. Разве не следует использовать эпсилон / толерантность?

jwezorek 30.03.2020 19:36
Ответ принят как подходящий

Убедитесь, что перекрестное произведение (b-a) и (c-a) равно 0, как говорит Дариус Бэкон, сообщает вам, выровнены ли точки a, b и c.

Но, поскольку вы хотите знать, находится ли c между a и b, вы также должны проверить, что скалярное произведение для (b-a) и (c-a) равно положительный и меньше, чем квадрат расстояния между 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) достаточно, не правда ли?
jfs 30.11.2008 05:01

Да, глупый я. Это ответ Шридхара Айера с перекрестным произведением вместо наклонов. Как я уже сказал, есть несколько возможных ответов. :)

Cyrille Ka 30.11.2008 07:03

Абсолютное значение перекрестного произведения в два раза больше площади треугольника, образованного тремя точками (со знаком, указывающим сторону третьей точки), поэтому ИМХО вам следует использовать эпсилон, который пропорционален расстоянию между двумя конечными точками.

bart 30.11.2008 12:21

Это работает в реальном пространстве, а не в целочисленном, как просил плакат.

cletus 01.12.2008 13:13

Можете ли вы сказать нам, почему это не работает с целыми числами? Я не вижу проблемы при условии, что эпсилон-проверка заменена на "! = 0".

Cyrille Ka 01.12.2008 18:48

Потому что либо а) приближенная линия между двумя точками важна, и эти векторы не совпадают в реальном выражении, либо б) вас интересует только точное совпадение, и в этом случае я уверен, что вы могли бы уменьшить его до наибольшего общего множителя тип расчета.

cletus 03.12.2008 14:20

После проверки условия перекрестного произведения проще проверить, что (c-a). (B-c)> 0 (с равенством, когда c = a или c = b); это просто говорит о том, что параллельные векторы c-a и b-c должны указывать в одном направлении.

Sumudu Fernando 29.09.2011 12:10

Я думаю, что это искажает мой ответ, который также проверяет условие промежуточности.

Darius Bacon 12.12.2011 01:07

Это только я или что-то не хватает в выражении для squaredlengthba? Почему лишние скобки?

Rob 20.11.2012 23:08

Да, лишние скобки - это всего лишь опечатка. Прошло 4 года, прежде чем кто-то что-то сказал. :)

Cyrille Ka 22.11.2012 07:36

Вам следует переименовать a, b, c, чтобы было понятнее, какие точки являются конечными точками сегмента, а какие - точкой запроса.

Craig Gidney 19.07.2013 20:37

Разве это не должно быть if abs(crossproduct) < epsilon: Из-за того простого факта, что это может привести к 0-делению, если перекрестное произведение равно 0

Climax 18.06.2018 10:30

Какое значение имеет эпсилон? Очень маленькое число, которое нужно учитывать при ошибках с плавающей запятой?

nivs1978 27.12.2018 23:47

Я не получаю перекрестное произведение как ноль при использовании точек (.5,0), (2,1), (1.6, .6). Перекрестное произведение в -0,2. Что могло быть не так? Я использую следующий код для тестирования: python ax,ay = (.5,0) bx,by = (2,1) cx,cy = (1.6,.6) crossproduct = (cy - ay) * (bx - ax) - (cx - ax) * (by - ay)

Amar C 08.02.2020 22:37

@AmarC: эти 3 точки не совпадают, вот почему. Если бы C был (1,4, .6), например, это было бы 0.

Cyrille Ka 10.02.2020 20:01

если скалярное произведение равно 0, это означает, что угол равен 90 ° => два вектора ортогональны, и поэтому точки не находятся на одной линии. Разве мы не должны проверять dotproduct <= 0 вместо <0? На первом этапе мы уже проверили, что они лежат в одной строке, но не будет ли <= немного логичнее? Или у меня ошибочное предположение?

Schlumpf 17.11.2020 15:15

Проверка на квадрат длины я еще не поняла. Почему это предположение верно? Точечный результат дает только указание угла между двумя векторами, который должен быть ~ 0 °, когда мы делим результат по его длине и используем arccos, поскольку два вектора параллельны. Так почему же это работает, если я сравниваю квадрат длины ba (AB²) с скалярным произведением двух векторов (AC и AB)?

Schlumpf 17.11.2020 15:22

Скалярное произведение между (c-a) и (b-a) должно быть равно произведению их длин (это означает, что векторы (c-a) и (b-a) выровнены и имеют одинаковое направление). Более того, длина (c-a) должна быть меньше или равна длине (b-a). Псевдокод:

# 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

Разве последнее условие не должно быть больше похоже на: ABS (product - lengthca * lengthba) <epsilon?

Jonathan Leffler 30.11.2008 03:12

Разве вам не следует вместо этого сравнивать квадраты длины? Следует избегать квадратных корней. Кроме того, если это неизбежно из-за переполнения, вы можете использовать math.hypot вместо math.sqrt (с соответствующим изменением аргументов).

Darius Bacon 30.11.2008 03:41

Меня тоже интересует этот эпсилон. Вы можете это объяснить? Конечно, если мы должны иметь дело с числами с плавающей запятой, мы должны быть осторожны с сравнениями, но мне не ясно, почему эпсилон делает это конкретное сравнение более точным.

Darius Bacon 30.11.2008 03:42

Я согласен. На этот вопрос есть несколько хороших ответов, и это нормально. Но этот код нужно изменить, чтобы не использовать sqrt, и последнее сравнение исправлено.

Cyrille Ka 30.11.2008 03:47

@Jonathan: действительно, код более знакомый и элегантный, используя abs. Спасибо.

Federico A. Ramponi 30.11.2008 05:16

Что касается эпсилон-материала: это просто вопрос отказа от использования == для сравнения чисел с плавающей запятой. Никогда не используйте x == y с числами с плавающей запятой. Вместо этого вы должны использовать abs (x-y) <= epsilon (small). В исправленном коде я использую> epsilon для выражения! =.

Federico A. Ramponi 30.11.2008 05:33

Обратите внимание, что мне не нравятся решения, в которых используются кросс-продукты. Точечное произведение и / или расстояние работают также в 4-мерном или n-мерном пространстве, а перекрестное произведение (как операция над двумя векторами) - нет.

Federico A. Ramponi 30.11.2008 05:37

Теперь он больше не использует sqrt.

Federico A. Ramponi 30.11.2008 06:11

Извините, мой эпсилон-вопрос был глупым; Мне удалось запутать ваше равенство и сравнение меньше чем. Это хороший вопрос об обобщении на более высокие измерения; Я догадываюсь, что вы могли бы ответить на этот вопрос с помощью внешнего продукта, но я не могу понять это сейчас.

Darius Bacon 30.11.2008 08:52

Мне понравилась идея сделать это без дорогостоящей операции извлечения квадратного корня, но я только что на собственном опыте убедился, что это не работает. У нас есть три точки: ab равно 4, bc равно 4, ac равно 6. a + b> c (4 + 4> 6) - это не линия. И все же a ^ 2 + b ^ 2 <c ^ 2 (16 + 16 <36).

Loren Pechtel 13.10.2012 08:19

Вот еще один подход:

  • Предположим, что две точки - это A (x1, y1) и B (x2, y2)
  • Уравнение прямой, проходящей через эти точки: (x-x1) / (y-y1) = (x2-x1) / (y2-y1) .. (просто приравнивая наклоны)

Точка C (x3, y3) будет находиться между A и B, если:

  • x3, y3 удовлетворяет приведенному выше уравнению.
  • x3 лежит между x1 и x2, а y3 лежит между y1 и y2 (тривиальная проверка)

При этом не учитываются ошибки округления (неточность координат).

bart 30.11.2008 12:23

Я думаю, это правильная идея, но в ней мало деталей (как мы можем проверить это уравнение на практике?) И немного ошибочно: последним y3 должно быть y2.

Darius Bacon 30.11.2008 20:37

Используя более геометрический подход, рассчитайте следующие расстояния:

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 + bcab:

is_on_segment = abs(ac + bc - ab) < EPSILON

Это потому, что есть три возможности:

  • 3 точки образуют треугольник => ac + bc> ab
  • Они коллинеарны, и c находится за пределами сегмента ab => ac + bc> ab
  • Они коллинеарны и c находится внутри сегмента ab => ac + bc = ab

Как упоминает Джонатан Леффлер в другом комментарии, у этого есть числовые проблемы, которых избегают другие подходы, такие как перекрестное произведение. Глава, на которую я ссылаюсь в своем ответе, объясняет.

Darius Bacon 30.11.2008 20:30

Вот как бы я это сделал:

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)

Единственная проблема с этим - числовая стабильность - учет разностей чисел и т. д. Может привести к потере точности.

Jonathan Leffler 30.11.2008 03:10
-epsilon < (distance(a, c) + distance(c, b) - distance(a, b)) < epsilon
jfs 30.11.2008 04:04

@jfs, что вы имеете в виду? Для чего нужен чек с эпсилоном?

Neon Warge 30.08.2017 11:40

@NeonWarge: sqrt () возвращает число с плавающей запятой. == в большинстве случаев не подходит для поплавков.. Вместо этого можно использовать math.isclose(). В 2008 году не было math.isclose(), и поэтому я указал явное неравенство с epsilon (abs_tol на языке math.isclose()).

jfs 30.08.2017 12:03

Это абсолютно правильно, однако этот метод не очень хорош даже с math.isclose (). Когда координаты являются целыми числами, вы хотели бы провести точный тест. Мой другой ответ дает формулу для этого: stackoverflow.com/a/29301940/40078

Jules 06.09.2017 03:00

Вот как я это делал в школе. Я забыл, почему это плохая идея.

Обновлено:

@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()

Длина сегмента не важна, поэтому использование квадратного корня не требуется, и его следует избегать, поскольку мы можем потерять некоторую точность.

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)

Если c.x или c.y являются плавающими, то первый == в is_between() может выйти из строя (кстати, это замаскированный перекрестный продукт).

jfs 30.11.2008 07:11

добавить в is_between(): a, b = self.a, self.b

jfs 30.11.2008 20:26

Похоже, что это вернет истину, если все три точки одинаковы (что нормально, имхо), но ложь, если ровно две точки одинаковы - довольно непоследовательный способ определения промежуточности. Я разместил альтернативу в своем ответе.

Darius Bacon 30.11.2008 22:21

исправил это с помощью другого трюка cmp, но этот код начинает пахнуть ;-)

vincent 30.11.2008 23:20

Хорошо, много упоминаний линейной алгебры (перекрестное произведение векторов), и это работает в реальном (т.е. непрерывном или с плавающей запятой) пространстве, но в вопросе конкретно говорилось, что две точки были выражены как целые числа, и, таким образом, перекрестное произведение не является правильным решение, хотя оно может дать приблизительное решение.

Правильное решение - использовать Алгоритм линии Брезенхема между двумя точками и посмотреть, является ли третья точка одной из точек на линии. Если точки находятся на достаточно большом расстоянии, что вычисление алгоритма неэффективно (а для этого он должен быть действительно большим), я уверен, что вы могли бы покопаться и найти оптимизацию.

Он решает, как провести линию через двумерное целочисленное пространство между двумя произвольными точками, и это математически правильно. Если третья точка является одной из точек на этой линии, то она по определению находится между этими двумя точками.

cletus 01.12.2008 13:12

Нет, алгоритм линии Брезенхема решает, как создать приближение сегмента линии в двумерном целочисленном пространстве. Из сообщения оригинального плаката я не вижу, что это был вопрос о растеризации.

Cyrille Ka 01.12.2008 18:54

«Допустим, у вас есть двухмерная плоскость с 2 точками (называемыми a и b), представленная x INTEGER и y INTEGER для каждой точки». (курсив добавлен мной).

cletus 03.12.2008 14:17

Я думаю, что алгоритм линий Брезенхема дает целые точки шкафа для линии, которые затем можно использовать для рисования линии. Они могут не быть на связи. Например, если для значений от (0,0) до (11,13) алгоритм выдаст количество пикселей для рисования, но нет целых точек, кроме конечных точек, потому что 11 и 13 взаимно просты.

Grant M 15.01.2013 17:49

Как может решение, правильное для реального пространства (ℝ × ℝ), не быть правильным для целочисленного пространства (ℕ × ℕ), как ℕ∈ℝ. Или вы имеете в виду: «не оптимально для…» вместо «неправильно»?

Ideogram 09.01.2019 09:47

как насчет того, чтобы просто убедиться, что наклон такой же, а точка находится между остальными?

заданные точки (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)

Как насчет сумасшедших проблем с точностью с плавающей запятой, когда некоторые координаты близки или идентичны?

Robin Davies 05.04.2017 07:18

Компьютеры плохо справляются с вычислениями с плавающей запятой. В компьютере нет такой вещи, как бесконечно плавно регулируемые значения. Итак, если вы используете плавающие точки, вы должны установить определение и использовать небольшое значение эпсилон в качестве детерминанта, и любые две точки, расположенные ближе, чем этот эпсилон, должны рассматриваться как одна и та же точка. Определите точку, которая находится на той же линии и на том же расстоянии от конечных точек. Если ваша кандидатная точка находится в пределах вашего эпсилона от этой вычисленной точки, назовите ее идентичной.

Charles Bretana 05.04.2017 15:04

Я хотел сказать, что этот ответ непригоден для использования из-за проблем с точностью, когда вы фактически реализуете его в коде. Так что никто не должен его использовать. Прекрасный ответ на тест по математике. Но полный провал в компьютерном курсе. Я пришел сюда в поисках метода скалярного произведения (что верно); поэтому я подумал, что займу несколько минут, чтобы отметить многие ответы в этой цепочке, которые неверны, чтобы другие, знакомые с правильным решением, не испытали соблазна их использовать.

Robin Davies 13.04.2017 20:01

Вы правы в отношении проблем, возникающих из-за неспособности компьютеров представить все возможные действительные числа в строке. Вы ошибаетесь, что любое решение (включая метод скалярного произведения) может быть невосприимчивым к этим проблемам. Любое решение может страдать от этих проблем. Если вы не сделаете некоторые допущения для приемлемого эпсилон, точка, которая находится точно на линии (но чьи координаты не представлены в двоичном представлении с плавающей запятой ieee), также не пройдет тест скалярного произведения, поскольку компьютер будет представлять координаты точки неточно. на некоторую сумму.

Charles Bretana 13.04.2017 21:04

Любая точка на линейном сегменте (а, б) (где а и б - векторы) может быть выражена как линейная комбинация двух векторов а и б:

Другими словами, если c лежит на линейном сегменте (а, б):

c = ma + (1 - m)b, where 0 <= m <= 1

Решая для мм>, получаем:

m = (c.x - b.x)/(a.x - b.x) = (c.y - b.y)/(a.y - b.y)

Итак, наш тест выглядит (на 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;
        }

Правильный способ избежать проблем с точностью в большинстве других подходов. Также значительно более эффективен, чем большинство других подходов.

Robin Davies 05.04.2017 07:20

Мне это нужно для 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

Вот другой способ сделать это с кодом на C++. Учитывая две точки, l1 и l2, легко выразить отрезок прямой между ними как

l1 + A(l2 - l1)

где 0 <= A <= 1. Это известно как векторное представление линии, если вас интересует не только его использование для этой задачи. Мы можем разделить его на компоненты x и y, получив:

x = l1.x + A(l2.x - l1.x)
y = l1.y + A(l2.y - l1.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;
}

Вот код 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);
    return (dotProduct < 0);
}

dotProduct может сказать только о выравнивании. Ваш код неполный !!! С a (0,0), b (4,0), c (1,1) у вас есть dotproduct = (1-0) * (1-4) + (1-0) * (1-0) = - 3 + 1 = -3

user43968 02.06.2018 19:03

@ user43968 ты прав. Забыл, что тогда я работал с векторами. Ответ от Сирилла Ка правильный (полный)!

golwig 17.12.2020 19:47

Ответ на 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;
}

Обратите внимание, что

s * s

- скалярное произведение вектора сегмента через перегрузку оператора в C#

Ключ состоит в том, чтобы воспользоваться преимуществом проекции точки на бесконечную линию и заметить, что скалярная величина проекции тривиально говорит нам, находится ли проекция на отрезке или нет. Мы можем настроить границы скалярной величины, чтобы использовать допуск нечеткости.

Если проекция находится в пределах границ, мы просто проверяем, находится ли расстояние от точки до проекции в пределах границ.

Преимущество метода перекрестного произведения в том, что допуск имеет значимое значение.

Вы можете использовать клин и точечное произведение:

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

Вот мое решение с 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;
}

Похоже, этот код работает только с вертикальными и горизонтальными отрезками линий. Что, если ptLineStart - это (0,0), ptLineEnd - это (2,2), а ptPoint - (1, 1)?

vac 11.02.2018 00:17

C# версия ответа Жюля:

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;
    
}

Другие вопросы по теме