2点の平面(aとbと呼ばれる)があり、各点のx整数とy整数で表されているとします。
別の点cがaとbで定義された線分上にあるかどうかをどのように判断できますか?
私はpythonを最も使用していますが、どの言語の例も役に立ちます。
2点の平面(aとbと呼ばれる)があり、各点のx整数とy整数で表されているとします。
別の点cがaとbで定義された線分上にあるかどうかをどのように判断できますか?
私はpythonを最も使用していますが、どの言語の例も役に立ちます。
回答:
(ba)と(ca)の外積が0であるかどうかをチェックします。これは、Darius Baconが指示するように、点a、b、cが整列しているかどうかを示します。
ただし、cがaとbの間にあるかどうかを知りたい場合は、(ba)と(ca)のドット積が正であり、aとbの間の距離の2乗よりも小さいことも確認する必要があります。
最適化されていない疑似コード:
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
してc-a
いる0
:手段すべての点が同一直線上にあること。彼らはしている場合ならば、チェックc
の座標が間にあるa
Sと' 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
この答えは、以前は3つの更新の混乱でした。彼らからの有益な情報:Beautiful CodeのBrian Hayesの章は、共線性テスト関数の設計空間をカバーしています-有用な背景。ヴィンセントの答えはこれを改善するのに役立ちました。そして、x座標またはy座標のいずれか1つのみをテストすることを提案したのはヘイズでした。元々はの代わりにコードがありました。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)
==
内はis_between()
(ところで、それは変装でのCrossProductです)失敗する可能性があります。
is_between()
:a, b = self.a, self.b
C ++で提供されるコードを使用して、これを実行する別の方法を次に示します。2つの点、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コンポーネントをこれら2つの式に代入して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と等しいかどうかをテストします。
is_on_segment = abs(ac + bc - ab) < EPSILON
これは、次の3つの可能性があるためです。
わかりました、線形代数(ベクトルの外積)についての言及がたくさんあり、これは実(つまり、連続または浮動小数点)空間で機能しますが、質問では2つの点が整数として表現されているため、外積は正しくありません近似解を与えることができますが、解。
正しい解決策は、2点間でBresenhamの線アルゴリズムを使用し、3番目の点が線上の点の1つであるかどうかを確認することです。アルゴリズムの計算が非パフォーマンスになるほどポイントが遠い場合(そして、そうなるには実際に大きくする必要があります)、あなたは掘り下げて最適化を見つけることができると私は確信しています。
(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
ユーザーカーソルが特定の行の上または近くにあるかどうかを検出するためにhtml5キャンバスで使用するjavascriptにこれが必要でした。そこで、Darius Baconの回答をコーヒースクリプトに変更しました。
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
これが私が学校でそれをした方法です。なぜそれが良くないのか忘れてしまいました。
編集:
@Darius Bacon:以下のコードが良い考えではない理由を説明した「Beautiful Code」の本を引用しています。
#!/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はベクトルです)は、2つのベクトルaおよびbの線形結合として表すことができます。
つまり、cが線分(a、b)にある場合:
c = ma + (1 - m)b, where 0 <= m <= 1
mを解くと、次のようになります。
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/ から-> Subject 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)
if(b-y1)/(a-x1)=(y2-y2)/(x2-x1)And x1 <a <x2
次に、(a、b)は(x1、y1)と(x2、y2)の間の線上になければなりません
Vector2Dクラスを使用したC#の回答
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#でのオペレーターのオーバーロードによるセグメントベクトルの内積です。
重要なのは、点の無限線への射影を利用して、射影のスカラー量が射影がセグメント上にあるかどうかを簡単に教えてくれることを観察することです。スカラー量の範囲を調整して、ファジー許容値を使用できます。
投影が境界内にある場合は、点から投影までの距離が境界内にあるかどうかをテストします。
クロスプロダクトアプローチに対する利点は、許容誤差に意味のある値があることです。
UnityのC#を使用した私のソリューションを次に示します。
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;
}
ジュールの回答の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;
}