GIS開發 圖形常見算法


摘錄:OSGeo中國中心 http://www.osgeo.cn/post/ae457

計算機的出現使得很多原本十分繁瑣的工作得以大幅度簡化,但是也有一些在人們直觀看來很容易的問題卻需要拿出一套並不簡單的通用解決方案,比如幾何問題。在本文中,我們將對計算幾何常用的基本算法做一個全面的介紹,希望對您了解並應用計算幾何的知識解決問題起到幫助。

矢量的概念

如果一條線段的端點是有次序之分的,我們把這種線段成為有向線段(directed segment)。如果有向線段p1p2的起點p1在坐標原點,我們可以把它稱為矢量(vector)p2。

矢量加減法

設二維矢量P = ( x1,y1 ),Q = ( x2 ,y2 ),則矢量加法定義為: P + Q = ( x1 + x2 , y1 + y2 ),同樣的,矢量減法定義為: P - Q =( x1 - x2 , y1 - y2 )。顯然有性質 P + Q = Q + P , P - Q = - ( Q - P )。

矢量叉積

計算矢量叉積是與直線和線段相關算法的核心部分。設矢量P =(x1,y1),Q = (x2,y2),則矢量叉積定義為由(0,0)、p1、p2和p1+p2所組成的平行四邊形的帶符號的面積,即:P× Q = x1y2 - x2y1,其結果是一個標量。顯然有性質 P× Q = - ( Q× P )和 P× ( - Q ) = - ( P× Q )。一般在不加說明的情況下,本文下述算法中所有的點都看作矢量,兩點的加減法就是矢量相加減,而點的乘法則看作矢量叉積。

叉積的一個非常重要性質是可以通過它的符號判斷兩矢量相互之間的順逆時針關系:

若 P× Q > 0 ,則P在Q的順時針方向。若 P × Q < 0 ,則P在Q的逆時針方向。若 P × Q = 0 ,則P與Q共線,但可能同向也可能反向。

折線段的拐向判斷

折線段的拐向判斷方法可以直接由矢量叉積的性質推出。對於有公共端點的線段p0p1和p1p2,通過計算(p2 - p0) × (p1 - p0)的符號便可以確定折線段的拐向:

若(p2 - p0)× (p1 - p0) > 0,則p0p1在p1點拐向右側后得到p1p2。

若(p2 - p0)× (p1 - p0) < 0,則p0p1在p1點拐向左側后得到p1p2。

若(p2 - p0)× (p1 - p0) = 0,則p0、p1、p2三點共線。

判斷點是否在線段上

設 點為Q,線段為P1P2,判斷點Q在該線段上的依據是:( Q- P1 )× ( P2 - P1 ) = 0且 Q在以 P1,P2為對角頂點的矩形內。前者保證Q點在直線P1P2上,后者是保證Q點不在線段P1P2的延長線或反向延長線上,對於這一步驟的判斷可以用以下過 程實現:

ON-SEGMENT(pi,pj,pk) if min(xi,xj)<=xk<=max(xi,xj) andmin(yi,yj)<=yk<=max(yi,yj) then return true; else return false;

特別要注意的是,由於需要考慮水平線段和垂直線段兩種特殊情況,min(xi,xj)<=xk<=max(xi,xj)和min(yi,yj)<=yk<=max(yi,yj)兩個條件必須同時滿足才能返回真值。

判斷兩線段是否相交

我們分兩步確定兩條線段是否相交:

(1)快速排斥試驗

設以線段 P1P2為對角線的矩形為R,設以線段 Q1Q2 為對角線的矩形為T,如果R和T不相交,顯然兩線段不會相交。

(2)跨立試驗如果兩線段相交,則兩線段必然相互跨立對方。若P1P2跨立Q1Q2,則矢量 ( P1 - Q1 )和( P2 - Q1 )位於矢量( Q2 - Q1 )的兩側,即( P1 - Q1 )× ( Q2 - Q1 ) * ( P2 - Q1 )× ( Q2 - Q1 ) < 0。上式可改寫成( P1 -Q1 )× ( Q2 - Q1 ) * ( Q2 - Q1 )× ( P2 - Q1 ) > 0。當 ( P1 - Q1)× ( Q2 - Q1 ) = 0時,說明 ( P1 - Q1 )和 ( Q2 - Q1 )共線,但是因為已經通過快速排斥試驗,所以 P1一定在線段 Q1Q2上;同理,( Q2 - Q1 )×(P2 - Q1 ) = 0說明 P2一定在線段 Q1Q2上。所以判斷P1P2跨立Q1Q2的依據是:( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 )× ( P2 - Q1 ) >= 0。同理判斷Q1Q2跨立P1P2的依據是:( Q1 -P1 ) × ( P2 - P1 ) * ( P2 - P1 )× ( Q2 - P1 ) >= 0。具體情況如下圖所示:

這里寫圖片描述

在相同的原理下,對此算法的具體的實現細節可能會與此有所不同,除了這種過程外,大家也可以參考《算法導論》上的實現。

判斷線段和直線是否相交

有了上面的基礎,這個算法就很容易了。如果線段P1P2和直線Q1Q2相交,則P1P2跨立Q1Q2,即:( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 )× ( P2 - Q1 ) >= 0。

判斷矩形是否包含點

只要判斷該點的橫坐標和縱坐標是否夾在矩形的左右邊和上下邊之間。

判斷線段、折線、多邊形是否在矩形中

因為矩形是個凸集,所以只要判斷所有端點是否都在矩形中就可以了。

判斷矩形是否在矩形中

只要比較左右邊界和上下邊界就可以了。

判斷圓是否在矩形中

很容易證明,圓在矩形中的充要條件是:圓心在矩形中且圓的半徑小於等於圓心到矩形四邊的距離的最小值。

判斷點是否在多邊形中

判斷點P是否在多邊形中是計算幾何中一個非常基本但是十分重要的算法。以點P為端點,向左方作射線L,由於多邊形是有界的,所以射線L的左端一定在多邊形 外,考慮沿着L從無窮遠處開始自左向右移動,遇到和多邊形的第一個交點的時候,進入到了多邊形的內部,遇到第二個交點的時候,離開了多邊形,……所以很容 易看出當L和多邊形的交點數目C是奇數的時候,P在多邊形內,是偶數的話P在多邊形外。

但是有些特殊情況要加以考慮。如圖下圖(a)(b)(c)(d)所示。在圖(a)中,L和多邊形的頂點相交,這時候交點只能計算一個;在圖(b)中,L和 多邊形頂點的交點不應被計算;在圖(c)和(d)中,L和多邊形的一條邊重合,這條邊應該被忽略不計。如果L和多邊形的一條邊重合,這條邊應該被忽略不計。

這里寫圖片描述

為了統一起見,我們在計算射線L和多邊形的交點的時候,1。對於多邊形的水平邊不作考慮;2。對於多邊形的頂點和L相交的情況,如果該頂點是其所屬的邊上 縱坐標較大的頂點,則計數,否則忽略;3。對於P在多邊形邊上的情形,直接可判斷P屬於多邊行。由此得出算法的偽代碼如下:

count ← 0;以P為端點,作從右向左的射線L; for多邊形的每條邊s do if P在邊s上 then return true; if s不是水平的 then if s的一個端點在L上 if 該端點是s兩端點中縱坐標較大的端點then count← count+1 else if s和L相交 then count← count+1; if count mod 2 = 1 then return true; else return false;

其中做射線L的方法是:設P\’的縱坐標和P相同,橫坐標為正無窮大(很大的一個正數),則P和P\’就確定了射線L。

判斷點是否在多邊形中的這個算法的時間復雜度為O(n)。

另外還有一種算法是用帶符號的三角形面積之和與多邊形面積進行比較,這種算法由於使用浮點數運算所以會帶來一定誤差,不推薦大家使用。

判斷線段是否在多邊形內

線段在多邊形內的一個必要條件是線段的兩個端點都在多邊形內,但由於多邊形可能為凹,所以這不能成為判斷的充分條件。如果線段和多邊形的某條邊內交(兩線段內交是指兩線段相交且交點不在兩線段的端點),因為多邊形的邊的左右兩側分屬多邊形內外不同部分,所以線段一定會有一部分在多邊形外。於是我們得到線段在多邊形內的第二個必要條件:線段和多邊形的所有邊都不內交。

線段和多邊形交於線段的兩端點並不會影響線段是否在多邊形內;但是如果多邊形的某個頂點和線段相交,還必須判斷兩相鄰交點之間的線段是否包含於多邊形內部。

這里寫圖片描述

因此我們可以先求出所有和線段相交的多邊形的頂點,然后按照X-Y坐標排序(X坐標小的排在前面,對於X坐標相同的點,Y坐標小的排在前面,這種排序准則也是為了保證水平和垂直情況的判斷正確),這樣相鄰的兩個點就是在線段上相鄰的兩交點,如果任意相鄰兩點的中點也在多邊形內,則該線段一定在多邊形內。

證明如下:

命題1:如果線段和多邊形的兩相鄰交點P1,P2的中點P\’也在多邊形內,則P1, P2之間的所有點都在多邊形內。

證明:假設P1,P2之間含有不在多邊形內的點,不妨設該點為Q,在P1, P\’之間,因為多邊形是閉合曲線,所以其內外部之間有界,而P1屬於多邊行內部,Q屬於多邊性外部,P\’屬於多邊性內部,P1-Q-P\’完全連續, 所以P1Q和QP\’一定跨越多邊形的邊界,因此在P1,P\’之間至少還有兩個該線段和多邊形的交點,這和P1P2是相鄰兩交點矛盾,故命題成立。證畢。

由命題1直接可得出推論:推論2:設多邊形和線段PQ的交點依次為P1,P2,……Pn,其中Pi和Pi+1是相鄰兩交點,線段PQ在多邊形內的充要條件 是:P,Q在多邊形內且對於i =1, 2,……, n-1,Pi ,Pi+1的中點也在多邊形內。在實際編程中,沒有必要計算所有的交點,首先應判斷線段和多邊形的邊是否內交,倘若線段和多邊形的某條邊內交則線段一定在 多邊形外;如果線段和多邊形的每一條邊都不內交,則線段和多邊形的交點一定是線段的端點或者多邊形的頂點,只要判斷點是否在線段上就可以了。至此我們得出 算法如下:

if 線端PQ的端點不都在多邊形內 then return false;點集pointSet初始化為空; for多邊形的每條邊s do if線段的某個端點在s上 then將該端點加入pointSet; else if s的某個端點在線段PQ上 then 將該端點加入pointSet; else if s和線段PQ相交 // 這時候已經可以肯定是內交了 then return false;將pointSet中的點按照X-Y坐標排序; for pointSet中每兩個相鄰點 pointSet[i] , pointSet[ i+1] do if pointSet[i] , pointSet[ i+1]的中點不在多邊形中then return false; return true;

這個過程中的排序因為交點數目肯定遠小於多邊形的頂點數目n,所以最多是常數級的復雜度,幾乎可以忽略不計。因此算法的時間復雜度也是 O(n)。

判斷折線是否在多邊形內

只要判斷折線的每條線段是否都在多邊形內即可。設折線有m條線段,多邊形有n個頂點,則該算法的時間復雜度為O(m*n)。

判斷多邊形是否在多邊形內

只要判斷多邊形的每條邊是否都在多邊形內即可。判斷一個有m個頂點的多邊形是否在一個有n個頂點的多邊形內復雜度為O(m*n)。

判斷矩形是否在多邊形內

將矩形轉化為多邊形,然后再判斷是否在多邊形內。

判斷圓是否在多邊形內

只要計算圓心到多邊形的每條邊的最短距離,如果該距離大於等於圓半徑則該圓在多邊形內。計算圓心到多邊形每條邊最短距離的算法在后文闡述。

判斷點是否在圓內

計算圓心到該點的距離,如果小於等於半徑則該點在圓內。

判斷線段、折線、矩形、多邊形是否在圓內

因為圓是凸集,所以只要判斷是否每個頂點都在圓內即可。

判斷圓是否在圓內

設兩圓為O1,O2,半徑分別為r1, r2,要判斷O2是否在O1內。先比較r1,r2的大小,如果r1< r2則O2不可能在O1內;否則如果兩圓心的距離大於r1 - r2 ,則O2不在O1內;否則O2在O1內。

計算點到線段的最近點

如 果該線段平行於X軸(Y軸),則過點point作該線段所在直線的垂線,垂足很容易求得,然后計算出垂足,如果垂足在線段上則返回垂足,否則返回離垂足近 的端點;如果該線段不平行於X軸也不平行於Y軸,則斜率存在且不為0。設線段的兩端點為pt1和pt2,斜率為:k = ( pt2.y - pt1. y ) / (pt2.x - pt1.x );該直線方程為:y = k * ( x - pt1.x) + pt1.y。其垂線的斜率為 - 1 / k,垂線方程為:y = (-1/k) * (x - point.x) + point.y。

聯立兩直線方程解得:x = ( k^2 * pt1.x + k * (point.y - pt1.y ) + point.x ) / ( k^2 + 1),y = k * ( x- pt1.x) + pt1.y;然后再判斷垂足是否在線段上,如果在線段上則返回垂足;如果不在則計算兩端點到垂足的距離,選擇距離垂足較近的端點返回。

計算點到折線、矩形、多邊形的最近點

只要分別計算點到每條線段的最近點,記錄最近距離,取其中最近距離最小的點即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM