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)?
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
Où 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:
É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.
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.
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)
}
}
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
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)
}
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)