iOS 百度地图路线绘制与小车平滑移动

项目中用到了百度地图,记录一下,为以后开发提供帮助

纹理绘制和分段颜色绘制

我们在利用百度地图计算出路线的点后可以在地图上绘制出自己想要的纹理路线或者分段颜色显示,通过自定义或者利用百度给出的类直接绘制。

我们在计算出路线之后就需要进行路线的绘制了,我们在路线的回调方法中进行设置

#pragma mark - 计算车辆路线结果回调
- (void)onGetDrivingRouteResult:(BMKRouteSearch *)searcher result:(BMKDrivingRouteResult *)result errorCode:(BMKSearchErrorCode)error

当获取到路线的点时,根据百度地图demo,我们可以在此方法中进行路线的自定义设置,可以是纹理图片,可以是分段颜色绘制,也可以是单一颜色,都可以。

     // 通过points构建BMKPolyline
     //构建分段颜色索引数组
     NSArray *colorIndexs = [NSArray arrayWithObjects:
     [NSNumber numberWithInt:0],nil];
     //构建BMKPolyline,使用分段颜色索引,其对应的BMKPolylineView必须设置colors属性
     myPolyline = [BMKPolyline polylineWithPoints:temppoints count: planPointCounts textureIndex:colorIndexs];       
      [_mapView addOverlay:myPolyline];

路线分段颜色绘制
#pragma mark - 路线分段颜色绘制
//根据overlay生成对应的View
- (BMKOverlayView *)mapView:(BMKMapView *)mapView viewForOverlay:(id <BMKOverlay>)overlay
{    
    if ([overlay isKindOfClass:[BMKPolyline class]])
    {
        BMKPolylineView* polylineView = [[BMKPolylineView alloc] initWithOverlay:overlay];
        if (overlay == colorfulPolyline) {
            polylineView.lineWidth = 5;
            /// 使用分段颜色绘制时,必须设置(内容必须为UIColor)
            polylineView.colors = [NSArray arrayWithObjects:
                                   [[UIColor alloc] initWithRed:0 green:1 blue:0 alpha:1],
                                   [[UIColor alloc] initWithRed:1 green:0 blue:0 alpha:1],
                                   [[UIColor alloc] initWithRed:1 green:1 blue:0 alpha:0.5], nil];
        } else {
            polylineView.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:1];
            polylineView.lineWidth = 20.0;
            [polylineView loadStrokeTextureImage:[UIImage imageNamed:@"arrowTexture"]];
        }
        return polylineView;
    }
    return nil;
}

路线纹理绘制
#pragma mark - 路线纹理绘制

//根据overlay生成对应的View
-(BMKOverlayView *)mapView:(BMKMapView *)mapView viewForOverlay:(id<BMKOverlay>)overlay{

    if ([overlay isKindOfClass:[BMKPolyline class]])
    {
        BMKPolylineView* polylineView = [[BMKPolylineView alloc] initWithOverlay:overlay];

        polylineView.lineWidth = 8;
        polylineView.isFocus = YES;
        polylineView.tileTexture = YES;

        // 加载分段纹理绘制 
        UIImage *image = [UIImage imageNamed:@"lv"];
        [polylineView loadStrokeTextureImages:@[image]];

        return polylineView;
    }

    return nil;
}


【纹理图片】

  • (百度demo中获取)—-> 图片宽高必须为2的幂次方才可以正确的在项目中使用。

  • 这里写图片描述

  • 这里写图片描述

//

华丽的分割线

//

下面介绍一下小车的平滑移动,实现类似滴滴打车,小车移动的效果。

这里写图片描述

步骤1
我们需要先自定义Annotation类,为小车的平滑移动保存方向和Id数据,自定义大头针,方便设置小车方向的旋转 ,两个自定义的类都是为了之后的平滑移动提供帮助。

// 自定义DBMKPointAnnotation用来保存id和angle
@interface DBMKPointAnnotation : BMKPointAnnotation
/** ID  */
@property(strong,nonatomic)NSString *ID;
/** angle  */
@property(strong,nonatomic)NSString *Angle;
@end
// 自定义BMKAnnotationView,用于显示小车
@interface SportAnnotationView1 : BMKAnnotationView

@property (nonatomic, strong) UIImageView *imageView;

@end

@implementation SportAnnotationView1

@synthesize imageView = _imageView;

- (id)initWithAnnotation:(id<BMKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
    if (self) {
        //设置小车显示大小
        [self setBounds:CGRectMake(0.f, 0.f, 40.f, 40.f)];
        _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.f, 0.f, 40.f, 40.f)];
        _imageView.image = [UIImage imageNamed:@"小车图片"];
        _imageView.contentMode = UIViewContentModeScaleAspectFit;
        _imageView.userInteractionEnabled = YES;
        [self addSubview:_imageView];
    }
    return self;
}

@end

步骤2
实时的获取车辆的经纬度,方向,数据,进行展示.
百度给我们的方法非常简单,就是利用UIView动画,对大头针进行一个经纬度及反向转向的设置。下面是代码 —>

   [UIView animateWithDuration:0.5 animations:^{
        //转向                                                                         sportAnnotationView.imageView.transform=CGAffineTransformMakeRotation(angle);
} completion:^(BOOL finished) {
    //平移
    [UIView animateWithDuration:5.5 animations:^{
        annotation.coordinate = CLLocationCoordinate2DMake([dic[@"Lat"] floatValue], [dic[@"Lng"] floatValue]);
    }];
}];

至此小车的平滑移动,我们就可以简单的处理好了。但是,这样处理的话,会有一个bug,就是对地图进行缩放时,小车对跑出地图外,这个问题是怎么引起的呢?我们应该怎么解决呢?

引起这个问题的原因是运用的UIView动画,实际修改了属性的真实值,进行动画,UIView动画是一个过程动画,在缩放时会改变动画过程,导致平滑移动出路线外bug。这个问题就涉及到了UIView动画与核心动画的区别问题,在这个问题的处理上,我们用KeyAnimation动画进行处理(解决方案是根据百度官方人员回复处理的)。

这里创建一个显示当前位置的图层

#import <BaiduMapAPI_Map/BMKMapComponent.h>
#import <BaiduMapAPI_Utils/BMKUtilsComponent.h>

@interface CACoordLayer : CALayer

@property (nonatomic, assign) BMKMapView * mapView;

//定义一个BMKAnnotation对象
@property (nonatomic, strong) BMKPointAnnotation *annotation;

@property (nonatomic) double mapx;

@property (nonatomic) double mapy;

@property (nonatomic) CGPoint centerOffset;

@end
//
//  CACoordLayer.m
//

#import "CACoordLayer.h"

@implementation CACoordLayer

@dynamic mapx;
@dynamic mapy;

- (id)initWithLayer:(id)layer
{
    if ((self = [super initWithLayer:layer]))
    {
        if ([layer isKindOfClass:[CACoordLayer class]])
        {
            CACoordLayer * input = layer;
            self.mapx = input.mapx;
            self.mapy = input.mapy;
            [self setNeedsDisplay];
        }
    }
    return self;
}

+ (BOOL)needsDisplayForKey:(NSString *)key
{

    if ([@"mapx" isEqualToString:key])
    {
        return YES;
    }
    if ([@"mapy" isEqualToString:key])
    {
        return YES;
    }

    return [super needsDisplayForKey:key];
}

- (void)display
{

    CACoordLayer * layer = [self presentationLayer];
    BMKMapPoint mappoint = BMKMapPointMake(layer.mapx, layer.mapy);
    //根据得到的坐标值,将其设置为annotation的经纬度
    self.annotation.coordinate = BMKCoordinateForMapPoint(mappoint);
    //设置layer的位置,显示动画
    CGPoint center = [self.mapView convertCoordinate:BMKCoordinateForMapPoint(mappoint) toPointToView:self.mapView];
    self.position = center;
}

@end

CACoordLayer类的关键代码needsDisplayForKey方法和图层重绘方法display,就是当mapx,mapy改变时会调用图层的重绘功能,为动画做铺垫。为大头针提供动画的能力。

根据创建好的显示当前位置的图层进行移动,下面主要说下关键代码,在自定义的大头针中设置使用的图层为我们刚才创建的图层CACoordLayer,当我们获取到车辆经纬度,方向等信息传给大头针时,我们需要的操作为

- (void)addTrackingAnimationForPoints:(NSArray *)points duration:(CFTimeInterval)duration
{
    if (![points count])
    {
        return;
    }

    CACoordLayer * mylayer = ((CACoordLayer *)self.layer);

    //preparing
    NSUInteger num = 2*[points count] + 1;
    NSMutableArray * xvalues = [NSMutableArray arrayWithCapacity:num];
    NSMutableArray *yvalues = [NSMutableArray arrayWithCapacity:num];

    NSMutableArray * times = [NSMutableArray arrayWithCapacity:num];

    double sumOfDistance = 0.f;
    //dis保存的为地址
    double * dis = malloc(([points count]) * sizeof(double));
//    NSLog(@"%p",dis);


    //the first point is set by the destination of last animation.
    BMKMapPoint preLoc;
    if (!([self.animationList count] > 0 || isAnimatingX || isAnimatingY))
    {
        lastDestination = BMKMapPointMake(mylayer.mapx, mylayer.mapy);
    }
    preLoc = lastDestination;

    [xvalues addObject:@(preLoc.x)];
    [yvalues addObject:@(preLoc.y)];
    [times addObject:@(0.f)];

    //set the animation points.
    for (int i = 0; i<[points count]; i++)
    {
        TracingPoint * tp = points[i];

        //position
        BMKMapPoint p = BMKMapPointForCoordinate(tp.coordinate);
        [xvalues addObjectsFromArray:@[@(p.x), @(p.x)]];//stop for turn
        [yvalues addObjectsFromArray:@[@(p.y), @(p.y)]];

        //distance
        dis[i] = BMKMetersBetweenMapPoints(p, preLoc);
        sumOfDistance = sumOfDistance + dis[i];
        dis[i] = sumOfDistance;

        //record pre
        preLoc = p;
    }

    //set the animation times.
    double preTime = 0.f;
    double turnDuration = TurnAnimationDuration/duration;
    for (int i = 0; i<[points count]; i++)
    {
        double turnEnd = dis[i]/sumOfDistance;
        double turnStart = (preTime > turnEnd - turnDuration) ? (turnEnd + preTime) * 0.5 : turnEnd - turnDuration;

        [times addObjectsFromArray:@[@(turnStart), @(turnEnd)]];

        preTime = turnEnd;
    }

    //record the destination.
    TracingPoint * last = [points lastObject];
    lastDestination = BMKMapPointForCoordinate(last.coordinate);

    free(dis);

    // add animation.
    CAKeyframeAnimation *xanimation = [CAKeyframeAnimation animationWithKeyPath:MapXAnimationKey];
    xanimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
    xanimation.values   = xvalues;
    xanimation.keyTimes = times;
    xanimation.duration = duration;
    xanimation.delegate = self;
    xanimation.fillMode = kCAFillModeForwards;

    CAKeyframeAnimation *yanimation = [CAKeyframeAnimation animationWithKeyPath:MapYAnimationKey];
    yanimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
    yanimation.values   = yvalues;
    yanimation.keyTimes = times;
    yanimation.duration = duration;
    yanimation.delegate = self;
    yanimation.fillMode = kCAFillModeForwards;

    [self pushBackAnimation:xanimation];
    [self pushBackAnimation:yanimation];

    mylayer.mapView = [self mapView];
}
#pragma mark - Animation Delegate

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    if ([anim isKindOfClass:[CAKeyframeAnimation class]])
    {
        CAKeyframeAnimation * keyAnim = ((CAKeyframeAnimation *)anim);
        if ([keyAnim.keyPath isEqualToString:MapXAnimationKey])
        {
            isAnimatingX = NO;

            CACoordLayer * mylayer = ((CACoordLayer *)self.layer);
            mylayer.mapx = ((NSNumber *)[keyAnim.values lastObject]).doubleValue;
            currDestination.x = mylayer.mapx;

            [self updateAnnotationCoordinate];
            [self popFrontAnimationForKey:MapXAnimationKey];
        }
        else if ([keyAnim.keyPath isEqualToString:MapYAnimationKey])
        {
            isAnimatingY = NO;

            CACoordLayer * mylayer = ((CACoordLayer *)self.layer);
            mylayer.mapy = ((NSNumber *)[keyAnim.values lastObject]).doubleValue;
            currDestination.y = mylayer.mapy;

            [self updateAnnotationCoordinate];
            [self popFrontAnimationForKey:MapYAnimationKey];
        }
        animateCompleteTimes++;
        if (animateCompleteTimes % 2 == 0) {
            if (_animateDelegate && [_animateDelegate respondsToSelector:@selector(movingAnnotationViewAnimationFinished)]) {
                [_animateDelegate movingAnnotationViewAnimationFinished];
            }
        }
    }
}

这个方法所做的就是将经纬度坐标转换为投影后的直角地理坐标,然后进行CAKeyframeAnimation动画,移动的属性是自定义CACoordLayer中的mapx,mapy,而不是UIView动画中的经纬度,我们都知道核心动画都是假象,并不是真正的移动,所以在移动完之后,重新为mapx赋值,更新大头针位置,避免出现小车移动的起始位置错乱的问题,简易的流程应该是这样的

Created with Raphaël 2.1.0 KeyFrame动画对mapx属性动画 needsDisplayForKey中设置了当前指定的属性key改变是否需要重新绘制,所以产生了动画 当动画结束后 赋值改变mapx,mapy,的值,保证下次动画起始位置的正确性 yes

//

华丽的分割线

//

内边距的设置

我们的项目还要实现,当路线绘制出来后,需要显示这个路线,并且需要有内边距,当我调用这个接口时

   [_mapView setVisibleMapRect:polyLine.boundingMapRect edgePadding:UIEdgeInsetsMake(20, 20, 280, 20) animated:YES];

地图缩放的显示范围并不是我设置的内边距,有误差,类似于这种
这里写图片描述

通过与百度地图技术人员的询问,得知在调取polyLine.boundingMapRect 不能够获取到数值,技术人员给出了另一个方法获取到polyline的MapRect,然后设置了内边距

//根据polyline设置地图范围
- (void)mapViewFitPolyLine:(BMKPolyline *) polyLine {
    CGFloat ltX, ltY, rbX, rbY;
    if (polyLine.pointCount < 1) {
        return;
    }
    BMKMapPoint pt = polyLine.points[0];
    ltX = pt.x, ltY = pt.y;
    rbX = pt.x, rbY = pt.y;
    for (int i = 1; i < polyLine.pointCount; i++) {
        BMKMapPoint pt = polyLine.points[i];
        if (pt.x < ltX) {
            ltX = pt.x;
        }
        if (pt.x > rbX) {
            rbX = pt.x;
        }
        if (pt.y < ltY) {
            ltY = pt.y;
        }
        if (pt.y > rbY) {
            rbY = pt.y;
        }
    }
    BMKMapRect rect;
    rect.origin = BMKMapPointMake(ltX , ltY);
    rect.size = BMKMapSizeMake(rbX - ltX, rbY - ltY);
    [_mapView setVisibleMapRect:rect edgePadding:UIEdgeInsetsMake(0, 0, 100, 0) animated:YES];
    _mapView.zoomLevel = _mapView.zoomLevel - 0.3;
}

而且根据百度技术人员的说法,这个方法也会有误差,是因为SDK设置了边距的级别,目前暂不能根据边距的大小进行精确设置

不过要按照项目要求实现,如果有更好的方法,可以告诉我,谢谢。






如有错误的地方❌,请指正✅。。。

第一次写博客,玻璃心求轻喷,希望自己能坚持下去,当做一种知识总结也能分享给一些新手朋友们.

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值