web-dev-qa-db-fra.com

Centrer MKMapView sur un point N-pixels sous la broche

Voulez centrer MKMapView sur un point N-pixels au-dessous d'un pin donné (qui peut être visible ou non dans le MapRect actuel).

J'ai essayé de résoudre ce problème en utilisant divers jeux avec -(CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view sans succès. 

Quelqu'un a-t-il emprunté cette voie (sans jeu de mots)?

enter image description here

33
eric

La technique la plus simple consiste à déplacer la carte vers le bas, par exemple à 40% de la valeur coordinate, en tirant parti de la span de la region de la MKMapView. Si vous n'avez pas besoin de pixels réels, mais que vous devez simplement le déplacer pour que le CLLocationCoordinate2D en question se trouve en haut de la carte (par exemple, à 10% du haut):

CLLocationCoordinate2D center = coordinate;
center.latitude -= self.mapView.region.span.latitudeDelta * 0.40;
[self.mapView setCenterCoordinate:center animated:YES];

Si vous souhaitez prendre en compte la rotation et la hauteur de la caméra, la technique ci-dessus peut ne pas être adéquate. Dans ce cas, vous pourriez:

  • Identifiez la position dans la vue vers laquelle vous souhaitez déplacer l'emplacement de l'utilisateur;

  • Convertissez cela en CLLocation;

  • Calculez la distance entre l'emplacement actuel de l'utilisateur et ce nouvel emplacement souhaité.

  • Déplacez la caméra de cette distance dans la direction de 180 ° par rapport à la direction actuelle de la caméra de la carte.

Par exemple. dans Swift 3, quelque chose comme:

var point = mapView.convert(mapView.centerCoordinate, toPointTo: view)
point.y -= offset
let coordinate = mapView.convert(point, toCoordinateFrom: view)
let offsetLocation = coordinate.location

let distance = mapView.centerCoordinate.location.distance(from: offsetLocation) / 1000.0

let camera = mapView.camera
let adjustedCenter = mapView.centerCoordinate.adjust(by: distance, at: camera.heading - 180.0)
camera.centerCoordinate = adjustedCenter

CLLocationCoordinate2D a la extension suivante:

extension CLLocationCoordinate2D {
    var location: CLLocation {
        return CLLocation(latitude: latitude, longitude: longitude)
    }

    private func radians(from degrees: CLLocationDegrees) -> Double {
        return degrees * .pi / 180.0
    }

    private func degrees(from radians: Double) -> CLLocationDegrees {
        return radians * 180.0 / .pi
    }

    func adjust(by distance: CLLocationDistance, at bearing: CLLocationDegrees) -> CLLocationCoordinate2D {
        let distanceRadians = distance / 6_371.0   // 6,371 = Earth's radius in km
        let bearingRadians = radians(from: bearing)
        let fromLatRadians = radians(from: latitude)
        let fromLonRadians = radians(from: longitude)

        let toLatRadians = asin( sin(fromLatRadians) * cos(distanceRadians)
            + cos(fromLatRadians) * sin(distanceRadians) * cos(bearingRadians) )

        var toLonRadians = fromLonRadians + atan2(sin(bearingRadians)
            * sin(distanceRadians) * cos(fromLatRadians), cos(distanceRadians)
                - sin(fromLatRadians) * sin(toLatRadians))

        // adjust toLonRadians to be in the range -180 to +180...
        toLonRadians = fmod((toLonRadians + 3.0 * .pi), (2.0 * .pi)) - .pi

        let result = CLLocationCoordinate2D(latitude: degrees(from: toLatRadians), longitude: degrees(from: toLonRadians))

        return result
    }
}

Ainsi, même si la caméra est inclinée et dans une direction autre que le nord, cela déplace la position de l'utilisateur (qui est centrée, où le réticule inférieur est) de 150 pixels (où se trouve le réticule supérieur), donnant quelque chose comme:

 enter image description here

Évidemment, vous devez être conscient des situations dégénérées (par exemple, vous vous trouvez à 1 km du pôle sud et vous essayez de déplacer la carte de 2 km au maximum; vous utilisez un angle de prise de vue si élevé que l'emplacement de l'écran souhaité est dépassé) horizon, etc.), mais pour des scénarios concrets et réalistes, une solution similaire à celle indiquée ci-dessus peut suffire. Évidemment, si vous ne laissez pas l’utilisateur changer la hauteur de la caméra, la réponse est encore plus simple.


Réponse originale: pour déplacer l'annotation n pixels

Si vous avez un CLLocationCoordinate2D, vous pouvez le convertir en un CGPoint, le déplacer de x pixels, puis le reconvertir en un CLLocationCoordinate2D:

- (void)moveCenterByOffset:(CGPoint)offset from:(CLLocationCoordinate2D)coordinate
{
    CGPoint point = [self.mapView convertCoordinate:coordinate toPointToView:self.mapView];
    point.x += offset.x;
    point.y += offset.y;
    CLLocationCoordinate2D center = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
    [self.mapView setCenterCoordinate:center animated:YES];
}

Vous pouvez appeler cela par:

[self moveCenterByOffset:CGPointMake(0, 100) from:coordinate];

Malheureusement, cela ne fonctionne que si la variable coordinate est visible avant de commencer. Vous devrez donc peut-être d'abord accéder aux coordonnées d'origine, puis ajuster le centre.

76
Rob

La seule façon de procéder de manière fiable consiste à utiliser les éléments suivants:

- (void)setVisibleMapRect:(MKMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animate

Pour ce faire, vous devez convertir la région de carte en MKMapRect en fonction d'une région de la carte sur laquelle vous souhaitez centrer. Utilisez évidemment le remplissage des bords pour le décalage en pixels. 

Voir ici pour cela: Convertir MKCoordinateRegion en MKMapRect

Commentaire: Je trouve assez étrange que ce soit la seule façon de le faire, étant donné que MKMapRect n'est pas quelque chose que l'on utilise normalement avec MKMapView - toutes les méthodes de conversion sont pour MKMapRegion. Mais bon, au moins ça marche. Testé dans mon propre projet.

7
n13

Pour Swift:

import MapKit

extension MKMapView {

func moveCenterByOffSet(offSet: CGPoint, coordinate: CLLocationCoordinate2D) {
    var point = self.convert(coordinate, toPointTo: self)

    point.x += offSet.x
    point.y += offSet.y

    let center = self.convert(point, toCoordinateFrom: self)
    self.setCenter(center, animated: true)
}

func centerCoordinateByOffSet(offSet: CGPoint) -> CLLocationCoordinate2D {
    var point = self.center

    point.x += offSet.x
    point.y += offSet.y

    return self.convert(point, toCoordinateFrom: self)
}
}
3
Steve Stomp

Une solution simple consiste à agrandir le cadre de la vue cartographique par rapport à la zone visible. Ensuite, placez votre épingle au centre de la vue de la carte et cachez toutes les zones indésirables derrière une autre vue ou en dehors des limites de l'écran.

Laissez-moi élaborer. Si je regarde votre capture d'écran, procédez comme suit:

La distance entre votre épingle et le bas est de 353 pixels. Alors, faites en sorte que vos vues de la carte encadrent deux fois la hauteur: 706 pixels. Votre capture d'écran a une hauteur de 411 pixels. Positionnez votre cadre à une origine de 706px - 411px = -293 pixel . Maintenant, centrez votre affichage de carte sur les coordonnées de la broche et vous avez terminé.

Mise à jour du 4 mars 2014:

J'ai créé un petit exemple d'application avec Xcode 5.0.2 pour en faire la démonstration: http://cl.ly/0e2v0u3G2q1d

2
Klaas

Swift 3 À JOUR

Mise à jour de la fonction avec Zoom

func zoomToPos() {

        let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta:  0.1)

        // Create a new MKMapRegion with the new span, using the center we want.
        let coordinate = moveCenterByOffset(offset: CGPoint(x: 0, y: 100), coordinate: (officeDetail?.coordinate)!)
        let region = MKCoordinateRegion(center: coordinate, span: span)

        mapView.setRegion(region, animated: true)


    }

    func moveCenterByOffset (offset: CGPoint, coordinate: CLLocationCoordinate2D) -> CLLocationCoordinate2D {
        var point = self.mapView.convert(coordinate, toPointTo: self.mapView)
        point.x += offset.x
        point.y += offset.y
        return self.mapView.convert(point, toCoordinateFrom: self.mapView)
    }
1
Mikel Sanchez

Comme alternative à la réponse acceptée, je suggère que votre instinct d’origine était correct. Vous pouvez travailler strictement dans l'espace de coordonnées en pixels des vues de carte pour obtenir des décalages et un positionnement final. Ensuite, en utilisant l’appel de conversion d’un lieu à l’autre, vous pouvez obtenir le lieu définitif et définir le centre des cartes.

Cela fonctionnera avec la caméra en rotation et respectera l’espace de l’écran. Dans mon cas, je devais centrer la carte sur une épingle, avec un décalage pour tenir compte du tiroir de la carte.

Voici les appels de conversion

func convert(_ coordinate: CLLocationCoordinate2D, toPointTo view: UIView?) -> CGPoint
func convert(_ point: CGPoint, toCoordinateFrom view: UIView?) -> CLLocationCoordinate2D

Et voici un exemple de Swift 4

//First get the position you want the pin to be (say 1/4 of the way up the screen)
let targetPoint = CGPoint(x: self.frame.width / 2.0, y: self.frame.height * CGFloat(0.25))

//Then get the center of the screen (this is used for calculating the offset as we are using setCenter to move the region
let centerPoint = CGPoint(x: self.frame.width / 2.0, y: self.frame.height / 2.0)

//Get convert the CLLocationCoordinate2D of the pin (or map location) to a screen space CGPoint
let annotationPoint = mapview.convert(myPinCoordinate, toPointTo: mapview)

//And finally do the math to set the offsets in screen space                
let mapViewPointFromAnnotation = CGPoint(x: annotationPoint.x + (centerPoint.x - targetPoint.x), y: annotationPoint.y + (centerPoint.y - targetPoint.y))

//Now convert that result to a Coordinate
let finalLocation = self.convert(mapViewPointFromAnnotation, toCoordinateFrom: mapview)

//And set the map center
mapview.setCenter(finalLocation, animated: true)
0
Drewsipher