检测一个点是否在 MKPolygon 覆盖层内部。

21

我希望能够判断点击是否在MKPolygon内。

我有一个MKPolygon:

CLLocationCoordinate2D  points[4];

points[0] = CLLocationCoordinate2DMake(41.000512, -109.050116);
points[1] = CLLocationCoordinate2DMake(41.002371, -102.052066);
points[2] = CLLocationCoordinate2DMake(36.993076, -102.041981);
points[3] = CLLocationCoordinate2DMake(36.99892, -109.045267);

MKPolygon* poly = [MKPolygon polygonWithCoordinates:points count:4];

[self.mapView addOverlay:poly];  

//create UIGestureRecognizer to detect a tap
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(foundTap:)];
tapRecognizer.numberOfTapsRequired = 1;
tapRecognizer.numberOfTouchesRequired = 1;
[self.mapView addGestureRecognizer:tapRecognizer];

这只是科罗拉多州的基本概述。

我已经设置了水龙头到经纬度的转换:

-(IBAction)foundTap:(UITapGestureRecognizer *)recognizer
{
    CGPoint point = [recognizer locationInView:self.mapView];

    CLLocationCoordinate2D tapPoint = [self.mapView convertPoint:point toCoordinateFromView:self.view];
}

但我不确定如何判断我的触摸点是否在MKPolygon内部。似乎没有一种方法可以进行此检查,所以我猜想需要将MKPolygon转换为CGRect并使用CGRectContainsPoint。

MKPolygon具有.points属性,但我似乎无法将它们取出。

有什么建议吗?

编辑:

以下两个解决方案适用于iOS 6或更低版本,但在iOS 7中会出现问题。在iOS 7中,polygon.path属性始终返回NULL。Anna女士很友善地提供了另一个SO问题中的解决方案。 它涉及从多边形点创建自己的路径,以传递到CGPathContainsPoint()。

我的多边形图像:

enter image description here

6个回答

12

我创建了这个MKPolygon类别,以便任何人都可以使用它。看起来运行良好。您必须考虑内部多边形(即多边形中的孔):

@interface MKPolygon (PointInPolygon)
  -(BOOL) pointInPolygon:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView;
@end

@implementation MKPolygon (PointInPolygon)

-(BOOL) pointInPolygon:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView {
    MKMapPoint mapPoint = MKMapPointForCoordinate(point);
    MKPolygonView * polygonView = (MKPolygonView*)[mapView viewForOverlay:self];
    CGPoint polygonViewPoint = [polygonView pointForMapPoint:mapPoint];
    return CGPathContainsPoint(polygonView.path, NULL, polygonViewPoint, NO) && 
        ![self pointInInteriorPolygons:point mapView:mapView];
}

-(BOOL) pointInInteriorPolygons:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView {
    return [self pointInInteriorPolygonIndex:0 point:point mapView:mapView];
}

-(BOOL) pointInInteriorPolygonIndex:(int) index point:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView {
    if(index >= [self.interiorPolygons count])
        return NO;
    return [[self.interiorPolygons objectAtIndex:index] pointInPolygon:point mapView:mapView] || [self pointInInteriorPolygonIndex:(index+1) point:point mapView:mapView];
}

@end

我刚刚在我的简单多边形(没有洞)上实现了类别和工作。你说需要考虑到这一点吗?这是否意味着它无法检测出点击是否在洞内? - Padin215
好的,这段代码在iOS7上出了问题。与 CGPathContainsPoint(polygonView.path, NULL, polygonViewPoint, NO) 有关,现在始终返回 FALSE - Padin215

9

您的foundTap方法:

-(IBAction)foundTap:(UITapGestureRecognizer *)recognizer
{
    CGPoint point = [recognizer locationInView:self.mapView];

    CLLocationCoordinate2D tapPoint = [self.mapView convertPoint:point toCoordinateFromView:self.view];

    [self pointInsideOverlay:tapPoint];

    if (isInside) 
     {
       ....
     }
}

以下是检查点是否在覆盖物内的方法,可从上一个方法中进行调用:
-(void)pointInsideOverlay:(CLLocationCoordinate2D )tapPoint 
{
    isInside = FALSE; 

    MKPolygonView *polygonView = (MKPolygonView *)[mapView viewForOverlay:polygonOverlay];

    MKMapPoint mapPoint = MKMapPointForCoordinate(tapPoint);

    CGPoint polygonViewPoint = [polygonView pointForMapPoint:mapPoint];

    BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polygonView.path, NULL, polygonViewPoint, NO);

        if ( !mapCoordinateIsInPolygon )

            //we are finding points that are inside the overlay
        {
            isInside = TRUE;
        }
}

这是一种基本检查的好方法,可以查看触点是否在多边形的矩形内。但不准确地检测多边形绘制区域内的触点。 - Padin215
2
@Log139 你为什么这么说?polygonViewMKPolygonViewpath是它的CGPathRef,而不是它的边界矩形,CGPathContainsPoint可以进行正确的路径/点包含测试,对吧?我在这里漏掉了什么?如果你使用[polygonView pointInside:polygonViewPoint withEvent:nil],你可能是对的。但是如果你获取实际的path并调用CGPathContainsPoint,我认为你没问题。 - Rob
1
好的,我撤回之前的评论,这确实有效。嗯,在iOS6或更早版本中有效,在iOS7中会出现问题。 - Padin215

8

感谢@Steve Stomp,这里是更新的Swift 4.2版本。

extension MKPolygon {
    func contain(coor: CLLocationCoordinate2D) -> Bool {
        let polygonRenderer = MKPolygonRenderer(polygon: self)
        let currentMapPoint: MKMapPoint = MKMapPoint(coor)
        let polygonViewPoint: CGPoint = polygonRenderer.point(for: currentMapPoint)
        if polygonRenderer.path == nil {
            return false
        }else{
            return polygonRenderer.path.contains(polygonViewPoint)
        }
    }
}

7

我从一个带有字符串数据的 xml 文件中获取 MKPolygon 数据点。我将数据字符串解析为一组点,并使用http://alienryderflex.com/polygon/ 中提供的方法。

这对我很有效。

-(BOOL)isPoint:(CLLocationCoordinate2D)findLocation inPloygon:(NSArray*)polygon{

    NSMutableArray *tempPolygon=[NSMutableArray arrayWithArray:polygon];
    int   i, j=(int)tempPolygon.count-1 ;
    bool  oddNodes=NO;
    double x=findLocation.latitude;
    double y=findLocation.longitude;

    for (i=0; i<tempPolygon.count; i++) {
        NSString*coordString=[tempPolygon objectAtIndex:i];
        NSArray*pointsOfCoordString=[coordString componentsSeparatedByString:@","];
        CLLocationCoordinate2D point=CLLocationCoordinate2DMake([[pointsOfCoordString objectAtIndex:1] doubleValue], [[pointsOfCoordString objectAtIndex:0] doubleValue]);
        NSString*nextCoordString=[tempPolygon objectAtIndex:j];
        NSArray*nextPointsOfCoordString=[nextCoordString componentsSeparatedByString:@","];
        CLLocationCoordinate2D nextPoint=CLLocationCoordinate2DMake([[nextPointsOfCoordString objectAtIndex:1] doubleValue], [[nextPointsOfCoordString objectAtIndex:0] doubleValue]);


        if ((point.longitude<y && nextPoint.longitude>=y)
            ||  (nextPoint.longitude<y && point.longitude>=y)) {
            if (point.latitude+(y-point.longitude)/(nextPoint.longitude-point.longitude)*(nextPoint.latitude-point.latitude)<x) {
                oddNodes=!oddNodes; }}
        j=i; }


    return oddNodes;

}

我的多边形(NSArray)对象是以字符串形式表示的,例如 @"-89.860021,44.944266,0"


您能否详细阐述一下您的答案,并对您提供的解决方案进行更多描述? - abarisone
1
该方法检查您提供的位置是否位于由数据点(数据点是地理位置数组)构成的多边形内。完整算法请参阅http://alienryderflex.com/polygon/。 - Harsh Jaiswal
对我来说很有效,但我必须更改多边形组件的顺序:@"经度,纬度,0" 谢谢! - Ricardo Ruiz Romero
完美解决方案 - Ahmed Sahib

7
这个方法在 #Swift 4.2 版本中适用:
extension MKPolygon {
    func isCoordinateInsidePolyon(coordinate: CLLocationCoordinate2D) -> Bool {
        let polygonRenderer = MKPolygonRenderer(polygon: self)
        let currentMapPoint: MKMapPoint = MKMapPoint(coordinate)
        let polygonViewPoint: CGPoint = polygonRenderer.point(for: currentMapPoint)
        if polygonRenderer.path == nil {
            return false
        } else {
            return polygonRenderer.path.contains(polygonViewPoint)
        }
    }
}

1
这个方法可行,并且有一个好处,就是不像其他答案中描述的MKPolygonView方法那样需要MKMapView。 - oh7lzb

5
确定一个点是否在任意多边形内部并不容易,因此苹果公司没有将其作为MKPolygon的一部分提供。您可以访问这些点,从而可以迭代遍历这些边缘。
要确定一个点p是否在多边形s内部,请将每条边视为s中的定向线段。如果从p开始的射线沿任何固定方向(通常平行于X或Y轴)与该线段相交,则取该射线与该定向线段的叉积的Z分量的符号。如果Z分量> 0,则将1添加到计数器中。如果它<0,则减去1。在实现时的技巧是避免当边缘与射线几乎平行或当射线通过顶点时出现问题(它只需计算一次,而不是每个边缘都计算一次)。
当您对s中的所有边执行此操作时,您将计算出射线与多边形轮廓相交的次数,其中如果边缘从左到右,则添加,如果从右到左,则减去。如果结果总和为零,则在多边形外部。否则,在多边形内部。
有许多优化方法。其中之一是在进行更完整的测试之前进行快速包围盒测试。另一个是具有关于所有边缘的边界的数据结构,以轻松丢弃不与射线相交的边缘。
编辑:A X B(A与B的叉积)的Z分量如下:
a.x * b.y - a.y * b.x

由于你所关心的仅是符号,你可以进行检查。

a.x * b.y > a.y * b.x

1
好的,明白了。我正在处理中,只是在尝试找出如何检测光线是否与线段相交。 - Padin215
1
Adrian Bowyer的《程序员的几何》中有伪代码。但是你不需要通用情况:假设有一个水平射线。 - DRVic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接