2012-07-29 91 views
22

當在iPhone上的內置Maps.app上顯示方向時,您可以通過點擊它來「選擇」通常顯示的3種路線替代方案之一。我不想複製這個功能,並檢查一個水龍頭是否在給定的MKPolyline中。如何檢測MKPolylines /覆蓋像Maps.app上的水龍頭?

目前,我發現水龍頭上的MapView這樣的:

// Add Gesture Recognizer to MapView to detect taps 
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleMapTap:)]; 

// we require all gesture recognizer except other single-tap gesture recognizers to fail 
for (UIGestureRecognizer *gesture in self.gestureRecognizers) { 
    if ([gesture isKindOfClass:[UITapGestureRecognizer class]]) { 
     UITapGestureRecognizer *systemTap = (UITapGestureRecognizer *)gesture; 

     if (systemTap.numberOfTapsRequired > 1) { 
      [tap requireGestureRecognizerToFail:systemTap]; 
     } 
    } else { 
     [tap requireGestureRecognizerToFail:gesture]; 
    } 
} 

[self addGestureRecognizer:tap]; 

我把手水龍頭如下:

- (void)handleMapTap:(UITapGestureRecognizer *)tap { 
    if ((tap.state & UIGestureRecognizerStateRecognized) == UIGestureRecognizerStateRecognized) { 
     // Check if the overlay got tapped 
     if (overlayView != nil) { 
      // Get view frame rect in the mapView's coordinate system 
      CGRect viewFrameInMapView = [overlayView.superview convertRect:overlayView.frame toView:self]; 
      // Get touch point in the mapView's coordinate system 
      CGPoint point = [tap locationInView:self]; 

      // Check if the touch is within the view bounds 
      if (CGRectContainsPoint(viewFrameInMapView, point)) { 
       [overlayView handleTapAtPoint:[tap locationInView:self.directionsOverlayView]]; 
      } 
     } 
    } 
} 

可正常工作,現在我需要檢查,如果水龍頭在於內給定的MKPolyline overlayView(不嚴格,我用戶點擊折線附近的某個地方,這應該作爲一個命中來處理)。

這樣做的好方法是什麼?

- (void)handleTapAtPoint:(CGPoint)point { 
    MKPolyline *polyline = self.polyline; 

    // TODO: detect if point lies withing polyline with some margin 
} 

謝謝!

回答

44

這個問題相當古老,但我的答案可能對其他尋找這個問題的解決方案的人有用。

此代碼檢測poly線上的觸摸,每個縮放級別的最大距離爲22像素。只需將您的UITapGestureRecognizerhandleTap

/** Returns the distance of |pt| to |poly| in meters 
* 
* from http://paulbourke.net/geometry/pointlineplane/DistancePoint.java 
* 
*/ 
- (double)distanceOfPoint:(MKMapPoint)pt toPoly:(MKPolyline *)poly 
{ 
    double distance = MAXFLOAT; 
    for (int n = 0; n < poly.pointCount - 1; n++) { 

     MKMapPoint ptA = poly.points[n]; 
     MKMapPoint ptB = poly.points[n + 1]; 

     double xDelta = ptB.x - ptA.x; 
     double yDelta = ptB.y - ptA.y; 

     if (xDelta == 0.0 && yDelta == 0.0) { 

      // Points must not be equal 
      continue; 
     } 

     double u = ((pt.x - ptA.x) * xDelta + (pt.y - ptA.y) * yDelta)/(xDelta * xDelta + yDelta * yDelta); 
     MKMapPoint ptClosest; 
     if (u < 0.0) { 

      ptClosest = ptA; 
     } 
     else if (u > 1.0) { 

      ptClosest = ptB; 
     } 
     else { 

      ptClosest = MKMapPointMake(ptA.x + u * xDelta, ptA.y + u * yDelta); 
     } 

     distance = MIN(distance, MKMetersBetweenMapPoints(ptClosest, pt)); 
    } 

    return distance; 
} 


/** Converts |px| to meters at location |pt| */ 
- (double)metersFromPixel:(NSUInteger)px atPoint:(CGPoint)pt 
{ 
    CGPoint ptB = CGPointMake(pt.x + px, pt.y); 

    CLLocationCoordinate2D coordA = [mapView convertPoint:pt toCoordinateFromView:mapView]; 
    CLLocationCoordinate2D coordB = [mapView convertPoint:ptB toCoordinateFromView:mapView]; 

    return MKMetersBetweenMapPoints(MKMapPointForCoordinate(coordA), MKMapPointForCoordinate(coordB)); 
} 


#define MAX_DISTANCE_PX 22.0f 
- (void)handleTap:(UITapGestureRecognizer *)tap 
{ 
    if ((tap.state & UIGestureRecognizerStateRecognized) == UIGestureRecognizerStateRecognized) { 

     // Get map coordinate from touch point 
     CGPoint touchPt = [tap locationInView:mapView]; 
     CLLocationCoordinate2D coord = [mapView convertPoint:touchPt toCoordinateFromView:mapView]; 

     double maxMeters = [self metersFromPixel:MAX_DISTANCE_PX atPoint:touchPt]; 

     float nearestDistance = MAXFLOAT; 
     MKPolyline *nearestPoly = nil; 

     // for every overlay ... 
     for (id <MKOverlay> overlay in mapView.overlays) { 

      // .. if MKPolyline ... 
      if ([overlay isKindOfClass:[MKPolyline class]]) { 

       // ... get the distance ... 
       float distance = [self distanceOfPoint:MKMapPointForCoordinate(coord) 
               toPoly:overlay]; 

       // ... and find the nearest one 
       if (distance < nearestDistance) { 

        nearestDistance = distance; 
        nearestPoly = overlay; 
       } 
      } 
     } 

     if (nearestDistance <= maxMeters) { 

      NSLog(@"Touched poly: %@\n" 
        " distance: %f", nearestPoly, nearestDistance); 
     } 
    } 
} 
+0

偉大的解決方案,工作正常:),謝謝 – polo987 2014-08-22 13:12:56

+0

這是一個很好的解決方案。一個問題,這裏究竟計算了什麼? double u =((pt.x - ptA.x)* xDelta +(pt.y - ptA.y)* yDelta)/(xDelta * xDelta + yDelta * yDelta); ...我有種從那裏迷路,你能否添加一些評論來解釋從哪裏和下面計算出來的? – Bocaxica 2017-01-08 10:49:07

+1

@Bocaxica那部分不是我的代碼。請參考http://paulbourke.net/geometry/pointlineplane/ – Jensemann 2017-01-08 12:37:40

1

通過Jensemann下面提出的解決方案是偉大的工作。請參閱下面適用於Swift 2的代碼,在IOS 8和9(XCode 7.1)上成功測試。

func didTapMap(gestureRecognizer: UIGestureRecognizer) { 
    tapPoint = gestureRecognizer.locationInView(mapView) 
    NSLog("tapPoint = %f,%f",tapPoint.x, tapPoint.y) 
    //convert screen CGPoint tapPoint to CLLocationCoordinate2D... 
    let tapCoordinate = mapView.convertPoint(tapPoint, toCoordinateFromView: mapView) 
    let tapMapPoint = MKMapPointForCoordinate(tapCoordinate) 
    print("tap coordinates = \(tapCoordinate)") 
    print("tap map point = \(tapMapPoint)") 

    // Now we test to see if one of the overlay MKPolyline paths were tapped 
    var nearestDistance = Double(MAXFLOAT) 
    let minDistance = 2000  // in meters, adjust as needed 
    var nearestPoly = MKPolyline() 
    // arrayPolyline below is an array of MKPolyline overlaid on the mapView 
    for poly in arrayPolyline {     
     // ... get the distance ... 
     let distance = distanceOfPoint(tapMapPoint, poly: poly) 
     print("distance = \(distance)") 
     // ... and find the nearest one 
     if (distance < nearestDistance) { 
      nearestDistance = distance 
      nearestPoly = poly 
     } 
    } 
    if (nearestDistance <= minDistance) { 
     NSLog("Touched poly: %@\n distance: %f", nearestPoly, nearestDistance); 
    } 
} 


func distanceOfPoint(pt: MKMapPoint, poly: MKPolyline) -> Double { 
    var distance: Double = Double(MAXFLOAT) 
    var linePoints: [MKMapPoint] = [] 
    var polyPoints = UnsafeMutablePointer<MKMapPoint>.alloc(poly.pointCount) 
    for point in UnsafeBufferPointer(start: poly.points(), count: poly.pointCount) { 
     linePoints.append(point) 
     print("point: \(point.x),\(point.y)") 
    } 
    for n in 0...linePoints.count - 2 { 
     let ptA = linePoints[n] 
     let ptB = linePoints[n+1] 
     let xDelta = ptB.x - ptA.x 
     let yDelta = ptB.y - ptA.y 
     if (xDelta == 0.0 && yDelta == 0.0) { 
      // Points must not be equal 
      continue 
     } 
     let u: Double = ((pt.x - ptA.x) * xDelta + (pt.y - ptA.y) * yDelta)/(xDelta * xDelta + yDelta * yDelta) 
     var ptClosest = MKMapPoint() 
     if (u < 0.0) { 
      ptClosest = ptA 
     } else if (u > 1.0) { 
      ptClosest = ptB 
     } else { 
      ptClosest = MKMapPointMake(ptA.x + u * xDelta, ptA.y + u * yDelta); 
     } 
     distance = min(distance, MKMetersBetweenMapPoints(ptClosest, pt)) 
    } 
    return distance 
} 
1

您可以參考我的答案可能會幫助您找到所需的解決方案。

我在我的MKMapView中添加了手勢。

[mapV addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(mapTapped:)]]; 

這是我如何處理我的手勢,並找出水龍頭是否在覆蓋視圖或不。

- (void)mapTapped:(UITapGestureRecognizer *)recognizer 
    { 

     MKMapView *mapView = (MKMapView *)recognizer.view; 

     CGPoint tapPoint = [recognizer locationInView:mapView]; 
     NSLog(@"tapPoint = %f,%f",tapPoint.x, tapPoint.y); 

     //convert screen CGPoint tapPoint to CLLocationCoordinate2D... 
     CLLocationCoordinate2D tapCoordinate = [mapView convertPoint:tapPoint toCoordinateFromView:mapView]; 

     //convert CLLocationCoordinate2D tapCoordinate to MKMapPoint... 
     MKMapPoint point = MKMapPointForCoordinate(tapCoordinate); 

     if (mapView.overlays.count > 0) { 
       for (id<MKOverlay> overlay in mapView.overlays) 
       { 

        if ([overlay isKindOfClass:[MKCircle class]]) 
        { 
         MKCircle *circle = overlay; 
         MKCircleRenderer *circleRenderer = (MKCircleRenderer *)[mapView rendererForOverlay:circle]; 

         //convert MKMapPoint tapMapPoint to point in renderer's context... 
         CGPoint datpoint = [circleRenderer pointForMapPoint:point]; 
         [circleRenderer invalidatePath]; 


         if (CGPathContainsPoint(circleRenderer.path, nil, datpoint, false)){ 

          NSLog(@"tapped on overlay"); 
          break; 
        } 

       } 

     } 

     } 
    } 

謝謝。 這可能會幫助你。

0

更新了斯威夫特3

func isTappedOnPolygon(with tapGesture:UITapGestureRecognizer, on mapView: MKMapView) -> Bool { 
    let tappedMapView = tapGesture.view 
    let tappedPoint = tapGesture.location(in: tappedMapView) 
    let tappedCoordinates = mapView.convert(tappedPoint, toCoordinateFrom: tappedMapView) 
    let point:MKMapPoint = MKMapPointForCoordinate(tappedCoordinates) 

    let overlays = mapView.overlays.filter { o in 
     o is MKPolygon 
    } 

    for overlay in overlays { 
     let polygonRenderer = MKPolygonRenderer(overlay: overlay) 
     let datPoint = polygonRenderer.point(for: point) 
     polygonRenderer.invalidatePath() 

     return polygonRenderer.path.contains(datPoint) 
    } 
    return false 
} 
+0

如何將手勢識別器添加到mapview中? – thexande 2017-02-27 02:59:15

6

@Jensemanns答案斯威夫特4,它的方式是,我發現,工作對我來說,以檢測在MKPolyline點擊的唯一解決方案:

let map = MKMapView() 
let mapTap = UITapGestureRecognizer(target: self, action: #selector(mapTapped(_:))) 
map.addGestureRecognizer(mapTap) 

func mapTapped(_ tap: UITapGestureRecognizer) { 
    if tap.state == .recognized && tap.state == .recognized { 
     // Get map coordinate from touch point 
     let touchPt: CGPoint = tap.location(in: map) 
     let coord: CLLocationCoordinate2D = map.convert(touchPt, toCoordinateFrom: map) 
     let maxMeters: Double = meters(fromPixel: 22, at: touchPt) 
     var nearestDistance: Float = MAXFLOAT 
     var nearestPoly: MKPolyline? = nil 
     // for every overlay ... 
     for overlay: MKOverlay in map.overlays { 
      // .. if MKPolyline ... 
      if (overlay is MKPolyline) { 
       // ... get the distance ... 
       let distance: Float = Float(distanceOf(pt: MKMapPointForCoordinate(coord), toPoly: overlay as! MKPolyline)) 
       // ... and find the nearest one 
       if distance < nearestDistance { 
        nearestDistance = distance 
        nearestPoly = overlay as! MKPolyline 
       } 

      } 
     } 

     if Double(nearestDistance) <= maxMeters { 
      print("Touched poly: \(nearestPoly) distance: \(nearestDistance)") 

     } 
    } 
} 

func distanceOf(pt: MKMapPoint, toPoly poly: MKPolyline) -> Double { 
    var distance: Double = Double(MAXFLOAT) 
    for n in 0..<poly.pointCount - 1 { 
     let ptA = poly.points()[n] 
     let ptB = poly.points()[n + 1] 
     let xDelta: Double = ptB.x - ptA.x 
     let yDelta: Double = ptB.y - ptA.y 
     if xDelta == 0.0 && yDelta == 0.0 { 
      // Points must not be equal 
      continue 
     } 
     let u: Double = ((pt.x - ptA.x) * xDelta + (pt.y - ptA.y) * yDelta)/(xDelta * xDelta + yDelta * yDelta) 
     var ptClosest: MKMapPoint 
     if u < 0.0 { 
      ptClosest = ptA 
     } 
     else if u > 1.0 { 
      ptClosest = ptB 
     } 
     else { 
      ptClosest = MKMapPointMake(ptA.x + u * xDelta, ptA.y + u * yDelta) 
     } 

     distance = min(distance, MKMetersBetweenMapPoints(ptClosest, pt)) 
    } 
    return distance 
} 

func meters(fromPixel px: Int, at pt: CGPoint) -> Double { 
    let ptB = CGPoint(x: pt.x + CGFloat(px), y: pt.y) 
    let coordA: CLLocationCoordinate2D = map.convert(pt, toCoordinateFrom: map) 
    let coordB: CLLocationCoordinate2D = map.convert(ptB, toCoordinateFrom: map) 
    return MKMetersBetweenMapPoints(MKMapPointForCoordinate(coordA), MKMapPointForCoordinate(coordB)) 
} 
0

此代碼中的真正「cookie」是點 - >線距函數。我很高興找到它,它運行得很好(iOS 4,迅速4)。感謝大家,特別是@Jensemann。這是我的重構它:

public extension MKPolyline { 

    // Return the point on the polyline that is the closest to the given point 
    // along with the distance between that closest point and the given point. 
    // 
    // Thanks to: 
    // http://paulbourke.net/geometry/pointlineplane/ 
    // https://stackoverflow.com/questions/11713788/how-to-detect-taps-on-mkpolylines-overlays-like-maps-app 

    public func closestPoint(to: MKMapPoint) -> (point: MKMapPoint, distance: CLLocationDistance) { 

     var closestPoint = MKMapPoint() 
     var distanceTo = CLLocationDistance.infinity 

     let points = self.points() 
     for i in 0 ..< pointCount - 1 { 
      let endPointA = points[i] 
      let endPointB = points[i + 1] 

      let deltaX: Double = endPointB.x - endPointA.x 
      let deltaY: Double = endPointB.y - endPointA.y 
      if deltaX == 0.0 && deltaY == 0.0 { continue } // Points must not be equal 

      let u: Double = ((to.x - endPointA.x) * deltaX + (to.y - endPointA.y) * deltaY)/(deltaX * deltaX + deltaY * deltaY) // The magic sauce. See the Paul Bourke link above. 

      let closest: MKMapPoint 
      if u < 0.0 { closest = endPointA } 
      else if u > 1.0 { closest = endPointB } 
      else { closest = MKMapPointMake(endPointA.x + u * deltaX, endPointA.y + u * deltaY) } 

      let distance = MKMetersBetweenMapPoints(closest, to) 
      if distance < distanceTo { 
       closestPoint = closest 
       distanceTo = distance 
      } 
     } 

     return (closestPoint, distanceTo) 
    } 
} 
相關問題