Centering Mkmapview on Spot N-Pixels Below Pin

Centering MKMapView on spot N-pixels below pin

The easiest technique is to just shift the map down, say 40% from where the coordinate would be, taking advantage of the span of the region of the MKMapView. If you don't need actual pixels, but just need it to move down so that the CLLocationCoordinate2D in question is near the top of the map (say 10% away from the top):

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

If you want to account for rotation and pitch of the camera, the above technique may not be adequate. In that case, you could:

  • Identify the position in the view to which you want to shift the user location;

  • Convert that to a CLLocation;

  • Calculate the distance of the current user location from that new desired location;

  • Move the camera by that distance in the direction 180° from the current heading of the map's camera.

E.g. in Swift 3, something like:

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

Where CLLocationCoordinate2D has the following extension:

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

So, even with the camera pitched and at a heading other than due north, this moves the user's location (which is centered, where the lower crosshair is) up 150 pixels (where the upper crosshair is), yielding something like:

Sample Image

Obviously, you should be conscious about degenerate situations (e.g. you're 1 km from the south pole and you try to shift the map up 2 km meters; you're using a camera angle pitched so far that the desired screen location is past the horizon; etc.), but for practical, real-world scenarios, something like the above might be sufficient. Obviously, if you don't let the user change the pitch of the camera, the answer is even easier.


Original answer: for moving the annotation n pixels

If you have a CLLocationCoordinate2D, you can convert it to a CGPoint, move it x pixels, and then convert it back to a 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];
}

You can call this by:

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

Unfortunately, this only works if the coordinate is visible before you start, so you might have to go to the original coordinate first, and then adjust the center.

User Current Location on specific position on MAP

Here is the trick to use to position the map in such a way that UserLocation pin gets adjusted automatically :

func setUserLocationOnLowerPositiononMap(coordinate: CLLocationCoordinate2D) {
var region = MKCoordinateRegion(center: coordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
region.center = coordinate
self.map.setRegion(region, animated: true)
//self.map.moveCenterByOffSet(offSet: CGPoint(x: 0, y: -130), coordinate: coordinate)
self.map.moveCenterByOffSet(offSet: CGPoint(x: 0, y: SCREEN_HEIGHT - SCREEN_HEIGHT * 4/3 + 0 ), coordinate: coordinate)
}

Center an MKAnnotation in a specific MKMapRect

I stumbled across this post here, which massively helped: Centering MKMapView on spot N-pixels below pin.

As mentioned in the answer, you should work out the destination point by converting it out of the map view and into your view controller's view with a more or less standardised frame.

// define the rect in which to center the annotation
let visibleRect = CGRectMake(0, 0, CGRectGetWidth(view.frame), 200)

centerAnnotationInRect(someAnnotation, rect: visibleRect)

func centerAnnotationInRect(annotation: MKAnnotation, rect: CGRect) {

guard let mapView = mapView else {
return
}

let visibleCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect))

let annotationCenter = mapView.convertCoordinate(annotation.coordinate, toPointToView: view)

let distanceX: CGFloat = visibleCenter.x - annotationCenter.x
let distanceY = visibleCenter.y - annotationCenter.y

mapView.scrollWithOffset(CGPoint(x: distanceX, y: distanceY), animated: true)
}

MKMapView centerCoordinate not exact

In your answer you fixed one particular scenario where the center coordinate appears to be off, but really is not because it is just partially obscured by the status bar. Unfortunately, it is not as simple as that. There are other edge cases where the center coordinate actually can get offset a bit. (I notice it when I change frame of map view as keyboard is presented and later dismissed.) This is a curious little map view idiosyncrasy.

Anyone know how to determine how "off" the center coordinate is so I can adjust?

Yes, just convert the centerCoordinate to a CGPoint and compare to the center. The only trick is to make sure you do these in the same coordinate system (in the example below, the coordinate system of the map view):

func adjustOverlayCenterYConstraint() {
let mapCenter = mapView.convert(mapView.centerCoordinate, toPointTo: mapView)
let viewCenter = mapView.superview!.convert(mapView.center, to: mapView)

overlayCenterYConstraint.constant = mapCenter.y - viewCenter.y
}

In this example, I have a centerY constraint, whose constant I am adjusting, but it illustrates the idea of how to programmatically determine whether the center is offset a bit and how to adjust it. Theoretically, you might have to adjust the x coordinate, too, but the idea would be the same.


You said:

is still off (now its south and west of the true center)

If it is off both south and west, then the problem is likely just the coordinate system. Remember, center is in the coordinate system of the superview, so make sure to convert to a consistent coordinate system (either get coordinateCenter in coordinate system of map view’s superview and compare to center, or get both in the coordinate system of the mapview, like shown above).

Move MKMapView point to pixel coordinates

EDIT:

Ok so I messed around with this a little and was able to to put something together that seems to work, hoping that I now actually understand what you're trying to achieve!

- (void)shiftToCorner{
//ignore "Annotation", this is just my own custom MKAnnotation subclass
Annotation *currentAnnotation = [[mapView selectedAnnotations] objectAtIndex:0];
[mapView setCenterCoordinate:currentAnnotation.coordinate];

CGPoint fakecenter = CGPointMake(20, 20);
CLLocationCoordinate2D coordinate = [mapView convertPoint:fakecenter toCoordinateFromView:mapView];
[mapView setCenterCoordinate:coordinate animated:YES];
}

Let me quickly explain what this does; Let's say you want your annotation to move to a position 20 points in from the right edge and 20 points up from the bottom edge of the map view. If you think about it, if the current annotation is at the center of the map view, then the distance to this point is the same as the distance to (20, 20). This means that we can send our annotation to this point by first centering our map view on the annotation, then animating to (20, 20).



Related Topics



Leave a reply



Submit