2012-07-29 31 views
22

Khi hiển thị chỉ đường trên Maps.app tích hợp trên iPhone, bạn có thể "chọn" một trong 3 lựa chọn thay thế tuyến đường thường được hiển thị bằng cách nhấn vào nó. Tôi không thể sao chép chức năng này và kiểm tra xem một vòi nước nằm trong một MKPolyline nhất định.Làm thế nào để phát hiện vòi trên MKPolylines/Lớp phủ như Maps.app?

Hiện nay tôi phát hiện vòi trên MapView như thế này:

// 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]; 

tôi xử lý các vòi như sau:

- (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]]; 
      } 
     } 
    } 
} 

này làm việc như mong đợi, bây giờ tôi cần phải kiểm tra xem vòi nước nằm trong lớp phủ MKPolyline đã cho (không nghiêm ngặt, tôi người dùng chạm vào một nơi nào đó gần polyline này sẽ được xử lý như một lần truy cập).

Cách hay để làm điều này là gì?

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

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

Cảm ơn!

Trả lời

44

Câu hỏi khá cũ, nhưng câu trả lời của tôi có thể hữu ích cho những người khác đang tìm kiếm giải pháp cho vấn đề này.

Mã này phát hiện chạm trên nhiều dòng với khoảng cách tối đa là 22 pixel ở mọi mức thu phóng. Chỉ cần trỏ của bạn UITapGestureRecognizer-handleTap:

/** 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

giải pháp tuyệt vời, làm việc tốt :), cảm ơn – polo987

+0

Đây là một giải pháp tốt. Một câu hỏi, chính xác những gì đang được tính toán ở đây? đôi u = ((pt.x - ptA.x) * xDelta + (pt.y - ptA.y) * yDelta)/(xDelta * xDelta + yDelta * yDelta); ...Tôi bị mất từ ​​đó, bạn có thể thêm một số ý kiến ​​để giải thích những gì đang được tính toán từ đó và dưới đây? – Bocaxica

+1

@ Bocaxica phần đó không phải là mã của tôi. Xin vui lòng tham khảo http://paulbourke.net/geometry/pointlineplane/ – Jensemann

1

Các giải pháp đề xuất dưới đây bởi Jensemann đang làm việc tuyệt vời. Xem mã dưới đây được điều chỉnh cho Swift 2, được thử nghiệm thành công trên iOS 8 và 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

Bạn có thể tham khảo câu trả lời của tôi có thể giúp bạn tìm giải pháp mong muốn.

Tôi đã thêm cử chỉ trên MKMapView của mình.

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

Đây là cách tôi xử lý cử chỉ của tôi và tìm hiểu xem nhấn có ở chế độ xem Lớp phủ hay không.

- (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; 
        } 

       } 

     } 

     } 
    } 

Cảm ơn. Điều này có thể giúp bạn hy vọng.

0

Cập nhật cho Swift 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

Làm cách nào để thêm trình nhận dạng cử chỉ vào chế độ xem bản đồ cho mục này? – thexande

6

@Jensemanns câu trả lời trong Swift 4, mà bằng cách này là giải pháp duy nhất mà tôi thấy rằng làm việc cho tôi để phát hiện các nhấp chuột vào một 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" thực sự trong mã này là hàm point -> line. Tôi rất vui khi tìm thấy nó và nó hoạt động rất tốt (nhanh 4, iOS 11). Cảm ơn tất cả mọi người, đặc biệt là @Jensemann. Đây là việc tái cấu trúc của tôi về nó:

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) 
    } 
} 
Các vấn đề liên quan