问题: 接口给出一条路上下两车道路线的经纬度, 让我们用不同颜色绘制, 表示路况, 如果直接绘制, 缩小地图两条车道会重叠在一起, 只有放大地图才能看到两条车道
网上搜了很久都没有相关资料, 所以我想了个应该不是最好的方法, 就是将路线向旁边偏移一定的距离, 这个距离随着地图缩放级别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''重叠了也是不会影响路线的, 就是不知道会不会影响性能