简体   繁体   English

iOS路径跟随:将坐标列表转换为贝塞尔曲线

[英]iOS path following: convert list of coordinates to a bezier curve

I have an array of coordinates which represents a users route. 我有一个坐标数组,代表用户路线。 I want to be able to redraw this route and have another user follow it exactly. 我希望能够重绘这条路线并让其他用户完全遵循它。 Although I only want to save significant coordinates (waypoints) when the route will change direction. 虽然我只想在路线改变方向时保存重要的坐标(航路点)。

Is there a way I can convert the array of coordinates to a bezier curve and use this so I only get significant coordinates instead of saving every one in an array(most of which are in the same direction and aren't needed) 有没有办法我可以将坐标数组转换为贝塞尔曲线并使用它,所以我只得到重要的坐标,而不是保存一个数组中的每一个(大多数是相同的方向,不需要)

I want to be able to reconstruct the route to give waypoints. 我希望能够重建路线以提供航路点。 These waypoints will be used to direct the user along the route as eventually I will be using MKDirections to direct them to each waypoint. 这些航路点将用于引导用户沿着路线前进,因为最终我将使用MKDirections将它们引导到每个航路点。

You should not use MKDirections since it is not made for following a precalculated path but to give you the fastest route to the destination! 您不应该使用MKDirections,因为它不是为了遵循预先计算的路径而是为您提供到达目的地的最快路线! Therefore I will use MKMapOverlayRenderer instead! 因此我将改用MKMapOverlayRenderer!

There are several things you can to achieve this. 有几件事你可以实现这一目标。 I want to talk about easy algorithm for removing the unnecessary waypoints: 我想谈谈用于删除不必要的航路点的简单算法:

NSArray* path = YOURPATH
NSMutableArray* waypoints = [path mutableCopy];

CGFloat smoothness = 0.01;

for(NSInteger scale=1;scale<log2(waypoints.count);scale++)
{
    for(NSInteger index = 0; index<waypoints.count-2;index++)
    {
        CLLocation *start = [waypoints objectAtIndex:index];
        CLLocation *middle = [waypoints objectAtIndex:index+1];
        CLLocation *end = [waypoints objectAtIndex:index+2];

        CGFloat dist_start_end = [end distanceFromLocation:start];
        CGFloat dist_start_middle_end = [middle distanceFromLocation:start]+[end distanceFromLocation:middle];

        CGFloat diff = dist_start_end-dist_start_middle_end;
        CGFloat ratio = diff/(dist_start_end);

        if (ratio<1+smoothness) {
            [waypoints removeObjectAtIndex:index+1];
            index-=1;
        }
    }
}

This should remove the unwanted points. 这应该删除不需要的点。 You should playaround with the 'smoothness' variable. 您应该使用'smoothness'变量进行游戏。 The bigger the value, the more details will be removed! 值越大,删除的细节越多! (therefore better compression) (因此压缩效果更好)

Now with this array, you can construct your bezier path! 现在使用此数组,您可以构建贝塞尔路径!

I will not go into the details on how to actually put the bezier path onto the map, there are so many other questions about this on Stackoverflow and also tutorials on the internet. 我不会详细介绍如何将bezier路径实际放到地图上,还有很多关于Stackoverflow的其他问题以及互联网上的教程。 But I will shortly touch on how you should create/use the two control points of the bezier path: 但我将简要介绍如何创建/使用bezier路径的两个控制点:

Given the waypoints array we created above, you will probably add some additional control points, in order to avoid driving directions taking a shortcut through a building. 给定我们上面创建的航点阵列,您可能会添加一些额外的控制点,以避免驾驶方向在建筑物中使用快捷方式。 Therefore you should calculate the control points between two actual points on the path like this: 因此,您应该计算路径上两个实际点之间的控制点,如下所示:

CGFloat cornerRadius = 5;
NSMutableArray* pathPoints = [NSMutableArray new];

if (waypoints.count>1) {
    [pathPoints addObject:[waypoints objectAtIndex:0]];
    [pathPoints addObject:[waypoints objectAtIndex:0]];
    [pathPoints addObject:[waypoints objectAtIndex:1]];
    [pathPoints addObject:[waypoints objectAtIndex:1]];
}
for (NSInteger index = 1;index<waypoints.count-1;index++) {
    CLLocation* lastLocation = [waypoints objectAtIndex:index-1];
    CLLocation* currentLocation = [waypoints objectAtIndex:index];
    CLLocation* nextLocation = [waypoints objectAtIndex:index+1];

    [pathPoints addObject:currentLocation];

    CGFloat latDiff = currentLocation.coordinate.latitude - lastLocation.coordinate.latitude;
    CGFloat longDiff = currentLocation.coordinate.longitude - lastLocation.coordinate.longitude;
    CGFloat dist = [currentLocation distanceFromLocation:lastLocation];
    CGFloat controlPointALat = cornerRadius*latDiff/dist;
    CGFloat controlPointALong = cornerRadius*longDiff/dist;

    CLLocation* controlPointA =
    [[CLLocation alloc] initWithLatitude:controlPointALat longitude:controlPointALong];
    [pathPoints addObject:controlPointA];

    if (index<waypoints.count-2) {
        CLLocation* nextNextLocation = [waypoints objectAtIndex:index+2];

        latDiff = nextNextLocation.coordinate.latitude - nextLocation.coordinate.latitude;
        longDiff = nextNextLocation.coordinate.longitude - nextLocation.coordinate.longitude;
        dist = [nextNextLocation distanceFromLocation:nextLocation];
        CGFloat controlPointBLat = cornerRadius*latDiff/dist;
        CGFloat controlPointBLong = cornerRadius*longDiff/dist;

        CLLocation* controlPointB =
        [[CLLocation alloc] initWithLatitude:controlPointBLat longitude:controlPointBLong];
        [pathPoints addObject:controlPointB];
    }else{
        [pathPoints addObject:controlPointA];
    }
    [pathPoints addObject:nextLocation];
}

Now you can use this pathArray to build your bezierPaths with a simple for loop: 现在,您可以使用此pathArray通过简单的for循环构建bezierPaths:

for(NSInteger index=0;index<pathPoints.count-3;index+=4)
{
   CLLocation *start = [pathPoints objectAtIndex:index];
   CLLocation *controlPointA = [pathPoints objectAtIndex:index+1];
   CLLocation *controlPointB = [pathPoints objectAtIndex:index+2];
   CLLocation *end = [pathPoints objectAtIndex:index+3];

   //BEZIER CURVE HERE
}

You can either use a UIBezierPath and use the CGPath property to create your MKMapOverlayRenderer, or you can directly use a CGPath. 您可以使用UIBezierPath并使用CGPath属性来创建MKMapOverlayRenderer,也可以直接使用CGPath。 Later adding the path to your map is described here: MKMapOverlayRenderer 稍后将添加路径到地图: MKMapOverlayRenderer

Createing a UIBezierPath is described here: UIBezierPath 此处描述了创建UIBezierPath: UIBezierPath

You can take an array of NSValues because you can't add a CGPoint directly into an Array 您可以使用NSValues数组,因为您无法将CGPoint直接添加到数组中

This is how we do it 这是我们的做法

NSMutableArray *arrayOfPoints = [[NSMutableArray alloc] init];
[arrayOfPoints addObject:[NSValue valueWithCGPoint:CGPointMake(10, 20)]];
// here point is the cgpoint you want to add 

Now you have your points added to an array what you can do is traverse every object and add them to your UIBezierPath 现在,您已将点添加到数组中,您可以执行的操作是遍历每个对象并将它们添加到UIBezierPath

  NSInteger  i = 0;
   UIBezierPath _path1 = [UIBezierPath bezierPath];

for(NSValue *val in arrayOfPoints){
    if (i==0){
        [_path1 moveToPoint:[val CGPointValue]];
    }
    else{
        [_path1 addLineToPoint:[val CGPointValue]];
    }
    i++;
}

I hope this helps and solve your problem 我希望这有助于解决您的问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM