1. 前言
主要記錄一些關于坐標和線段的計算方法。因為經常會碰見,需要在平面上,計算坐標點。
例如兩個坐標點之間的距離,兩個線段是否平行,兩個不相交的線段的交點。
由于程序中的坐標原點,都是左上角開始的。所以很少涉及象限的問題。以下的一些算法,不會強調象限問題。
這里,主要介紹如何使用勾股定理計算坐標距離,斜率計算線段交點等。
2. 根據兩個坐標點,計算距離
平面中,兩點之間,直線最短。而在已知兩個坐標點的x軸和y軸的情況下。我們可以通過勾股定理,來計算兩個坐標點的距離。
因為,兩個坐標點之間x軸的距離和y軸的距離可以看做三角形的兩條直角邊。斜邊就是我們要計算的距離了。
而勾股定理為:a^2^+b^2^=C^2^
讓我們帶入到代碼中來實現:
public double getPointDistance(Point point1, Point point2) {
int a = point2.y - point1.y;
int b = point2.x - point1.x;
return Math.sqrt(a * a + b * b);
}
兩個坐標point1,point2 其實順序無所謂。
兩個x軸坐標相減,得到的是在x軸上的距離。這個值可能為正,也可能為負。但無所謂,因為進行平方之后。只會是正數。
同理,Y軸也是一樣的。所以我們計算時不用管哪個坐標點是前還是后。
Math.sqrt()是 JAVA 提供的開平方工具。
我們得到的X軸的距離和Y軸的距離,都是相對于x軸和y軸垂直的。所以這兩個距離組合的就是直角三角形的兩條直角邊。
兩點的距離就是直角三角形的斜邊了。也就是上面公式中的勾股定義直接計算即可。
有些小伙伴可能就會問了,如果這兩個點的Y軸或者X軸的值是相同的。那么還可以這么計算么?
結論當然是可以了。
用上面的代碼舉例子,如果兩個坐標點的Y軸相同。那么它們的距離實際上就是X軸的距離。
int a = point2.y - point1.y; //兩個值相同,那么a的結果就是0
int b = point2.x - point1.x; // 那么距離就是 b的值。
// 帶入進去0的平方也是0.那么就是b的平方進行開方運算。結果也就是b。(來源:zinyan.com)
Math.sqrt(0+b*b);
所以,如果兩個坐標點的Y軸相同,或者X軸相同。那么最后計算的結果仍然是正確的。
但,我們可以添加一個判斷,來減少這種情況下的多余的平方,開方計算。
所以,完整版代碼如下所示:
public double getPointDistance(Point point1, Point point2) {
int a = point2.y - point1.y;
int b = point2.x - point1.x;
if (a == 0)
return b;
if (b == 0)
return a;
return Math.sqrt(a * a + b * b);
}
但是,如果我們的x軸和y軸的坐標值是 double? 或者 float 。就不能這么判斷了。
因為浮點運算,本身就不精確。我們判斷的時候,需要考慮到這個浮動范圍。
我們也可以不用考慮這方面的優化。因為多一個平方開方,也耗費不了多少內存和時間。
3. 計算兩個線段的交點
計算:在平面直角坐標系中點A和點B組成了線段A,點C和點D組成了線段B。如果他們有交點。那么交點坐標是多少。
而在平面直角坐標系中,同一平面內兩條直線只有相交和平行兩種情況。這個定義是一個數學定理。
所以我們計算交點的時候,可以先處理一下兩個線段是否平行的問題。
3.1 判斷線段是否平行
那么,該如何判斷兩個線段是否平行呢?很簡單比較兩個線段的斜率是否相同即可。
斜率,計算的是一條直線相對橫坐標軸的傾斜角度。所以它也叫做角系數。
例如,這兩個線段,都相較于X軸傾斜了30°,那么不就是證明了這兩個線段是平行線了么。
而直線的斜率公式為:k=(y2-y1)/(x2-x1)。其中K值就是斜率結果了。
那么線段是否平行就可以寫為:
public boolean getParallel(Point pointA, Point pointB, Point pointC, Point pointD) {
int line1K = (pointB.y - pointA.y) / (pointB.x - pointA.x);
int line2K = (pointD.y - pointC.y) / (pointD.x - pointC.x);
return line1K==line2K
}
但是如果碰見了線段的x軸是相同的怎么辦?也就是說線段垂直于X軸上。那么上面的方法就有問題了。
因為pointB.x - pointA.x =0了。
所以,我們需要進行變種:
//實際比較模式:
(pointB.y - pointA.y) / (pointB.x - pointA.x)==(pointD.y - pointC.y) / (pointD.x - pointC.x)
//改除法為乘法:
(pointB.y - pointA.y) * (pointD.x - pointC.x) == (pointD.y - pointC.y) * (pointB.x - pointA.x)
這兩個等式是相同的。
這樣我們就可以判斷兩個線條是否平行了。完整代碼如下:
public boolean getParallel(Point pointA, Point pointB, Point pointC, Point pointD) {
return (pointB.y - pointA.y) * (pointD.x - pointC.x) == (pointD.y - pointC.y) * (pointB.x - pointA.x)
}
那么,方法中的坐標點,有前后要求么?答案是沒有的。只需要知道這個直線上的任意兩點就可以。
點斜式斜率公式:K=(y2-y1)/(x2-x1)?也可以寫為:K=(y1-y2)/(x1-x2) 這兩個公式的結果是等值的。
公式中的K?就是斜率值,而x和y是坐標點X軸和Y軸的值。
將上面的公式進行簡單的變換,我們可以得到:
- y2=K(x2-x1)+y1
- x2=(y2-y1)/K+x1
也就是說,x1,y1 是已知的坐標點。斜率K也知道的情況下。我們如果知道交點的X軸就可以計算出Y軸坐標。反之當我們知道Y軸坐標也可以計算出X軸坐標。
3.2 計算線段交點
在某種情況下,交點坐標的某個值是可以快速確定的。例如其中一條線段垂直X軸。或者平行于X軸。那么它們兩個線段的交點的X軸或者Y軸就是已經明確了。
例如有坐標點:Point pointA?, Point pointB?, Point pointC?, Point pointD。其中 pointA 和 pointB 組合成線段1,pointC 和pointD組合成線段2。
public Point getCross1(Point pointA, Point pointB, Point pointC, Point pointD) {
Point point =new Point();
if (pointA.x - pointB.x == 0) {
//線段1 兩個坐標的x軸相等 說明是垂直x軸的情況,它們的交點x軸就是pointA的x軸
point.x= pointA.x ;
//解釋1:線段1垂直X軸,所以它的斜率值計算是0.無法計算,所以我們使用線段2的坐標計算斜率。
//解釋2:我在其他方法中判斷過平行線的情況,所以如果線段1垂直,那么線段2肯定不會垂直。
int k = (pointD.y-pointC.y)/(pointD.x-pointC.x);
//解釋3:代入公式:y2=K(x2-x1)+y1。在已知x2,x1,y1,K的情況下求y2
point.y= k*(point.x-pointC.x)+pointC.y;
}else if (pointC.x - pointD.x == 0) {
//線段2 垂直X軸的情況。它們的交點X軸就是線段2中的坐標的X軸
point.x = pointC.x;
//解釋1:線段2垂直X軸,所以它的斜率值計算是0.無法計算,所以我們使用線段1的坐標計算斜率。
int k = (pointB.y - pointA.y) / (pointB.x - pointA.x);
//代入公式進行計算y軸坐標值
point.y = k * (point.x - pointA.x) + pointA.y;
}
return point;
}
可以得到垂直的,而平行于x軸的情況也可以參照上面的示例進行額外處理:
public Point getCross1(Point pointA, Point pointB, Point pointC, Point pointD) {
Point point =new Point();
if (pointA.x - pointB.x == 0) {
//線段1 兩個坐標的x軸相等 說明是垂直x軸的情況,它們的交點x軸就是pointA的x軸
point.x= pointA.x ;
//解釋1:線段1垂直X軸,所以它的斜率值計算是0.無法計算,所以我們使用線段2的坐標計算斜率。
//解釋2:我在其他方法中判斷過平行線的情況,所以如果線段1垂直,那么線段2肯定不會垂直。
//因為是交點,所以交點坐標是滿足線段2的斜率公式的。
int k = (pointD.y-pointC.y)/(pointD.x-pointC.x);
//解釋3:代入公式:y2=K(x2-x1)+y1。 在已知x2,x1,y1,K的情況下求y2
point.y= k*(point.x-pointC.x)+pointC.y;
}else if (pointC.x - pointD.x == 0) {
//線段2 垂直X軸的情況。它們的交點X軸就是線段2中的坐標的X軸
point.x = pointC.x;
//解釋1:線段2垂直X軸,所以它的斜率值計算是0.無法計算,所以我們使用線段1的坐標計算斜率。
int k = (pointB.y - pointA.y) / (pointB.x - pointA.x);
//代入公式 進行計算y軸坐標值
point.y = k * (point.x - pointA.x) + pointA.y;
}else if(pointA.y-pointB.y==0){
//說明線段1,平行于X軸
point.y= pointA.y;
//使用線段2,計算斜率K。因為線段1平行x軸是沒有斜率的
int k = (pointD.y-pointC.y)/(pointD.x-pointC.x);
//代入公式: x2=(y2-y1)/K+x1 在已知y2,y1,K,x1的情況下求x2
point.x = (point.y - line2start.y) / k + line2start.x;
}else if(pointD.y-pointC.y==0){
//說明線段2,平行于X軸
point.y = pointD.y;
int k = (pointB.y-pointA.y)/(pointB.x-pointA.x);
//使用線段1,計算斜率K。因為線段2平行x軸是沒有斜率的zinyan.com
point.x =(point.y - line1start.y) / k + line1start.x;
}
return point;
}
通過上面的方法,應該給大家詳細介紹了。線段平行或者垂直情況下??焖儆嬎憬稽c的運算邏輯。
但是,如果線段并不垂直或者平行于X軸或者Y軸。那么如何計算呢?在實際處理過程中,不垂直才是最多的場景。所以上面的方法還需要進行擴充。
仍然使用:Point pointA?, Point pointB?, Point pointC?, Point pointD 這四個坐標點,計算未知的交點。
假如交點坐標是Point point。我們來一步步推導相關的方程組。
int k1 = (pointB.y - pointA.y) / (pointB.x - pointA.x); //線段1的斜率
int k2= (pointD.y - pointC.y) / (pointD.x - pointC.x); //線段2的斜率
線段1的斜率和線段2的斜率肯定是不一樣的。但是線段公式中斜率是一個常量。也就是說只要是直線上的任意兩點,計算出來的斜率是固定的。我們再根據點斜式公式的變種:y2=K(x2-x1)+y1? 和x2=(y2-y1)/K+x1??梢缘玫揭韵拢?/p>
PS: x1 ,x2,y1,y2 只是表示的變量,不是數值*2哦。別弄混了。
int k1 = (pointB.y - pointA.y) / (pointB.x - pointA.x); //線段1的斜率
int k2= (pointD.y - pointC.y) / (pointD.x - pointC.x); //線段2的斜率
point.x =(point.y-pointB.y)/k1+porintB.x; // 根據線段1的點斜式公式可以得到的方程組
point.x =(point.y-pointD.y)/k2+porintD.x; // 根據線段2的點斜式公式可以得到的方程組
point.y = k1(point.x-pointA.x)+pointA.y;
point.y = k2(point.x-pointC.x)+pointC.y;
在上面的計算過程中,x和y的兩種算法得到的結果是相同的。我們先求x軸坐標的話,從y的兩個等式進行計算。得到以下方程組:
k1(point.x-pointA.x)+pointA.y-(k2(point.x-pointC.x)+pointC.y)=0; //第一步
//第一步去除乘法括號
k1*point.x-k1*pointA.x+pointA.y-(k2*point.x-k2*pointC.x+pointC.y)=0;
//去除所有的括號
k1*point.x-k1*pointA.x+pointA.y-k2*point.x+k2*pointC.x-pointC.y=0;
//由于point.x 是未知數,其他的都是已知數。我們將未知數和已知進行等式的移動, 從左邊移動到右邊的時候,正負要互換
k1*point.x-k2*point.x=k1*pointA.x-pointA.y-k2*pointC.x+pointC.y;
//左邊的等式可以繼續簡化
(k1-k2)*point.x = k1*pointA.x-pointA.y-k2*pointC.x+pointC.y;
//確保左邊只有一個point.x 這個未知變量
point.x= (k1*pointA.x-pointA.y-k2*pointC.x+pointC.y)/(k1-k2);
通過上面的推導,當我們知道線段1的斜率,線段2的斜率。以及線段1的某個坐標點坐標。線段2的某個坐標點坐標。
我們就可以直接通過公式:(k1*pointA.x-pointA.y-k2*pointC.x+pointC.y)/(k1-k2)計算出交點的x軸坐標。
當我們知道x軸坐標。之后通過y2=K(x2-x1)+y1點斜式方程的變種。直接計算y軸的坐標:
point.x = (k1*pointA.x-pointA.y-k2*pointC.x+pointC.y)/(k1-k2);
point.y = k1(point.x-pointA.x)+pointA.y; //直接得到Y值的坐標
上面計算y軸坐標是使用的線段1的斜率進行計算的。所以x1和y1的值需要時線段1上的坐標點。
我們也可以使用線段2進行計算得到y軸值:
point.y = k2(point.x-pointC.x)+pointC.y; //直接得到Y值的坐標
到這里,我們就可以得到斜線的交點了。我們總結一下方法:
public Point getCross1(Point pointA, Point pointB, Point pointC, Point pointD) {
Point point =new Point();
if (pointA.x - pointB.x == 0) {
//線段1 兩個坐標的x軸相等 說明是垂直x軸的情況,它們的交點x軸就是pointA的x軸
point.x= pointA.x ;
//解釋1:線段1垂直X軸,所以它的斜率值計算是0.無法計算,所以我們使用線段2的坐標計算斜率。
//解釋2:我在其他方法中判斷過平行線的情況,所以如果線段1垂直,那么線段2肯定不會垂直。
//因為是交點,所以交點坐標是滿足線段2的斜率公式的。
int k = (pointD.y-pointC.y)/(pointD.x-pointC.x);
//解釋3:代入公式:y2=K(x2-x1)+y1。 在已知x2,x1,y1,K的情況下求y2
point.y= k*(point.x-pointC.x)+pointC.y;
}else if (pointC.x - pointD.x == 0) {
//線段2 垂直X軸的情況。它們的交點X軸就是線段2中的坐標的X軸
point.x = pointC.x;
//解釋1:線段2垂直X軸,所以它的斜率值計算是0.無法計算,所以我們使用線段1的坐標計算斜率。
int k = (pointB.y - pointA.y) / (pointB.x - pointA.x);
//代入公式 進行計算y軸坐標值(zinyan.com)
point.y = k * (point.x - pointA.x) + pointA.y;
}else if(pointA.y-pointB.y==0){
//說明線段1,平行于X軸
point.y= pointA.y;
//使用線段2,計算斜率K。因為線段1平行x軸是沒有斜率的
int k = (pointD.y-pointC.y)/(pointD.x-pointC.x);
//代入公式: x2=(y2-y1)/K+x1 在已知y2,y1,K,x1的情況下求x2
point.x = (point.y - line2start.y) / k + line2start.x;
}else if(pointD.y-pointC.y==0){
//說明線段2,平行于X軸
point.y = pointD.y;
int k = (pointB.y-pointA.y)/(pointB.x-pointA.x);
//使用線段1,計算斜率K。因為線段2平行x軸是沒有斜率的
point.x =(point.y - line1start.y) / k + line1start.x;
}else{
int k1 = (pointB.y - pointA.y) / (pointB.x - pointA.x); //線段1的斜率
int k2= (pointD.y - pointC.y) / (pointD.x - pointC.x); //線段2的斜率
point.x = (k1*pointA.x-pointA.y-k2*pointC.x+pointC.y)/(k1-k2);
point.y = k1(point.x-pointA.x)+pointA.y; //直接得到Y值的坐標
}
return point;
}
到這里,我們其實就可以獲取交點了。而除此以外,我們還可以通過斜截式公式,來計算交點
3.3 斜截式計算交點
我們上面的推導過程使用的都是點斜式的公式進行的。其實我們還可以通過直線的斜截式方程:y=kx+b來進行推導直線的交點。相較于點斜式,個人認為斜截式可能會更容易理解吧。
在公式中,K表達的是斜率。斜率計算公式在上面有介紹。就不重復了
而y和x就是我們的坐標點的Y軸值和X軸值。b就是Y軸截距。
在平面直角坐標系中,直線的Y軸截距是相等的。也就是說不管是在直線的哪個點,代入到上面的公式中來得到的b值都是固定的。
public Point getCross1(Point pointA, Point pointB, Point pointC, Point pointD) {
Point point =new Point();
/** 前面直角的方法省略了 主要是判斷斜線的交點*/
int k1 = (pointB.y - pointA.y) / (pointB.x - pointA.x); //得到線段1的 斜率K的值
int b1 = pointA.y - pointA.x * k1; //得到線段1的 Y截距 b的值
int k2 = (pointD.y - pointC.y) / (pointD.x - pointC.x); // 得到線段2的斜率K的值
//斜截式公式:y=kx+b ,進行簡單轉換一下就是:b= y-kx
int b2 = pointC.y - pointC.x * k2; //得到線段2的 Y截距b的值。在這里我們可以使用pointC的值,也可以使用pointD的值
}
然后,由于交點需要滿足線段1的斜截式,也需要滿足線段2的斜截式公式,所以我們可以得到:
point.y = k1 * point.x + b1; // y=kx+b
point.y = k2 * point.x + b2; // y=kx+b
//根據上面的公式轉換。
k1 * point.x + b1 = k2 * point.x + b2; //這樣整個表達式中就只有point.x 這一個變量了。
//根據數學表達式的規則,移動等號兩邊的數據。將未知數移動到左邊
k1 * point.x - k2 * point.x = b2 - b1;// 移動過程中要注意加減法
//然后再提取乘法
(k1 - k2) * point.x = b2 - b1;//再進行表達式變換
point.x = (b2 - b1)/(k1 - k2); //也就是最終的結果值了
當我們知道x值之后。代入斜截式中可以快速得到y值:
point.y = k1 * point.x + b1;
完整版本效果就是:
public Point getCross1(Point pointA, Point pointB, Point pointC, Point pointD) {
Point point =new Point();
/** 前面直角的方法省略了 主要是判斷斜線的交點*/
int k1 = (pointB.y - pointA.y) / (pointB.x - pointA.x); //得到線段1的 斜率K的值
int b1 = pointA.y - pointA.x * k1; //得到線段1的 Y截距 b的值
int k2 = (pointD.y - pointC.y) / (pointD.x - pointC.x); // 得到線段2的斜率K的值
//斜截式公式:y=kx+b ,進行簡單轉換一下就是:b= y-kx
int b2 = pointC.y - pointC.x * k2; //得到線段2的 Y截距b的值。在這里我們可以使用pointC的值,也可以使用pointD的值
point.x = (b2 - b1)/(k1 - k2);
point.y = k1 * point.x + b1;
}
有些公式可能寫的結果是這樣的:
//情況1
point.x = (b2 - b1)/(k1 - k2);
//情況2
point.x = (b1 - b2)/(k2 - k1);
這兩個情況下,是等效的。在上面介紹了情況1的表達式是如何推導的。現在介紹一下如何推導出情況2:
point.y = k1 * point.x + b1; // y=kx+b
point.y = k2 * point.x + b2; // y=kx+b
//根據上面的公式轉換。
k1 * point.x + b1 = k2 * point.x + b2; //這樣整個表達式中就只有point.x 這一個變量了。
//將未知數移動到右側
b1-b2 = k2*point.x-k1*point.x;// 移動過程中要注意加減法
//然后再提取乘法
b1-b2 = (k2-k1)*point.x;//再進行表達式變換
point.x = (b1 - b2)/(k2 - k1); //也就是最終的結果值了
所以,這種表達式結果是一致的。
PS:在上面的代碼中,我的變量是int型的。那是因為我自定義的類型參數。你如果是double也是沒有關系的。單位格式不影響計算邏輯。只是最終結果值的精度有差異而已。
3. 小結
到這里,詳細介紹了平面坐標系下的距離判斷。線段平行和線段交點的計算。關于斜率的計算,稍微涉及了高中的知識。但是整體的計算過程也就初中水平了。
只是由于很長時間沒有接觸了。一些概念和公式都忘記完了。
所以,才會按照完全不懂的情況下。充分介紹一下這中間的運算過程。
后面可能會更新,如何計算角度。根據坐標點,計算運動方向等等吧。