美文网首页iOS技巧文字iOS公共基础
记录一次对运动记录轨迹曲线的纠偏

记录一次对运动记录轨迹曲线的纠偏

作者: codiy_huang | 来源:发表于2016-09-26 19:08 被阅读1304次

    本文提供的方法仅供客户端在没有太高的曲线精度要求的情况下优化的一种思路, 如果要求太高的话可以采用百度地图SDK提供的轨迹纠偏的API;

    首先说明一下需求的产生: 随着现在越来越多人开始注重运动, 运动类APP也是越来越多, 而运动轨迹的记录, 是运动类APP不可缺少的功能, 由于运动过程中 GPS 精度的变化, 不可避免的会导致定位误差,产生轨迹漂移的现象, 尽可能的减少漂移产生的轨迹曲线锯齿现象正是下面要解决的问题.

    先来看下面两张对图:


    优化前.PNG
    优化后.PNG

    从上面可以看出, 前后的效果还是比较不错, 去除了部分漂移带来的锯齿, 下面谈谈优化的思路.

    对比第一张图的锯齿形状可以看出, 大部分比较明显的锯齿的顶点与其前后两个点之间形成的夹角非常小, 一般来说, 角度小于120度的时候, 就会产生, 我们首先处理的就是这部分点的情况; 下面先看代码

    - (NSArray *)correctedLocationsFromLocations:(NSArray<CLLocation *> *)locations ratio:(CGFloat)ratio {
        @autoreleasepool {
            // 轨迹点异常校正
            NSInteger count = locations.count;
            NSMutableArray *tmp = [NSMutableArray arrayWithCapacity:locations.count];
            for (NSInteger i = 0; i < count; i++) {
                @autoreleasepool {
                    if (i > 0 && i < count - 1) {
                        CLLocation *location_a = tmp[i-1];
                        CLLocation *location_b = locations[i];
                        CLLocation *location_c = locations[i+1];
                        CGFloat a = fabs([location_b distanceFromLocation:location_c]);
                        CGFloat b = fabs([location_a distanceFromLocation:location_c]);
                        CGFloat c = fabs([location_a distanceFromLocation:location_b]);
                        CGFloat angle_b = acos(((a*a) + (c*c) - (b*b)) / (2*a*c));
                        if (angle_b < M_PI / 6) {
                            CLLocationDegrees mid_longitude = location_a.coordinate.longitude;
                            CLLocationDegrees mid_latitude = location_a.coordinate.latitude;
                            CGFloat speed = location_b.speed;
                            CGFloat course = location_b.course;
                            CGFloat horizontalAccuracy = location_b.horizontalAccuracy;
                            CGFloat verticalAccuracy = location_b.verticalAccuracy;
                            CGFloat altitude = location_b.altitude;
                            NSDate *timestamp = location_b.timestamp;
                            CLLocationCoordinate2D coor = {mid_latitude, mid_longitude};
                            CLLocation *loc = [[CLLocation alloc] initWithCoordinate:coor altitude:altitude horizontalAccuracy:horizontalAccuracy verticalAccuracy:verticalAccuracy course:course speed:speed timestamp:timestamp];
                            [tmp addObject:loc];
                        } else if (angle_b < M_PI / 3) {
                            CLLocationDegrees mid_longitude = (location_a.coordinate.longitude + location_c.coordinate.longitude) / 2;
                            CLLocationDegrees mid_latitude = (location_a.coordinate.latitude + location_c.coordinate.latitude) / 2;
                            CGFloat speed = location_b.speed;
                            CGFloat course = location_b.course;
                            CGFloat horizontalAccuracy = location_b.horizontalAccuracy;
                            CGFloat verticalAccuracy = location_b.verticalAccuracy;
                            CGFloat altitude = location_b.altitude;
                            NSDate *timestamp = location_b.timestamp;
                            CLLocationCoordinate2D coor = {mid_latitude, mid_longitude};
                            CLLocation *loc = [[CLLocation alloc] initWithCoordinate:coor altitude:altitude horizontalAccuracy:horizontalAccuracy verticalAccuracy:verticalAccuracy course:course speed:speed timestamp:timestamp];
                            [tmp addObject:loc];
                        } else if (angle_b < M_PI * ratio) {
                            CLLocationDegrees mid_longitude = (location_a.coordinate.longitude +location_b.coordinate.longitude + location_c.coordinate.longitude) / 3;
                            CLLocationDegrees mid_latitude = (location_a.coordinate.latitude + location_b.coordinate.latitude + location_c.coordinate.latitude) / 3;
                            CGFloat speed = location_b.speed;
                            CGFloat course = location_b.course;
                            CGFloat horizontalAccuracy = location_b.horizontalAccuracy;
                            CGFloat verticalAccuracy = location_b.verticalAccuracy;
                            CGFloat altitude = location_b.altitude;
                            NSDate *timestamp = location_b.timestamp;
                            CLLocationCoordinate2D coor = {mid_latitude, mid_longitude};
                            CLLocation *loc = [[CLLocation alloc] initWithCoordinate:coor altitude:altitude horizontalAccuracy:horizontalAccuracy verticalAccuracy:verticalAccuracy course:course speed:speed timestamp:timestamp];
                            [tmp addObject:loc];
                        } else {
                            CLLocation *loc = locations[i];
                            [tmp addObject:location_b];
                        }
                        continue;
                    } else {
                        CLLocation *loc = locations[i];
                        [tmp addObject:loc];
                        continue;
                    }
                }
            }
            return [tmp copy];
        }
    }
    

    这个方法需要传入需要校正的GPS点数组 locations 和一个最大校正的角度 ratio, 根据余弦定理:

    余弦定理三角形.png 角β计算公式.png
    由此可以计算出β的值:
    1. 如果 β < 30º, 将 B 点的坐标修改为 A 点的坐标;
    2. 如果 β < 60º, 将 B 点的坐标修改为 A 点和 B 点的中点的坐标;
    3. 如果 β < ratio, 将 B 点的坐标修改为 A 点、 B 点和C 点的的中点的坐标;

    第1. 2方法可以将三角形处理成一条直线, 3方法处理后可以增大 β, 降低锯齿; 通过多次调整ratio的值, 可以消除大部分的锯齿;

    在实际的使用中还出现了另外一个问题, 对于轨迹中出现的形如 "Z" 字的形状, 如下图, B和C两点总是在 AD连线的两边; 理想的状态总是想将这四个点连成一条直线, 但是经过以上的方法的处理以后, 得到的曲线是一条弯曲的曲线;

    Z形曲线.png

    这种情况比较难以确定angle_bangle_c的大小, 根据上面的经验, 按照平均每个角 90º 来处理(不要问我怎么来的, 我也不知道怎么来的, 哈哈哈), 那么两角之和小于 180º 的话, 就需要进行处理, 下面是处理的代码

    - (NSArray *)correcteZShapeFromLocations:(NSArray *)locations {
        @autoreleasepool {
            NSInteger count = locations.count;
            if (count <= 5) {
                return locations;
            }
            
            NSMutableArray *tmp = [NSMutableArray arrayWithCapacity:locations.count];
            for (NSInteger i = 0; i < count - 4; i++) {
                @autoreleasepool {
                    if (i > 0) {
                        {
                            CLLocation *location_a = tmp[i-1];
                            CLLocation *location_b = locations[i];
                            CLLocation *location_c = locations[i+1];
                            CLLocation *location_d = locations[i+2];
                            
                            CGFloat angle_b = 0;
                            CGFloat angle_c = 0;
                            {
                                CGFloat a = fabs([location_b distanceFromLocation:location_c]);
                                CGFloat b = fabs([location_a distanceFromLocation:location_c]);
                                CGFloat c = fabs([location_a distanceFromLocation:location_b]);
                                if (a>0 && c > 0) {
                                    angle_b = acos(((a*a) + (c*c) - (b*b)) / (2*a*c));
                                }
                            }
                            
                            {
                                CGFloat d = fabs([location_b distanceFromLocation:location_c]);
                                CGFloat b = fabs([location_d distanceFromLocation:location_c]);
                                CGFloat c = fabs([location_d distanceFromLocation:location_b]);
                                if (b>0 && d>0) {
                                    angle_c = acos(((d*d) + (b*b) - (c*c)) / (2*d*b));
                                }
                            }
                            
                            CGFloat r = (angle_b + angle_c);
                            if (r < M_PI && r > 0) {
                                CLLocation *location_mid_a_d = [self midLocationFromLocation1:location_a location2:location_d];
                                {
                                    CLLocationDegrees mid_longitude = (location_mid_a_d.coordinate.longitude + location_mid_a_d.coordinate.longitude) / 2;
                                    CLLocationDegrees mid_latitude = (location_mid_a_d.coordinate.latitude + location_mid_a_d.coordinate.latitude) / 2;
                                    CGFloat speed = location_b.speed;
                                    CGFloat course = location_b.course;
                                    CGFloat horizontalAccuracy = location_b.horizontalAccuracy;
                                    CGFloat verticalAccuracy = location_b.verticalAccuracy;
                                    CGFloat altitude = location_b.altitude;
                                    NSDate *timestamp = location_b.timestamp;
                                    CLLocationCoordinate2D coor = {mid_latitude, mid_longitude};
                                    CLLocation *loc = [[CLLocation alloc] initWithCoordinate:coor altitude:altitude horizontalAccuracy:horizontalAccuracy verticalAccuracy:verticalAccuracy course:course speed:speed timestamp:timestamp];
                                    [tmp addObject:loc];
                                }
                                
                                {
                                    CLLocationDegrees mid_longitude = (location_mid_a_d.coordinate.longitude + location_mid_a_d.coordinate.longitude) / 2;
                                    CLLocationDegrees mid_latitude = (location_mid_a_d.coordinate.latitude + location_mid_a_d.coordinate.latitude) / 2;
                                    CGFloat speed = location_c.speed;
                                    CGFloat course = location_c.course;
                                    CGFloat horizontalAccuracy = location_c.horizontalAccuracy;
                                    CGFloat verticalAccuracy = location_c.verticalAccuracy;
                                    CGFloat altitude = location_c.altitude;
                                    NSDate *timestamp = location_c.timestamp;
                                    CLLocationCoordinate2D coor = {mid_latitude, mid_longitude};
                                    CLLocation *loc = [[CLLocation alloc] initWithCoordinate:coor altitude:altitude horizontalAccuracy:horizontalAccuracy verticalAccuracy:verticalAccuracy course:course speed:speed timestamp:timestamp];
                                    [tmp addObject:loc];
                                }
                                
                                i++;
                            } else {
                                [tmp addObject:location_b];
                            }
                        }
                    } else {
                        CLLocation *loc = locations[i];
                        [tmp addObject:loc];
                    }
                }
            }
            
            CLLocation *location_1 = locations[count - 4];
            CLLocation *location_2 = locations[count - 3];
            CLLocation *location_3 = locations[count - 2];
            CLLocation *location_4 = locations[count - 1];
            
            [tmp addObject:location_1];
            [tmp addObject:location_2];
            [tmp addObject:location_3];
            [tmp addObject:location_4];
            
            return [tmp copy];
        }
    }
    

    获取两个点中间的点:

    /** 两个点的中点 */
    - (CLLocation *)midLocationFromLocation1:(CLLocation *)location1 location2:(CLLocation *)location2 {
        CLLocationDegrees mid_longitude = (location1.coordinate.longitude +location2.coordinate.longitude) / 2;
        CLLocationDegrees mid_latitude = (location1.coordinate.latitude + location2.coordinate.latitude) / 2;
        CGFloat speed = location1.speed;
        CGFloat course = location1.course;
        CGFloat horizontalAccuracy = location1.horizontalAccuracy;
        CGFloat verticalAccuracy = location1.verticalAccuracy;
        CGFloat altitude = location1.altitude;
        NSDate *timestamp = location1.timestamp;
        CLLocationCoordinate2D coor = {mid_latitude, mid_longitude};
        CLLocation *loc = [[CLLocation alloc] initWithCoordinate:coor altitude:altitude horizontalAccuracy:horizontalAccuracy verticalAccuracy:verticalAccuracy course:course speed:speed timestamp:timestamp];
        return loc;
    }
    

    好了, 通过以上的处理, 基本可以满足客户端对轨迹的粗略纠偏; 如果大家还有其他思路的话, 可以一起探讨.

    相关文章

      网友评论

      • 蓝蓝的白云:大神,有demo吗?发我一下学习学习
      • 众彳亍:请问下作者,你这个对于折返跑是不是不能狗适用啊?
        codiy_huang:@众彳亍 可以适用,不过要看在拐角处的那些点是什么情况,如果点比较多,是没问题的,如果只有一两个,这些点都会被处理
      • wo叫天然呆:你好,我想问下,如果在运动的过程中,GPS断了,这时候定位误差就会变得比较大,如何去校准?你这套还适用么?
        codiy_huang:@wo叫天然呆 我们之前的处理方式是把这些点保存起来,等待后续有高精度的点加入以后,按照文章里提到的方式处理,多次处理以后,可以尽量变平滑,如果要求太高的话,可以用第三方的像百度SDK提供的纠偏的方法处理
        wo叫天然呆:@codiy_huang 如果是精度误差变大么,因为我们现在一般都是GPS+数据连接定位,GPS没了就剩下数据连接,精度误差一下子扩大了50倍。这种情况下,如何去校准呢
        codiy_huang:这种情况要分析是什么原因导致的GPS断掉,一般来说,断了以后分两种情况,一种是精度变小,一种是根本获取不到定位点,要具体来处理这两种
      • 3c31e1ae2731:你好 我想知道你是自己想的,还是参考的其他文章,我想了解更多关于这个问题的资料
        codiy_huang:@吟诵的利威尔 这是自己琢磨出来的,所以有很多参数都很随便,有空可以探讨探讨

      本文标题:记录一次对运动记录轨迹曲线的纠偏

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