如何判斷一個指定的位置點座標(GPS上的經緯度)是否落在一個多邊形區域內?
業務場景舉例:快遞選擇收穫區域、車輛電子圍欄、運動軌跡路線、地理位置資訊檢測範圍和地圖等過濾等等。
比方說地圖上有一塊區域(抽象成多邊形),然後裡面每一個位置點(畫素點)都有對應的GPS的經緯度座標值,題目要求的就是判斷任意點(使用者輸入的資訊)與多邊形的位置關係(是否在裡面還是在圖形區域外面)。
具體有一個需求為:每一個店維護多個可配送的地址,配送地址為地圖中的多邊形區域,使用者選擇收貨地址的時候需要判斷該收貨地址在不在多邊形區域內。(給定一個點的座標以及一個多邊形的所有頂點座標。要求能夠判斷這個點是在多邊形內,還是在多邊形外? )
驗證地址:Map Polygon/Polyline Tool https://www.keene.edu/campus/maps/tool/
以上的ABCDE,分別是以下陣列裡面的資料
- Point[] ps = new Point[] { new Point(120.2043 , 30.2795), new Point(120.2030 , 30.2511), new Point(120.1810 , 30.2543), new Point(120.1798 , 30.2781), new Point(120.1926,30.2752) };
那麼問題來了:如何判斷一個指定的經緯度點是否落在一個多邊形區域內?
這個是演算法和判斷的依據也是解答本題的關鍵。(保證準確率和速度的關鍵)
網路上有很多演算法和第三庫來實現這類功能,但本文著重講原理和自己實現寫程式。
話不多說,直接丟擲寫程式通常用的流程模型圖,如下;
經過在網上的一番搜尋,
發現目前比較通用的就是射線法和座標軸法,而我採用的就是X軸射線法。
主要理論來源於西安交大的一篇論文(即參考文獻的第二條)
判斷一個點向左的射線跟一個多邊形的交叉點有幾個,如果結果為奇數的話那麼說明這個點落在多邊形中,反之則不在。
1、理論支援:如果從需要判斷的點出發的一條射線與該多邊形的焦點個數為奇數,則該點在此多邊形內,否則該點在此多邊形外。(射線不能與多邊形頂點相交)程式碼講解: 主要的類有兩個↓↓
2、程式設計思路:
該程式的思路是從A點出發向左做一條水平射線(平行於x軸,向X軸的反方向),判斷與各邊是否有焦點。
dLon1, dLon2, dLat1, dLat2分別表示邊的起點和終點的經度和緯度(x軸和y軸)。
先判斷A點是否在邊的兩端點d1和d2的水平平行線之間,不在則不可能有交點,繼續判斷下一條邊。
在之間則說明可能與A點向左的射線有交點,接下來利用幾何方法得到A點的水平直線與該邊交點的x座標。
然後判斷交點的x座標在A點的左側還是右側,左側則總交點數加一,右側則不在A點左射線上,繼續判斷下一條邊。
一個是座標點的抽象類(點的座標位置),另一個是位置關係判斷工具類(判斷點和多邊形的關係)。
因為座標描點要涉及到座標以及小數點的經緯關係,故要用到浮點型運算,也就是要使用到double雙精度。而地圖涉及到很多個畫素點構成的圖形區域,故要用到list陣列。結果要展示需要用到js程式碼。
def IsPtInPoly(aLon, aLat, pointList):
'''''
:param aLon: double 經度
:param aLat: double 緯度
:param pointList: list [(lon, lat)...] 多邊形點的順序需根據順時針或逆時針,不能亂
'''
iSum = 0
iCount = len(pointList)
if(iCount < 3):
return False
for i in range(iCount):
pLon1 = pointList[i][0]
pLat1 = pointList[i][1]
if(i == iCount - 1):
pLon2 = pointList[0][0]
pLat2 = pointList[0][1]
else:
pLon2 = pointList[i + 1][0]
pLat2 = pointList[i + 1][1]
if ((aLat >= pLat1) and (aLat < pLat2)) or ((aLat>=pLat2) and (aLat < pLat1)):
if (abs(pLat1 - pLat2) > 0):
pLon = pLon1 - ((pLon1 - pLon2) * (pLat1 - aLat)) / (pLat1 - pLat2);
if(pLon < aLon):
iSum += 1
if(iSum % 2 != 0):
return True
else:
return False
1、是座標點的抽象類 - package com.niux.crm.core.common.bmap;
- /**
- * 用於構造百度地圖中的經緯度點
- *
- * @author zhengtian
- * @date 2013-8-5 下午02:54:41
- */
- public class BmapPoint {
- private double lng;// 經度
- private double lat;// 緯度
- public BmapPoint() {
- }
- public BmapPoint(double lng, double lat) {
- this.lng = lng;
- this.lat = lat;
- }
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof BmapPoint) {
- BmapPoint bmapPoint = (BmapPoint) obj;
- return (bmapPoint.getLng() == lng && bmapPoint.getLat() == lat) ? true : false;
- } else {
- return false;
- }
- }
- public double getLng() {
- return lng;
- }
- public void setLng(double lng) {
- this.lng = lng;
- }
- public double getLat() {
- return lat;
- }
- public void setLat(double lat) {
- this.lat = lat;
- }
- }
2、位置關係判斷工具類
點與多邊形的位置關係的判定規則:1、,根據多邊形的座標,虛擬出一個外包矩形,主要是為了提前過濾不相關的點,減少運算量。2、然後判斷是否有重合的點。3、判斷點與斜線的交點。4、判斷點過頂點的情況。5、判斷點與邊重合的情況。6、判斷點在邊上的情況。
其中點過頂點,以及點與邊重合的情況,主要採用了加權邊的思想,論文與程式碼中有註釋。
- package com.niux.crm.core.common.bmap;
- import java.util.Arrays;
- /**
- * 用於點與多邊形位置關係的判斷
- *
- * @author zhengtian
- * @date 2013-8-5 上午11:59:35
- */
- public class GraphUtils {
- /**
- * 判斷點是否在多邊形內(基本思路是用交點法)
- *
- * @param point
- * @param boundaryPoints
- * @return
- */
- public static boolean isPointInPolygon(BmapPoint point, BmapPoint[] boundaryPoints) {
- // 防止第一個點與最後一個點相同
- if (boundaryPoints != null && boundaryPoints.length > 0
- && boundaryPoints[boundaryPoints.length - 1].equals(boundaryPoints[0])) {
- boundaryPoints = Arrays.copyOf(boundaryPoints, boundaryPoints.length - 1);
- }
- int pointCount = boundaryPoints.length;
- // 首先判斷點是否在多邊形的外包矩形內,如果在,則進一步判斷,否則返回false
- if (!isPointInRectangle(point, boundaryPoints)) {
- return false;
- }
- // 如果點與多邊形的其中一個頂點重合,那麼直接返回true
- for (int i = 0; i < pointCount; i++) {
- if (point.equals(boundaryPoints[i])) {
- return true;
- }
- }
- /**
- * 基本思想是利用X軸射線法,計算射線與多邊形各邊的交點,如果是偶數,則點在多邊形外,否則在多邊形內。還會考慮一些特殊情況,如點在多邊形頂點上
- * , 點在多邊形邊上等特殊情況。
- */
- // X軸射線與多邊形的交點數
- int intersectPointCount = 0;
- // X軸射線與多邊形的交點權值
- float intersectPointWeights = 0;
- // 浮點型別計算時候與0比較時候的容差
- double precision = 2e-10;
- // 邊P1P2的兩個端點
- BmapPoint point1 = boundaryPoints[0], point2;
- // 迴圈判斷所有的邊
- for (int i = 1; i <= pointCount; i++) {
- point2 = boundaryPoints[i % pointCount];
- /**
- * 如果點的y座標在邊P1P2的y座標開區間範圍之外,那麼不相交。
- */
- if (point.getLat() < Math.min(point1.getLat(), point2.getLat())
- || point.getLat() > Math.max(point1.getLat(), point2.getLat())) {
- point1 = point2;
- continue;
- }
- /**
- * 此處判斷射線與邊相交
- */
- if (point.getLat() > Math.min(point1.getLat(), point2.getLat())
- && point.getLat() < Math.max(point1.getLat(), point2.getLat())) {// 如果點的y座標在邊P1P2的y座標開區間內
- if (point1.getLng() == point2.getLng()) {// 若邊P1P2是垂直的
- if (point.getLng() == point1.getLng()) {
- // 若點在垂直的邊P1P2上,則點在多邊形內
- return true;
- } else if (point.getLng() < point1.getLng()) {
- // 若點在在垂直的邊P1P2左邊,則點與該邊必然有交點
- ++intersectPointCount;
- }
- } else {// 若邊P1P2是斜線
- if (point.getLng() <= Math.min(point1.getLng(), point2.getLng())) {// 點point的x座標在點P1和P2的左側
- ++intersectPointCount;
- } else if (point.getLng() > Math.min(point1.getLng(), point2.getLng())
- && point.getLng() < Math.max(point1.getLng(), point2.getLng())) {// 點point的x座標在點P1和P2的x座標中間
- double slopeDiff = 0.0d;
- if (point1.getLat() > point2.getLat()) {
- slopeDiff = (point.getLat() - point2.getLat()) / (point.getLng() - point2.getLng())
- - (point1.getLat() - point2.getLat()) / (point1.getLng() - point2.getLng());
- } else {
- slopeDiff = (point.getLat() - point1.getLat()) / (point.getLng() - point1.getLng())
- - (point2.getLat() - point1.getLat()) / (point2.getLng() - point1.getLng());
- }
- if (slopeDiff > 0) {
- if (slopeDiff < precision) {// 由於double精度在計算時會有損失,故匹配一定的容差。經試驗,座標經度可以達到0.0001
- // 點在斜線P1P2上
- return true;
- } else {
- // 點與斜線P1P2有交點
- intersectPointCount++;
- }
- }
- }
- }
- } else {
- // 邊P1P2水平
- if (point1.getLat() == point2.getLat()) {
- if (point.getLng() <= Math.max(point1.getLng(), point2.getLng())
- && point.getLng() >= Math.min(point1.getLng(), point2.getLng())) {
- // 若點在水平的邊P1P2上,則點在多邊形內
- return true;
- }
- }
- /**
- * 判斷點通過多邊形頂點
- */
- if (((point.getLat() == point1.getLat() && point.getLng() < point1.getLng()))
- || (point.getLat() == point2.getLat() && point.getLng() < point2.getLng())) {
- if (point2.getLat() < point1.getLat()) {
- intersectPointWeights += -0.5;
- } else if (point2.getLat() > point1.getLat()) {
- intersectPointWeights += 0.5;
- }
- }
- }
- point1 = point2;
- }
- if ((intersectPointCount + Math.abs(intersectPointWeights)) % 2 == 0) {// 偶數在多邊形外
- return false;
- } else { // 奇數在多邊形內
- return true;
- }
- }
- /**
- * 判斷點是否在矩形內在矩形邊界上,也算在矩形內(根據這些點,構造一個外包矩形)
- *
- * @param point
- * 點物件
- * @param boundaryPoints
- * 矩形邊界點
- * @return
- */
- public static boolean isPointInRectangle(BmapPoint point, BmapPoint[] boundaryPoints) {
- BmapPoint southWestPoint = getSouthWestPoint(boundaryPoints); // 西南角點
- BmapPoint northEastPoint = getNorthEastPoint(boundaryPoints); // 東北角點
- return (point.getLng() >= southWestPoint.getLng() && point.getLng() <= northEastPoint.getLng()
- && point.getLat() >= southWestPoint.getLat() && point.getLat() <= northEastPoint.getLat());
- }
- /**
- * 根據這組座標,畫一個矩形,然後得到這個矩形西南角的頂點座標
- *
- * @param vertexs
- * @return
- */
- private static BmapPoint getSouthWestPoint(BmapPoint[] vertexs) {
- double minLng = vertexs[0].getLng(), minLat = vertexs[0].getLat();
- for (BmapPoint bmapPoint : vertexs) {
- double lng = bmapPoint.getLng();
- double lat = bmapPoint.getLat();
- if (lng < minLng) {
- minLng = lng;
- }
- if (lat < minLat) {
- minLat = lat;
- }
- }
- return new BmapPoint(minLng, minLat);
- }
- /**
- * 根據這組座標,畫一個矩形,然後得到這個矩形東北角的頂點座標
- *
- * @param vertexs
- * @return
- */
- private static BmapPoint getNorthEastPoint(BmapPoint[] vertexs) {
- double maxLng = 0.0d, maxLat = 0.0d;
- for (BmapPoint bmapPoint : vertexs) {
- double lng = bmapPoint.getLng();
- double lat = bmapPoint.getLat();
- if (lng > maxLng) {
- maxLng = lng;
- }
- if (lat > maxLat) {
- maxLat = lat;
- }
- }
- return new BmapPoint(maxLng, maxLat);
- }
- }
【關於3D地圖圖形請參考】
IOS高德3D地圖畫多邊形,以及判斷某一經緯度是否在該多邊形內 - 簡書 https://www.jianshu.com/p/fb1177cac1ec
翻看了一下高德提供的技術文件,沒找到3D地圖下該怎麼完成此功能。無奈自己翻找高德的SDK,發現了這兩個類:MAPolygon MAOverlayRenderer
MAPolygon用於定義一個由多個點組成的閉合多邊形,點與點之間按順序尾部相連,第一個點與最後一個點相連,通常MAPolygon是MAPolygonView的model
MAOverlayRenderer是地圖覆蓋物Renderer的基類,提供繪製overlay的介面但並無實際的實現(render相關方法只能在重寫後的glRender方法中使用)
【參考文獻】
1、兩條直線的關係
http://www.cnblogs.com/devymex/archive/2010/08/19/1803885.html
2、點與多邊形的關係
http://wenku.baidu.com/view/5e3913a2b0717fd5360cdccf.html?qq-pf-to=pcqq.c2c
3、https://en.wikipedia.org/wiki/Haversine_formula
4、百度map js程式碼 https://www.cnblogs.com/relax/p/3507014.html
5、關於經緯度得到的多邊形面積。 https://www.cnblogs.com/JeffController/p/5618742.html
【附錄座標軸函式公式圖】
直接上程式碼(兩個點)半正矢公式 計算(Haversine formula):因為難度大暫時不考慮此類演算法。
關於指定的經緯度是否落在多邊形內 - https://blog.csdn.net/qq_22929803/article/details/46818009
相關文章
- 如何判斷一個點在地圖上?如何判斷一個點在多邊形內?地圖
- JS 射線法 判斷點是否在多邊形內部JS斷點
- 如何判斷某經緯度是否在地圖不規則區域內(Objective-C 實現)地圖Object
- AutoCAD C# 判斷多邊形與點的位置關係C#
- 如何用python判斷列表中是否包含多個字串中的一個或多個?Python字串
- 如何判斷一個玩法是否合格?
- 如何判斷一個元素是否在可視區域中?
- 判斷點是否在多邊形內的Python實現及小應用(射線法)斷點Python
- springboot + mongodb 通過經緯度座標匹配平面區域的方法YWKSSpring BootMongoDB
- 如何判斷一個 interface{} 的值是否為 nil ?
- 如何判斷一個物件是否為空?物件
- 判斷一個物件是否為空物件,判斷一個物件中是否有空值物件
- js呼叫百度地圖介面繪製任意多邊形並獲取每個點的經緯度等JS地圖
- 獲取資料庫中到指定經緯度距離的座標資料庫
- 如何判斷一個元素文字是否換行?
- python如何判斷一個物件是否是列表Python物件
- 如何快速將地址解析為經緯度座標?
- python如何判斷一個數是否是整數Python
- WPF如何得到一個在使用者控制元件內部的元素的座標位置控制元件
- Linux判斷上一個語句是否執行成功Linux
- 內心裡的一把火(判斷平面內的點是否在三角形內)
- JS如何判斷一個陣列是否為空、是否含有某個值JS陣列
- 寫一個方法判斷陣列內元素是否全部相同陣列
- 簡單計算給定兩個給定經緯度座標的距離
- Python獲取IP的地理位置:經緯度,國家,區域,城市Python
- plotly 座標軸範圍截斷rangebreaks使用的一個注意點
- 判斷一個陣列是否排好序陣列
- 判斷點是否在三角形內斷點
- 如何判斷一個js物件是否存在迴圈引用JS物件
- javascript中如何判斷一個字串是否為JSON格式JavaScript字串JSON
- java 根據兩個位置的經緯度,來計算兩地的距離 經緯度處理Java
- (UE4 4.20)UE4 如何判斷一個點是否在導航網格(Navigation)內Navigation
- 根據經緯度座標查詢最近的門店
- 如何在億級資料中判斷一個元素是否存在?
- 如何判斷一個元素在億級資料中是否存在?
- PHP 判斷一個字元是否在字串中PHP字元字串
- PHP判斷一個字串是否包含亂碼PHP字串
- 判斷一個有向圖是否有環