在 LBS 開發中,可能常常要碰到這樣的問題,如何判斷一個指定的經緯度點是否落在一個多邊形區域內?好比在地圖上畫了一個多邊形區域,而後給出一個經緯度點,怎樣判斷這個點是否在這個多邊形範圍以內?git
最近接到個考勤打卡場景需求:後端
第一點好解決:在用戶差旅狀態下禁止打卡交互就能夠了,而第二點可能就有點複雜了: 如何來判斷用戶進入考勤範圍內呢?拓展下相似的需求還有外賣點餐判斷是否在商家配送範圍?判斷共享單車是否停靠在停車點?bash
這些需求拆分到最後都是 在判斷一個座標點是否在一個無規則的多邊形內的問題。服務器
理論支持app
需求: 判斷某點座標是否在多邊形內
方法: 求解經過該點的水平射線與多邊形各邊的交點個數
結果: 水平射線與多邊形交點爲奇數,則在多邊形內部;交點爲偶數,則在多邊形外部ide
接下來就是上代碼。咱們首先要作的就是與後端商定 app 與服務器數據傳輸的規則:服務器傳回包含五邊形點座標字符串,這五個點按順序聯結框定出一個不規則的五邊形區域, 這個五邊形區域就是咱們的打卡考勤有效範圍。測試
服務器傳回的多邊形各點座標:ui
@"POLYGON((116.2310052844 39.9980477478,116.5143798001 40.0028565483,116.2460357549 39.8348654814,116.3976525318 39.7646827931,116.5157236632 39.8221811347))";
複製代碼
咱們先把這個字符串處理成五個包含經度和緯度的 Coordinate 對象。Coordinate 對象的結構爲:atom
@interface Coordinate:NSObject
@property (nonatomic, assign) double lon;
@property (nonatomic, assign) double lat;
@end
@implementation Coordinate
@end
複製代碼
須要注意的是,咱們在處理字符串的時候,將火星座標轉化爲百度座標。(服務傳回的座標爲火星座標,項目中定位模塊定到位後直接將經緯度轉化爲了百度座標,這裏是爲了保持與服務器座標系的一致進行轉化,各位小夥伴須要根據本身項目實際狀況進行座標轉換)加密
各地圖API座標系統科普與轉換
- WGS84座標系:即地球座標系,國際上通用的座標系。設備通常包含GPS芯片或者北斗芯片獲取的經緯度爲WGS84地理座標系,
- 谷歌地圖採用的是WGS84地理座標系(中國範圍除外);
- GCJ02座標系:即火星座標系,是由中國國家測繪局制訂的地理信息系統的座標系統。由WGS84座標系經加密後的座標系。
- 谷歌中國地圖和搜搜中國地圖採用的是GCJ02地理座標系; BD09座標系:即百度座標系,GCJ02座標系經加密後的座標系;
- 搜狗座標系、圖吧座標系等,估計也是在GCJ02基礎上加密而成的。
處理服務器返回的數據,並將火星座標轉化爲百度座標:
//處理服務器返回的數據
- (void)dealWithDotCoordinateWithString:(NSString *)locString{
//locString = @"POLYGON((116.2310052844 39.9980477478,116.5143798001 40.0028565483,116.2460357549 39.8348654814,116.3976525318 39.7646827931,116.5157236632 39.8221811347))";
locString = [locString stringByReplacingOccurrencesOfString:@"POLYGON((" withString:@""];
locString = [locString stringByReplacingOccurrencesOfString:@"))" withString:@""];
NSArray *locArray = [locString componentsSeparatedByString:@","];
NSMutableArray *locResult = [NSMutableArray new];
NSInteger index = 0;
for (NSString * str in locArray) {
NSArray *strArray = [str componentsSeparatedByString:@" "];
if (strArray.count > 1) {
Coordinate *lonAndLat = [Coordinate new] ;
NSString *lon = [strArray objectAtIndex:0];
lonAndLat.lon = [lon doubleValue];
NSString *lat = [strArray objectAtIndex:1];
lonAndLat.lat = [lat doubleValue];
//將服務器的火星座標轉換爲百度座標
Coordinate *baiduLoc = [self lonAndLatLocationBaiduFromMars:lonAndLat];
[locResult addObject:baiduLoc];
index ++;
}
}
}
複製代碼
火星座標轉換爲百度座標的方法:
//將火星座標轉換爲百度座標的方法
- (Coordinate *)lonAndLatLocationBaiduToMars:(Coordinate *)coordinate{
double x_pi = M_PI * 3000.0 / 180.0;
double x = coordinate.lon, y = coordinate.lat;
double z = sqrt(x * x + y * y) + 0.00002 * sin(y * x_pi);
double theta = atan2(y, x) + 0.000003 * cos(x * x_pi);
coordinate.lon = z * cos(theta) + 0.0065;
coordinate.lat = z * sin(theta) + 0.006;
return coordinate;
}
複製代碼
百度座標轉化爲火星座標方法:
//百度座標轉化爲火星座標
- (Coordinate *) lonAndLatLocationMarsToMars:(Coordinate *)coordinate{
double x_pi = M_PI * 3000.0 / 180.0;
double x = coordinate.lon - 0.0065, y = coordinate.lat - 0.006;
double z = sqrt(x * x + y * y) - 0.00002 * sin(y * x_pi);
double theta = atan2(y, x) - 0.000003 * cos(x * x_pi);
coordinate.lon = z * cos(theta);
coordinate.lat = z * sin(theta);
return coordinate;
}
複製代碼
接下來就是重點,怎麼判斷座標在多邊形內部方法:
//判斷點是否在多邊形內部
- (BOOL)judgeLocationX:(double)locationX locationY:(double)locationY insideSignArea:(NSArray *)areaArray{
if(areaArray.count==0){
NSLog(@"考勤區域爲空 直接返回 true");
return true;
}
NSMutableArray *xArray = [NSMutableArray new];
NSMutableArray *yArray = [NSMutableArray new];
for (Coordinate *coordinate in areaArray) {
[xArray addObject: [NSNumber numberWithDouble:coordinate.lon]];
[yArray addObject:[NSNumber numberWithDouble:coordinate.lat]];
}
BOOL flag = NO;
//取橫座標和縱座標的最大值和最小值,根據這四個值minX,maxX,minY,maxY,算出一個四邊形,判斷目標點是否在這個四邊形內,不知足,直接返回false,證實該目標點不在此多邊形內部。
double minX = [[xArray valueForKeyPath:@"@min.doubleValue"] doubleValue];
double maxX = [[xArray valueForKeyPath:@"@max.doubleValue"] doubleValue];
double minY = [[yArray valueForKeyPath:@"@min.doubleValue"] doubleValue];
double maxY = [[yArray valueForKeyPath:@"@max.doubleValue"] doubleValue];
if (longitude < minX || longitude > maxX || latitude < minY || latitude > maxY ) {
return false;
}
//座標點畫條水平線射線計算與多邊形的交點個數,奇數在多邊形內, 偶數在多邊形外。
int count = (int) areaArray.count ;
for (int i = 0, j = count-1; i < count; j = i++) {
if ( ( ([yArray[i] doubleValue] > locationY) != ([yArray[j] doubleValue] > locationY)) &&
(locationX < ([xArray[j] doubleValue] - [xArray[i] doubleValue]) * (locationY-[yArray[i] doubleValue]) / ([yArray[j] doubleValue]-[yArray[i] doubleValue]) + [xArray[i] doubleValue]) )
flag = !flag;
}
NSLog(@"座標點是否在不規則區域內: %d",success);
return flag;
}
複製代碼
方法內部對座標點進行判斷,判斷該點緯度是否在多邊形相鄰兩點緯度之間,若是在兩緯度之間則接着判斷該點單方向的水平射線與這兩相鄰點連結邊是否有交點。若是有交點則開始計數。接着遍歷判斷與多邊形其它邊是否有交點,這樣就能夠獲得該水平射線與多邊形邊交點的總個數,交點總數爲奇數則該點在多邊形內部;交點總數爲偶數則該點在多邊形外部。上面方法中並無統計交點個數而是直接使用 flag
記錄總數的奇偶性。
最後進行一些簡單的數據測試:
BOOL flag1 = [self judgeLocationX:116.3839694879 locationY:39.9274612554 insideSignArea:locResult]; //應返回 true
BOOL flag2 = [self judgeLocationX:116.4010873480 locationY:39.8485685476 insideSignArea:locResult]; //應返回 true
BOOL flag3 = [self judgeLocationX:116.5473037259 locationY:40.1688347176 insideSignArea:locResult]; //應返回 false
BOOL flag4 = [self judgeLocationX:116.1909733537 locationY:40.0254447029 insideSignArea:locResult]; //應返回 false
複製代碼
回想下咱們剛纔都作了些什麼: