iOS 高德地图用折线绘制自定义来回路况

问题: 接口给出一条路上下两车道路线的经纬度, 让我们用不同颜色绘制, 表示路况, 如果直接绘制, 缩小地图两条车道会重叠在一起, 只有放大地图才能看到两条车道

网上搜了很久都没有相关资料, 所以我想了个应该不是最好的方法, 就是将路线向旁边偏移一定的距离, 这个距离随着地图缩放级别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''重叠了也是不会影响路线的, 就是不知道会不会影响性能


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容