判斷點(diǎn)是否在多邊形內(nèi)的Python實(shí)現(xiàn)及小應(yīng)用(射線法)

判斷一個(gè)點(diǎn)是否在多邊形內(nèi)是處理空間數(shù)據(jù)時(shí)經(jīng)常面對的需求,例如GIS中的點(diǎn)選功能初澎、根據(jù)多邊形邊界篩選出位于多邊形內(nèi)的點(diǎn)举农、求交集、篩選不在多邊形內(nèi)的點(diǎn)等等腌且。
判斷一個(gè)點(diǎn)是否在多邊形內(nèi)有幾種不同的思路梗肝,相應(yīng)的方法(感覺還談不上算法)有:

  • 射線法:從判斷點(diǎn)向某個(gè)統(tǒng)一方向作射線,依交點(diǎn)個(gè)數(shù)的奇偶判斷铺董;
  • 轉(zhuǎn)角法:按照多邊形頂點(diǎn)逆時(shí)針順序巫击,根據(jù)頂點(diǎn)和判斷點(diǎn)連線的方向正負(fù)(設(shè)定角度逆時(shí)針為正)求和判斷;
  • 夾角和法:求判斷點(diǎn)與所有邊的夾角和,等于360度則在多邊形內(nèi)部喘鸟。
  • 面積和法:求判斷點(diǎn)與多邊形邊組成的三角形面積和匆绣,等于多邊形面積則點(diǎn)在多邊形內(nèi)部。

面積和法涉及多個(gè)面積的計(jì)算什黑,比較復(fù)雜崎淳,夾角和法以及轉(zhuǎn)角法用到角度計(jì)算,會(huì)涉及反三角函數(shù)愕把,計(jì)算開銷比較大拣凹,而射線法主要涉及循環(huán)多邊形的每條邊進(jìn)行求交運(yùn)算,但大部分邊可以通過簡單坐標(biāo)比對直接排除恨豁,因此這是比較好的方法嚣镜。

射線法的實(shí)現(xiàn)

射線法就是以判斷點(diǎn)開始,向右(或向左)的水平方向作一射線橘蜜,計(jì)算該射線與多邊形每條邊的交點(diǎn)個(gè)數(shù)菊匿,如果交點(diǎn)個(gè)數(shù)為奇數(shù),則點(diǎn)位于多邊形內(nèi)计福,偶數(shù)則在多邊形外跌捆。該算法對于復(fù)合多邊形也能正確判斷。

復(fù)合多邊形的情況

射線法的關(guān)鍵是正確計(jì)算射線與每條邊是否相交象颖。并且規(guī)定線段與射線重疊或者射線經(jīng)過線段下端點(diǎn)屬于不相交佩厚。首先排除掉不相交的情況,下圖的情況都是需要排除掉的:
求交之前可排除的情況

排除掉這些情況的函數(shù)如下:

def isRayIntersectsSegment(poi,s_poi,e_poi): #[x,y] [lng,lat]
    #輸入:判斷點(diǎn)说订,邊起點(diǎn)抄瓦,邊終點(diǎn),都是[lng,lat]格式數(shù)組
    if s_poi[1]==e_poi[1]: #排除與射線平行陶冷、重合钙姊,線段首尾端點(diǎn)重合的情況
        return False
    if s_poi[1]>poi[1] and e_poi[1]>poi[1]: #線段在射線上邊
        return False
    if s_poi[1]<poi[1] and e_poi[1]<poi[1]: #線段在射線下邊
        return False
    if s_poi[1]==poi[1] and e_poi[1]>poi[1]: #交點(diǎn)為下端點(diǎn),對應(yīng)spoint
        return False
    if e_poi[1]==poi[1] and s_poi[1]>poi[1]: #交點(diǎn)為下端點(diǎn)埃叭,對應(yīng)epoint
        return False
    if s_poi[0]<poi[0] and e_poi[1]<poi[1]: #線段在射線左邊
        return False

    xseg=e_poi[0]-(e_poi[0]-s_poi[0])*(e_poi[1]-poi[1])/(e_poi[1]-s_poi[1]) #求交
    if xseg<poi[0]: #交點(diǎn)在射線起點(diǎn)的左側(cè)
        return False
    return True  #排除上述情況之后

排除掉上述情況真正需要求交點(diǎn)來判斷的情況只有兩種:


需要求交的兩種情況

函數(shù)isRayIntersectsSegment()里求交的部分就是利用兩個(gè)三角形的比例關(guān)系求出交點(diǎn)在起點(diǎn)的左邊還是右邊摸恍;用圖去理解如下:


求交的具體過程

最后判斷的代碼如下:

def isPoiWithinPoly(poi,poly):
    #輸入:點(diǎn),多邊形三維數(shù)組
    #poly=[[[x1,y1],[x2,y2],……,[xn,yn],[x1,y1]],[[w1,t1],……[wk,tk]]] 三維數(shù)組

    #可以先判斷點(diǎn)是否在外包矩形內(nèi) 
    #if not isPoiWithinBox(poi,mbr=[[0,0],[180,90]]): return False
    #但算最小外包矩形本身需要循環(huán)邊赤屋,會(huì)造成開銷,本處略去
    sinsc=0 #交點(diǎn)個(gè)數(shù)
    for epoly in poly: #循環(huán)每條邊的曲線->each polygon 是二維數(shù)組[[x1,y1],…[xn,yn]]
        for i in range(len(epoly)-1): #[0,len-1]
            s_poi=epoly[i]
            e_poi=epoly[i+1]
            if isRayIntersectsSegment(poi,s_poi,e_poi):
                sinsc+=1 #有交點(diǎn)就加1

    return True if sinsc%2==1 else  False

我們?nèi)∫粋€(gè)比較復(fù)雜的多邊形進(jìn)行測試壁袄,多邊形和一些點(diǎn)如圖:


測試用的有孔洞多邊形

用isPoiWithinPoly()的測試結(jié)果如下:


測試結(jié)果

點(diǎn)在多邊形內(nèi)的應(yīng)用

上面第一段已經(jīng)描述了一些應(yīng)用場景类早,下面給出一個(gè)應(yīng)用的例子:有一堆點(diǎn)數(shù)據(jù)存在csv文件里,如何檢索位于某個(gè)城市的點(diǎn)出來嗜逻,檢索出來之后的分析(例如加標(biāo)簽涩僻、改屬性、做統(tǒng)計(jì)還是其他)這里不討論,檢索的結(jié)果統(tǒng)一寫到新文件里逆日。點(diǎn)輸入的格式如下:

id,name,wgslng,wgslat,score,adds
1,沃美,116.3309,40.0706,4.3,昌平回龍觀同成街華聯(lián)購物中心4樓
2,星美國際,116.446,39.916,5,金匯路8號(hào)世界城E座
3,……

城市邊界為geojson格式嵌巷,就是加了一些限定條件的json格式數(shù)據(jù),如果需要詳細(xì)了解geojson格式室抽,可以參考本人之前的文章:GEOJSON標(biāo)準(zhǔn)格式學(xué)習(xí)搪哪。形如:

{
  "type": "FeatureCollection",
  "features": [{
      "type": "Feature",
      "properties": {},
      "geometry": {
        "type": "Polygon",
        "coordinates":
         [
            [
              [108.71658325195312,34.231106222010531],
              [108.96240234375,34.168635904722734],
              [109.00222778320313,34.354774165387568],
              [108.80172729492186,34.35023911062779],
              [108.71658325195312,34.231106222010531]
            ]
          ]
        }
      }
  ]
}

下面的代碼只考慮了Polygon的情況,對于MultiPolygon也是比較容易改的坪圾,要改為處理kml保存的邊界數(shù)據(jù)也不難改晓折。文中代碼同步于本人GitHub

import json
import csv
def pointInPolygon():
    gfile = './beijing_poly_wgs84.geojson' #utf-8編碼
    cin_path = './poi_cinema_wgs84.csv'
    out_path = './beijing_poi_cinema_wgs84.csv' #輸出文件

    pindex = [2, 3]  # wgslng,wgslat 在的位置

    with open(out_path, 'w', newline='') as cout_file:
        fin = open(cin_path, 'r', encoding='gbk') #出現(xiàn)編碼錯(cuò)誤就改編碼 utf-8
        gfn = open(gfile, 'r', encoding='utf-8')
        gjson = json.load(gfn)
        polygon = gjson["features"][0]["geometry"]['coordinates'] #提取多邊形,如果是4維數(shù)組需要相應(yīng)的處理
        filewriter = csv.writer(cout_file, delimiter=',')
        w = 0
        for line in csv.reader(fin, delimiter=','):
            if w == 0: #寫入表頭 id,name,… 如果沒有就去掉if語句
                filewriter.writerow(line)
                w = 1
                continue
            point = [float(line[pindex[0]]), float(line[pindex][1])]
            if isPoiWithinPoly(point, polygon): #在多邊形內(nèi)兽泄,寫入新表
                filewriter.writerow(line)
            else:
                continue
        fin.close()
        gfn.close()
    print('done')

歡迎關(guān)注本人公眾號(hào)漓概,有更多有趣內(nèi)容和資料:


lyns-sailing.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市病梢,隨后出現(xiàn)的幾起案子胃珍,更是在濱河造成了極大的恐慌,老刑警劉巖蜓陌,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件觅彰,死亡現(xiàn)場離奇詭異,居然都是意外死亡护奈,警方通過查閱死者的電腦和手機(jī)缔莲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霉旗,“玉大人痴奏,你說我怎么就攤上這事⊙崦耄” “怎么了读拆?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鸵闪。 經(jīng)常有香客問我檐晕,道長,這世上最難降的妖魔是什么蚌讼? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任辟灰,我火速辦了婚禮,結(jié)果婚禮上篡石,老公的妹妹穿的比我還像新娘芥喇。我一直安慰自己,他們只是感情好凰萨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布继控。 她就那樣靜靜地躺著械馆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪武通。 梳的紋絲不亂的頭發(fā)上霹崎,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音冶忱,去河邊找鬼尾菇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛朗和,可吹牛的內(nèi)容都是我干的错沽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼眶拉,長吁一口氣:“原來是場噩夢啊……” “哼千埃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忆植,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤放可,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后朝刊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耀里,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年拾氓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冯挎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咙鞍,死狀恐怖房官,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情续滋,我是刑警寧澤翰守,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站疲酌,受9級(jí)特大地震影響蜡峰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜朗恳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一湿颅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粥诫,春花似錦肖爵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至揉稚,卻和暖如春秒啦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搀玖。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國打工余境, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人灌诅。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓芳来,卻偏偏與公主長得像,于是被迫代替她去往敵國和親猜拾。 傳聞我的和親對象是個(gè)殘疾皇子即舌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 我夢見過許多壯闊的景色。一望無際深藍(lán)色的海挎袜,雄偉的高山顽聂,金色的日出,無垠的星空盯仪。然而使我深受震撼的僅僅是一束...
    青城青城閱讀 399評(píng)論 0 1