美文网首页
动画旅行路线地图

动画旅行路线地图

作者: 谁偷走了我爱吃的奶酪 | 来源:发表于2022-03-04 16:02 被阅读0次

前言:

这里写这篇文章是为了记录一下动画旅行路线地图从0到1的整个实现过程,心态、技术上都有一些想要分享的点,目前基本上没有文章有讲到这些,如果你正在实现类似的功能 (参照TravelBoast或者恋爱记的旅行地图模块),那么这将会帮到你


动画旅行路线地图

动画旅行路线技术功能点

  1. 绘制弧线
  2. 拖拽弧线生成新的途径点
  3. 预览视频地图进入的动画
  4. 车辆沿弧线进行移动
  5. 生成视频

绘制弧线

先去看高德的开发者文档,看有没有对应生成弧线的API,然而高德只支持折线,这里曾经都打算放弃了,后边就想着,高德不支持,那就自己通过贝塞尔来绘制弧线, 但是贝塞尔曲线封装的API最多只有三次贝塞尔曲线,那也就是四个点,问产品,最多支持两个途径点行不行,产品的回答你懂的,那只有继续查资料,功夫不负有心人,最终发现了Centripetal Catmull–Rom spline,可以通过一组点生成平滑的曲线,哈哈,弧线这就可以实现了,成功的迈出了第一步,这里贴上生成曲线的代码

/// 通过一组坐标生成曲线
/// @param pointsArray 坐标数组
/// @param granularity 点的数量
- (UIBezierPath *)smoothedPathWithPoints:(NSArray *) pointsArray andGranularity:(NSInteger)granularity {
    
    NSMutableArray *points = [pointsArray mutableCopy];
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetAllowsAntialiasing(context, YES);
    CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
    CGContextSetLineWidth(context, 0.6);
    UIBezierPath *smoothedPath = [UIBezierPath bezierPath];
   
    // Add control points to make the math make sense
    [points insertObject:[points objectAtIndex:0] atIndex:0];
    [points addObject:[points lastObject]];
    [smoothedPath moveToPoint:POINT(0)];
    
    for (NSUInteger index = 1; index < points.count - 2; index++) {
        CGPoint p0 = POINT(index - 1);
        CGPoint p1 = POINT(index);
        CGPoint p2 = POINT(index + 1);
        CGPoint p3 = POINT(index + 2);
        
        // now add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines
        for (int i = 1; i < granularity; i++) {
            
            float t = (float) i * (1.0f / (float) granularity);
            float tt = t * t;
            float ttt = tt * t;
            
            CGPoint pi; // intermediate point
            pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt);
            pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt);
            [smoothedPath addLineToPoint:pi];
        }
        
        // Now add p2
        [smoothedPath addLineToPoint:p2];
    }
    
    // finish by adding the last point
    [smoothedPath addLineToPoint:POINT(points.count - 1)];
    return smoothedPath;
}

拖拽弧线生成新的途径点

这里可以拆分为弧线拖拽以及点的插入,首先是弧线拖拽,我们可以先给地图添加移动手势,手势开始移动,判断手势的point是否在点上,取出对应的小标,进行点的位置更新,不在点上再判断是否在p0 和 p1两点构成的线段上,取出对应的小标插入新的点到数组中,生成新的途径点,如果都不在,就不做处理,这样拖拽弧线和点的生成、位置更新都可以实现了,判断在不在点上比较简单,就不赘述,这里贴上判断点point是否在p0 和 p1两点构成的线段上的代码

/**
*判断点point是否在p0 和 p1两点构成的线段上
*/
- (BOOL)xw_point:(CGPoint)point isInLineByTwoPoint:(CGPoint)p0 p1:(CGPoint)p1 {
    // 先设置一个所允许的最大值,点到线段的最短距离小于该值说明点在线段上
    CGFloat maxAllowOffsetLength = 40;
    // 通过直线方程的两点式计算出一般式的ABC参数,具体可以自己拿起笔换算一下,很容易
    CGFloat A = p1.y - p0.y;
    CGFloat B = p0.x - p1.x;
    CGFloat C = p1.x * p0.y - p0.x * p1.y;
    // 带入点到直线的距离公式求出点到直线的距离dis
    CGFloat dis = fabs((A * point.x + B * point.y + C) / sqrt(pow(A, 2) + pow(B, 2)));
    // 如果该距离大于允许值说明则不在线段上
    if (dis > maxAllowOffsetLength || isnan(dis)) {
        NSLog(@"===================inout");
        return NO;
    } else {
    // 否则我们要进一步判断,投影点是否在线段上,根据公式求出投影点的X坐标jiaoX
        CGFloat D = (A * point.y - B * point.x);
        CGFloat jiaoX = -(A * C + B *D) / (pow(B, 2) + pow(A, 2));
        //判断jiaoX是否在线段上,t如果在0~1之间说明在线段上,大于1则说明不在线段且靠近端点p1,小于0则不在线段上且靠近端点p0,这里用了插值的思想
        CGFloat t = (jiaoX - p0.x) / (p1.x - p0.x);
        if (t > 1  || isnan(t)) {
        //最小距离为到p1点的距离
            dis = XWLengthOfTwoPoint(p1, point);
        } else if (t < 0) {
        //最小距离为到p2点的距离
            dis = XWLengthOfTwoPoint(p0, point);
        }
        //再次判断真正的最小距离是否小于允许值,小于则该点在直线上,反之则不在
        if (dis <= maxAllowOffsetLength) {
            NSLog(@"===================inside");
            return YES;
        } else {
            NSLog(@"===================inout");
            return NO;
        }
    }
}

//这里是求两点距离公式
static inline CGFloat XWLengthOfTwoPoint(CGPoint point1, CGPoint point2) {
    return sqrt(pow(point1.x - point2.x, 2) + pow(point1.y - point2.y, 2));
}

预览视频地图进入的动画

刚开始看travelBoast的地图进入动画,感觉好炫酷,这个动画要咋写,非常疑惑,再去翻了翻高德的开发文档,说不定就有收获,哈哈,意外发现,高德提供了地图的一系列动画,于是慢慢的调整参数,两行代码就搞定了,最后进入的动画效果还不错

 MAMapStatus *status = [MAMapStatus statusWithCenterCoordinate:startAnnotation.coordinate zoomLevel:self.mapView.zoomLevel + 0.5 rotationDegree:0 cameraDegree:90 screenAnchor:CGPointMake(0.5, 0.5)];
[self.mapView setMapStatus:status animated:YES duration:1.0];

车辆沿弧线进行移动

这里弧线也参照Centripetal Catmull–Rom spline生成一组点进行创建,不同的是,这里需要用高德的折线进行绘制,因为预览时,用户可以进行地图的旋转、放大、缩小,所以需要绘制到地图上,所以通过高德的API生成一条线段,这里注意的是,实线会有一点点不那么曲,设置成虚线可以解决这个小问题,弧线这里画好了, 就需要开始移动车辆了,因为地图的中心点需要和车辆一起移动,所以这里就添加了一个定时器,持续的设置地图的setCenterCoordinate为下一个点的经纬度和设置车辆的经纬度就可以实现地图和车辆移动的效果了,最后我们还需要设置车辆的方向和弧线的方向保持一致,思路就是先获取下一个点和当前点的角度差,将车辆进行旋转就可以了

- (void)linkClick {
    if (self.index == self.coordinates.count - 1) {
        [self showMoveEndingAnimationView];
        return;
    }
    
    CLLocationCoordinate2D coordinate = [self.coordinates[self.index] MACoordinateValue];
    
    [self.annotations enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        LNAnnotation *annotation = (LNAnnotation *)obj;
        /// 这里是获取用户创建的途径点,进行拖尾、载具等更新
        if (fabs(annotation.coordinate.latitude - coordinate.latitude) < 0.0001 && fabs(annotation.coordinate.longitude - coordinate.longitude) < 0.0001) {
            self.startAnnotationView.animatedAnnotation = annotation;
            *stop = YES;
        }
    }];
    
    CGPoint currentPoint = [self.mapView convertCoordinate:coordinate toPointToView:self.mapView];
    CGPoint nextPoint = [self.mapView convertCoordinate:[self.coordinates[self.index + 1] MACoordinateValue] toPointToView:self.mapView];
    CGFloat angle = [self angleForStartPoint:currentPoint EndPoint:nextPoint];
    self.startAnnotationView.transform =  CGAffineTransformMakeRotation(M_PI_2 + angle);
    [self.mapView setCenterCoordinate:coordinate animated:NO];
    [self.startAnnotation setCoordinate:coordinate];
    self.index += 1;
}

- (CGFloat)angleForStartPoint:(CGPoint)startPoint EndPoint:(CGPoint)endPoint{
    
    CGPoint Xpoint = CGPointMake(startPoint.x + 100, startPoint.y);
    
    CGFloat a = endPoint.x - startPoint.x;
    CGFloat b = endPoint.y - startPoint.y;
    CGFloat c = Xpoint.x - startPoint.x;
    CGFloat d = Xpoint.y - startPoint.y;
    
    CGFloat rads = acos(((a*c) + (b*d)) / ((sqrt(a*a + b*b)) * (sqrt(c*c + d*d))));
    
    if (startPoint.y>endPoint.y) {
        rads = -rads;
    }
    return rads;
}

生成视频

最开始的方案是想要进行MapView的录屏,从而生成视频进行保存,查了好久资料都没有找到对应的方案,如果有同学知道,可以留言告诉我一下,最后没办法,选择了开启定时器,每秒截屏30帧,然后通过AVAssetWriter进行写成视频,最后保存到相册,这里不想自己写,可以参考Glimpse,封装了一下,可以拿来直接用,但是内存消耗会特别大,10s视频,会到1G往上走,低端机型就直接崩溃了,解决方案也很简单,再截图的时候,对图片进行压缩,内存消耗会减少很多,代码如下, 那么到这里视频的生成就可以实现了

- (UIImage *)imageFromView:(UIView *)view {
    UIGraphicsBeginImageContextWithOptions(view.frame.size , YES , 0 );
    
    if ([view respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
        [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
    } else {
        [view.layer renderInContext:UIGraphicsGetCurrentContext()];
    }
    UIImage *rasterizedView = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return [UIImage imageWithData:UIImageJPEGRepresentation(rasterizedView, 0.8)];
}

上面就是动画旅行路线地图主体的实现思路,当然,还有一些小细节实现没有讲到,但是相信大家都可以解决,回顾从开始准备放弃,到后来一步一步的往下走,最后实现功能,还是比较有成就感的,其实这次让我学习到,遇到困难,不要害怕,可以将困难进行拆分,一步一步的去克服它,就没那么难了,再就是不要轻言放弃,老话说的好,世上无难事,只怕有心人

相关文章

  • 动画旅行路线地图

    前言: 这里写这篇文章是为了记录一下动画旅行路线地图从0到1的整个实现过程,心态、技术上都有一些想要分享的点,目前...

  • 英语学习102,景点

    昨天我们确定了自驾新疆路线,我翻出地图,沿着旅行路线,把将要游览的景点依次列出来。 景点:嘉峪关-天山天池-喀纳斯...

  • 简单的路线规划demo

    前言 这个腾讯地图的demo可以做到:1.定位位置2.规划起点和终点的路线3.显示规划路线距离以及计算路费4.动画...

  • 地图路线

    8路车直接到新江南站,交易中心旁边,在车上我可以做很多事情,平时几乎都在方圆半里周边活动,今天外出远一些 喜欢用高...

  • Android高德地图开发(六)路线规划

    一、概述 这一章我们学习高德地图的路线规划,主要分为驾车路线,步行路线,公交路线,骑行路线。在高德地图中数据的获取...

  • 3-30~4-7日本行

    本次行程为期9天8晚,路线为东京->富士山->大阪->京都->奈良->东京 本次旅行地点:谷歌地图 日本气象map...

  • 高德地图之路线规划 多点路线规划路线最短原则排序算法

    继续高德地图 高德地图-自定义地图 高德地图-自定义导航 tsp最短路线原则和demo下载 继续写路线规划 上一...

  • 地图规划路线

    // ViewController.m import import "YYA...

  • Android 我们的矢量地图,放大不失真

    地图的轮廓看起来是不规则的,不规则轮廓就是地图的路线。路线在手,说走就走。我们一起来写一个矢量地图。 先来了解矢量...

  • 实习小记 十:单验(模拟)

    根据站名,搜索基站; 地图,高德大路线,奥维寻站,小路线; 在CDS plus也可以看地图; 首先找到站。 询问后...

网友评论

      本文标题:动画旅行路线地图

      本文链接:https://www.haomeiwen.com/subject/awjurrtx.html