問題: 接口給出一條路上下兩車道路線的經緯度, 讓我們用不同顏色繪制, 表示路況, 如果直接繪制, 縮小地圖兩條車道會重疊在一起, 只有放大地圖才能看到兩條車道
網上搜了很久都沒有相關資料, 所以我想了個應該不是最好的方法, 就是將路線向旁邊偏移一定的距離, 這個距離隨著地圖縮放級別zoomLevel變化而變化, 因為路線本來就是由很多個點連接而成的, 我們可以把這個操作分解成兩個點平移一定的距離, 如下是偏移前和偏移后的效果
1. 計算兩個點平移后的經緯度
由于接口給出的經緯度路線點都是有順序的, 所以相鄰的兩點, 可以分起點和終點, 搬出我中學數學, 看下圖
先忽略方向, A點B點是平移前的起點終點, A'B'是平移后的點, 現在我們知道AB的經緯度, 求平移了h的距離后A'B'的經緯度, 我們可以將經度當成X, 緯度當成Y, A'的坐標其實就是(A點的X(經度) + BD, A點的Y(緯度) + B'D), B'的坐標其實就是(B點的X(經度) + BD, B點的Y(緯度) + B'D), 我們知道A, B點的坐標,進而其實我們算出BD, B'D的長度就行了, ∠1 = arctan(AC/BC), AC是點AB Y值差的絕對值, 同理BC是X值差的絕對值, 知道∠1, 由于∠1 + ∠2 + ∠3 = 180°, ∠2 = 90°, 所以∠1 + ∠3 = 90°, 因為∠4 + ∠3 = 90°, 所以∠1 = ∠4, 剛剛我們求出來了∠1, 又知道偏移的距離h, 即BB'的長等于h, 所以BD = sin∠1 x h, B'D = cos∠1 x h
由于車道是有方向的, 所以不一定都是原坐標加上求出來的BD, B'D才得到平移后的坐標, 有可能向不同方向偏移, 像圖中的路線方向如果是A到B, 偏移只能向左下偏移, A到B是下車道,路線都是右上, 左下的, 圖中的偏移只能是從B到A, 也就是B才是起點
{
...
// 其中一條路的點數組, 元素如"113.024852,23.296519", 另外一條路復制下面的代碼就行了,調用的calculateCoordinateWithStartPoint方法一樣的,只是upRoadPoints不一樣
NSArray *upRoadPoints = @[...];
if (upRoadPoints.count < 2) {
return;
}
CLLocationCoordinate2D upCommonPolylineCoords[upRoadPoints.count];
for (NSInteger i = 0; i < upRoadPoints.count - 1; i ++) { // 少遍歷一次
NSString *upString = upRoadPoints[i]; // 起點
NSString *upNextString = upRoadPoints[i + 1]; // 終點
CGPoint changeCoordinate;
if (self.mapView.zoomLevel >= 16) { // 大于16不用偏移, 因為這個時候已經能看到兩條路了
changeCoordinate = CGPointMake(0, 0);
} else { // 縮放度zoomLevel為11的時候, 偏移是0.001, 到16為0, 相差了5級, 這個可以自己看地圖來調整
changeCoordinate = [self calculateCoordinateWithStartPoint:[upString componentsSeparatedByString:@","] endPoint:[upNextString componentsSeparatedByString:@","] distance:(0.001 - (self.mapView.zoomLevel - 11) * 0.001 / 5)];
}
if (i == upRoadPoints.count - 2) { // 最后一個點和倒數第二個點偏移一樣
upCommonPolylineCoords[i].longitude = [[upString componentsSeparatedByString:@","].firstObject floatValue] + changeCoordinate.x;
upCommonPolylineCoords[i].latitude = [[upString componentsSeparatedByString:@","].lastObject floatValue] + changeCoordinate.y;
upCommonPolylineCoords[i].longitude = [[upNextString componentsSeparatedByString:@","].firstObject floatValue] + changeCoordinate.x;
upCommonPolylineCoords[i].latitude = [[upNextString componentsSeparatedByString:@","].lastObject floatValue] + changeCoordinate.y;
} else {
upCommonPolylineCoords[i].longitude = [[upString componentsSeparatedByString:@","].firstObject floatValue] + changeCoordinate.x;
upCommonPolylineCoords[i].latitude = [[upString componentsSeparatedByString:@","].lastObject floatValue] + changeCoordinate.y;
}
}
MAPolyline *line = [MAPolyline polylineWithCoordinates:upCommonPolylineCoords count:upRoadPoints.count]; // 如果count沒傳對, 有兩個會延伸無限長的
[self.mapView addOverlay:line];
}
- (CGPoint)calculateCoordinateWithStartPoint:(NSArray *)start endPoint:(NSArray *)end distance:(CGFloat)distance
{
if (distance == 0) {
return CGPointMake(0, 0);
}
float radian;
if (fabs([start.lastObject floatValue] - [end.lastObject floatValue]) == 0) {
radian = 0;
} else {
radian = atanf(fabs([start.lastObject floatValue] - [end.lastObject floatValue]) / fabs([start.firstObject floatValue] - [end.firstObject floatValue]));
}
double x = fabs(sinf(radian) * distance);
double y = fabs(cosf(radian) * distance);
if (([end.firstObject floatValue] - [start.firstObject floatValue] <= 0) && ([end.lastObject floatValue] - [start.lastObject floatValue] < 0)) {
return CGPointMake(-x, y);
} else if (([end.firstObject floatValue] - [start.firstObject floatValue] > 0) && ([end.lastObject floatValue] - [start.lastObject floatValue] <= 0)) {
return CGPointMake(-x, -y);
} else if (([end.firstObject floatValue] - [start.firstObject floatValue] > 0) && ([end.lastObject floatValue] - [start.lastObject floatValue] > 0)) {
return CGPointMake(x, -y);
}
return CGPointMake(x, y);
}
2. 縮放級別改變, 折線粗細改變, 偏移量也要改變
直接用KVO監聽高德地圖的zoomLevel屬性是沒用的, 找了他里面的代理方法, 發現縮放的時候會有個將要開始縮放和結束縮放的代理方法, 只是開始的時候調用和結束的時候調用一次, 如果用手勢開始縮放后沒停下手是不會觸發的, 如果你們需求要求比較簡單, 可以在開始縮放的時候把折線刪除了, 結束縮放的時候再調用繪制的方法, 這樣優點是沒那么消耗性能, 缺點是用手勢縮放的時候看不到折線逐漸偏移的過程
然后高德地圖還有個方法是當地圖區域改變過程中會調用此接口, 這個方法就是地圖在移動或縮放等的時候會一直調用, 所以我是在這個方法里面將折線刪除了重新繪制的, 找不到什么好的方法, 如果有更好的方法還望大佬不吝賜教
/**
* @brief 地圖將要發生縮放時調用此接口
* @param mapView 地圖view
* @param wasUserAction 標識是否是用戶動作
*/
- (void)mapView:(MAMapView *)mapView mapWillZoomByUser:(BOOL)wasUserAction
{
self.isZoom = YES;
}
/**
* @brief 地圖縮放結束后調用此接口
* @param mapView 地圖view
* @param wasUserAction 標識是否是用戶動作
*/
- (void)mapView:(MAMapView *)mapView mapDidZoomByUser:(BOOL)wasUserAction
{
self.isZoom = NO;
}
/**
* @brief 地圖區域改變過程中會調用此接口 since 4.6.0
* @param mapView 地圖View
*/
- (void)mapViewRegionChanged:(MAMapView *)mapView
{
if (self.isZoom) { // 這樣可以保證除縮放操作外其他操作不會繪制
if (self.lineArray.count) { // lineArray是存放MAPolyline的數組
[mapView removeOverlays:self.lineArray];
[self.lineArray removeAllObjects];;
}
[self drawRoadWithLine]; // 繪制
}
}
而折線的粗細可以在高德的rendererForOverlay方法根據zoomLevel設置, 以為重新繪制會再觸發該方法
- (MAOverlayRenderer *)mapView:(MAMapView *)mapView rendererForOverlay:(id <MAOverlay>)overlay
{
if ([overlay isKindOfClass:[MAPolyline class]])
{
MAPolyline *line = (MAPolyline *)overlay;
MAPolylineRenderer *polylineRenderer = [[MAPolylineRenderer alloc] initWithPolyline:line];
polylineRenderer.lineWidth = 2.f + (mapView.zoomLevel - 11) * 2.f / 6.f; // 11->2.f 17->6.f, 同偏移量, 可以根據自己需要設置
...
return polylineRenderer;
}
return nil;
}
更新
2021年7月10日:
以上方法一般不會有問題, 不過我發現會出現偏差的情況, 就是如果相鄰的三個點, 如果形成的夾角如果很小, 比如低于90°, ,那么就會出現偏差, 如下圖, 由于AB平移只得到了A', BC得到B', A'B'的連線顯然是不對的.
由于地圖上連線這些點, 接口給回來我們基本上不存在三個點的夾角會出現小于90°這種情況, 因為他要保證路線平滑, 所以我就沒改, 但這里我簡單說下一下我想的解決方法.
就是如果是n個點, 平移后生成2n - 2個點, 比如上面的圖, 平移后應該是下圖的A'B'B''C', 就是除了首尾點, 其他點都生成兩點, 之前我是兩點遍歷生成一個點, 這次是相鄰的兩點都生成兩個點, 偏移后的點的數量就變成了2n - 2個點了, 如果途中的B'B''重疊了也是不會影響路線的, 就是不知道會不會影響性能