从特定点找到最近的点

时间:2015-10-16 14:24:37

标签: ios google-maps

我正在使用谷歌地图iOS在建筑群周围设置地理围栏。我在复合体周围创建了一条折线,如果用户点击折线外,它会将标记移动到折线上最近的点,否则它只会放置标记。使用this method似乎相对较好。

但是我注意到这个方法似乎只在有问题的点垂直于线上的点时起作用,否则会产生奇怪的结果。我已经发布了我的代码和一些截图。

-(CLLocationCoordinate2D) findClosestPointWithinFence:(CLLocationCoordinate2D) pointToTest {
    CLLocationDistance smallestDistance = 0;
    CLLocationCoordinate2D closestPoint = pointToTest;

    for(int i = 0; i < [geoFencePoints count] - 1; i++) {
        CGPoint point = [[geoFencePoints objectAtIndex:i] CGPointValue];
        CGPoint point2 = [[geoFencePoints objectAtIndex:i + 1] CGPointValue];
        CLLocationCoordinate2D locationA = CLLocationCoordinate2DMake(point.x, point.y);
        CLLocationCoordinate2D locationB = CLLocationCoordinate2DMake(point2.x, point2.y);
        CLLocationCoordinate2D myLoc = [self findClosestPointOnLine:locationA secondPoint:locationB fromPoint:pointToTest];

        if(GMSGeometryIsLocationOnPath(myLoc, dealershipParameters.path, YES)) {
            if(smallestDistance == 0) {
                smallestDistance = GMSGeometryDistance(myLoc, pointToTest);
                closestPoint = myLoc;
            } else {
                if(smallestDistance > GMSGeometryDistance(myLoc, pointToTest)) {
                    smallestDistance = GMSGeometryDistance(myLoc, pointToTest);
                    closestPoint = myLoc;
                }
            }
        }
    }
    return closestPoint;
}

-(CLLocationCoordinate2D) findClosestPointOnLine:(CLLocationCoordinate2D)locationA secondPoint:(CLLocationCoordinate2D)locationB fromPoint:(CLLocationCoordinate2D) pointToTest {
    CGPoint aToP = CGPointMake(pointToTest.latitude - locationA.latitude, pointToTest.longitude - locationA.longitude);
    CGPoint aToB = CGPointMake(locationB.latitude - locationA.latitude, locationB.longitude - locationA.longitude);

    float atb2 = (aToB.x * aToB.x) + (aToB.y * aToB.y);

    float atp_dot_atb = (aToP.x  * aToB.x) + (aToP.y * aToB.y);

    float t = atp_dot_atb / atb2;

    CLLocationCoordinate2D myLoc = CLLocationCoordinate2DMake(locationA.latitude + aToB.x * t, locationA.longitude + aToB.y * t);
    return myLoc;
}

-(BOOL)testIfInsideGeoFence:(CLLocationCoordinate2D) pointToTest {
    return GMSGeometryContainsLocation(pointToTest, dealershipParameters.path, YES) || GMSGeometryIsLocationOnPath(pointToTest, dealershipParameters.path, YES);
}

在第一个屏幕截图下方显示标记成功找到最近点,蓝线上的标记是我最初点击的位置,蓝线上的标记是它找到的点。第二个显示标记未能找到最近的点。屏幕上的标记是我最初点击的位置,因为它无法找到合适的解决方案,因此无法放置第二个标记。

Screenshot 1 Screenshot 2

2 个答案:

答案 0 :(得分:1)

我遇到了类似的问题。我认为发生的事情是你将线段视为一条线。由于该段不会延伸到与该点垂直的点,因此该段上的最近点将是其中一个端点,而不是该段的扩展。

这是我正在使用的方法。它接受段的端点并返回一个结构,该结构包含段上的最近点和距离给定点的距离。关键的区别是检查解决方案是否在段上的if-else语句。您可能需要为您的目的重做一些事情。

另外需要注意的是,我在MKMapPoints而不是CLLocationCoordinate2D个对象上执行数学计算的结果更为准确。我认为这与地球周围或某些无意义有关。

+ (struct TGShortestDistanceAndNearestCoordinate)distanceFromPoint:(CLLocationCoordinate2D)p
                   toLineSegmentBetween:(CLLocationCoordinate2D)l1
                                    and:(CLLocationCoordinate2D)l2 {
    return [[self class] distanceFromMapPoint:MKMapPointForCoordinate(p)
                         toLineSegmentBetween:MKMapPointForCoordinate(l1)
                                          and:MKMapPointForCoordinate(l2)];
}

+ (struct TGShortestDistanceAndNearestCoordinate)distanceFromMapPoint:(MKMapPoint)p
                      toLineSegmentBetween:(MKMapPoint)l1
                                       and:(MKMapPoint)l2 {
    double A = p.x - l1.x;
    double B = p.y - l1.y;
    double C = l2.x - l1.x;
    double D = l2.y - l1.y;

    double dot = A * C + B * D;
    double len_sq = C * C + D * D;
    double param = dot / len_sq;

    double xx, yy;

    if (param < 0 || (l1.x == l2.x && l1.y == l2.y)) {
        xx = l1.x;
        yy = l1.y;
    }
    else if (param > 1) {
        xx = l2.x;
        yy = l2.y;
    }
    else {
        xx = l1.x + param * C;
        yy = l1.y + param * D;
    }

    struct TGShortestDistanceAndNearestCoordinate result;
    MKMapPoint nearestPoint = MKMapPointMake(xx, yy);
    result.shortestDistance = MKMetersBetweenMapPoints(p, nearestPoint);
    result.nearestCoordinate = MKCoordinateForMapPoint(nearestPoint);

    return result;
}

答案 1 :(得分:0)

一个非常优雅的解决方案。但是我不确定“ param <0 ...”行中的测试。如果段是垂直的,则l1.x == l2.x;如果段是水平的,则l1.y ==l2.y。那么如何才能使这种结合成为真实呢? (当l1,l2相同时除外)