我写了一个小程序来找到一个点(x,y)相对于一个点(px,py)定义的线和一个角度(deg)相对于x轴的位置(在笛卡尔坐标系中)系统)。
toRadian deg = deg * (pi / 180)
lineSlope deg = tan $ toRadian deg
lineYintercept (x,y) deg = y - (x * lineSlope deg)
relativePointPosition (px,py) deg (x,y)
| s < 0 && deg>=0 && deg<90 = "Left"
| s < 0 && deg>=90 && deg<180 = "Left"
| s < 0 && deg>=180 && deg<270 = "Right"
| s < 0 && deg>=270 && deg<360 = "Right"
| s > 0 && deg>=0 && deg<90 = "Right"
| s > 0 && deg>=90 && deg<180 = "Right"
| s > 0 && deg>=180 && deg<270 = "Left"
| s > 0 && deg>=270 && deg<360 = "Left"
| s > 0 && deg==360 = "Right"
| s < 0 && deg==360 = "Left"
| otherwise = "On the line"
where s = lineSlope deg * x + lineYintercept (px,py) deg - y
对于远离线的点,它的效果非常好,但对于接近或在线上的点来说效果不太好。 如何提高准确度?
答案 0 :(得分:6)
tan(90°)未定义,因此提高精度并不能真正帮助您。由于浮点数的性质(它是圆整的并且不能完全表示π/ 2),对tan(deg * pi / 180)
的调用将给出非常大或非常小的数字,这取决于π/ 2的哪一侧价值四舍五入。
确切的结果是NaN
,但这对你也没什么帮助。您将不得不单独处理这些有问题的案例,或使用不具有此类“例外”案例的其他算法。
答案 1 :(得分:3)
您可以使用angle (px,py)
给出的行可以通过a*x+b*y+c=0
表单上的等式定义给出,其中a,b,c
由<{p}}给出
sin angle*(x-px) - cos angle*(y-py) = 0
因此,您可以简单地定义
lineF angle (px,py) = \ (x,y) -> (sin angle)*(x-px)-(cos angle)*(y-py)
并在像这样的测试中使用它
lineTest angle (px,py) (x,y)
| f (x,y) < -eps = "Left"
| f (x,y) > eps = "Right"
| otherwise = "On the line"
where
f = lineF angle (px,py)
eps = 1e-9 -- confidence interval as Neil Brown specified.
基本上,以最常规形式定义一条线的函数为您提供了一个计算距离线的距离的函数,您可以用它来测试事物的位置。您可能需要一些摆弄来弄清楚"Left"
和"Right"
实际上应该是什么意思的通用线。
答案 2 :(得分:1)
问题是,对tan
或pi
等函数的多次调用都会使您的计算默认为Double
,或者通常为Floating
类型。程序员通常都知道,你应该从不比较两个浮点值的相等性(你是隐式的),因为计算可能经常导致小的错误。最好定义一个自己的函数,它测试两个浮点数的差异是否在特定的限制之内:
equals :: Floating a => a -> a -> Bool
equals a b = abs (a - b) < 0.000001 -- change the value to whatever fits best
首先处理“平等”案件。
答案 3 :(得分:1)
你是如何运行这段代码的?如果线上绝对点,你的方法将只返回该点在线上。如果你在屏幕上绘制线条并读取用户点击的位置,那么除非角度是一个“漂亮”的角度,如0,90等,否则像素不太可能完全位于线上。
首先考虑从(100,100)开始绘制一条恰好0度的线。单击(200,100)将在行上,因为tan 0 == 0,因此lineSlope 0 == 0因此s下降到100 - 100 == 0.但现在考虑该行是否为0.000001度。根据我的计算器,取切线得到像0.000000017这样的数字。所以现在单击(200,100)会将s评估为0.000000017 * 200 + 100 - 100 * 0.000000017 - 100,这一切都会超过0。但是这一点会打破你的平等比较,你的函数会说“右”。
您可能希望将距离与具有epsilon值的行进行比较(例如,请参阅本页的第一部分:http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm - 但稍后不是hacky位)以允许稍微容忍如此接近你在线上计算的线。
答案 4 :(得分:0)
就风格问题而言,使用case
:数据
data Position = L
| R
| OnTheLine
deriving Show
relativePointPosition (px,py) deg (x,y)
| s < 0
= case () of {
_ | deg >= 0 && deg < 90 -> L
| deg >= 90 && deg < 180 -> L
| deg >= 180 && deg < 270 -> R
| deg >= 270 && deg < 360 -> R
| deg == 360 -> L
}
| otherwise -- s > 0
= case () of {
_ | deg >= 0 && deg < 90 -> R
| deg >= 90 && deg < 180 -> R
| deg >= 180 && deg < 270 -> L
| deg >= 270 && deg < 360 -> L
| deg == 360 -> R
| otherwise -> OnTheLine
}
where
s = lineSlope deg * x + lineYintercept (px,py) deg - y