2012-07-29 10 views
22

Quando si visualizzano le indicazioni sulla Maps.app integrata sull'iPhone, è possibile "selezionare" una delle 3 alternative di percorso normalmente visualizzate toccando su di essa. Non voglio replicare questa funzionalità e controllare se un rubinetto si trova all'interno di una determinata MKPolyline.Come rilevare i tocchi su MKPolylines/Overlay come Maps.app?

Attualmente mi rilevare rubinetti sul MapView come questo:

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

gestisco i rubinetti come segue:

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

Questo funziona come previsto, ora ho bisogno di controllare se il rubinetto si trova all'interno il dato overlayView di MKPolyline (non severo, l'utente tocca da qualche parte vicino alla polilinea che dovrebbe essere gestito come un colpo).

Qual è un buon modo per farlo?

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

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

Grazie!

risposta

44

La domanda è piuttosto vecchia, ma la mia risposta potrebbe essere utile ad altre persone che cercano una soluzione per questo problema.

Questo codice rileva i tocchi su linee poligonali con una distanza massima di 22 pixel in ogni livello di zoom. Basta puntare il 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

ottima soluzione, funzionante bene :), grazie – polo987

+0

Questa è una buona soluzione. Una domanda, cosa si calcola esattamente qui? double u = ((pt.x - ptA.x) * xDelta + (pt.y - ptA.y) * yDelta)/(xDelta * xDelta + yDelta * yDelta); ...Mi sono perso da lì, potresti aggiungere qualche commento per spiegare cosa viene calcolato da lì e da sotto? – Bocaxica

+1

@Bocaxica quella parte non è il mio codice. Si prega di fare riferimento a http://paulbourke.net/geometry/pointlineplane/ – Jensemann

1

La soluzione proposta di seguito per Jensemann sta lavorando molto. Vedi sotto codice adattato per Swift 2, testato con successo su IOS 8 e 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

È possibile fare riferimento alla mia risposta potrebbe essere utile per trovare la soluzione desiderata.

Ho aggiunto gesture sul mio MKMapView.

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

Questo è il modo in cui ho gestito il mio gesto e scoprire se il rubinetto è in visualizzazione Overlay oppure no.

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

       } 

     } 

     } 
    } 

Grazie. Questo potrebbe aiutarti, si spera.

0

aggiornato per 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

Come si aggiunge il riconoscimento dei gesti alla mappa per questo? – thexande

6

@Jensemanns risposta a Swift 4, che tra l'altro è stata l'unica soluzione che ho trovato che ha lavorato per me per rilevare clic su un 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

Il vero "cookie" in questo codice è la funzione point -> line distance. Ero così felice di trovarlo e ha funzionato benissimo (swift 4, iOS 11). Grazie a tutti, specialmente a @Jensemann. Ecco il mio refactoring:

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) 
    } 
} 
Problemi correlati