想要将 MKMapView 居中在给定引脚下方 N 像素的点上(在当前 MapRect 中可能可见也可能不可见)。
我一直在尝试使用各种游戏来解决这个问题-(CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view
,但没有成功。
有人走过这条路(没有双关语)吗?
想要将 MKMapView 居中在给定引脚下方 N 像素的点上(在当前 MapRect 中可能可见也可能不可见)。
我一直在尝试使用各种游戏来解决这个问题-(CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view
,但没有成功。
有人走过这条路(没有双关语)吗?
最简单的技术就是将地图向下移动,比如说从 40% 的位置,coordinate
利用span
的。如果您不需要实际像素,而只需要将其向下移动,以便问题位于地图顶部附近(例如距离顶部 10%):region
MKMapView
CLLocationCoordinate2D
CLLocationCoordinate2D center = coordinate;
center.latitude -= self.mapView.region.span.latitudeDelta * 0.40;
[self.mapView setCenterCoordinate:center animated:YES];
如果您想考虑相机的旋转和俯仰,上述技术可能不够用。在这种情况下,您可以:
确定视图中要将用户位置移动到的位置;
将其转换为CLLocation
;
计算当前用户位置与新的期望位置的距离;
将相机从地图相机的当前航向沿 180° 方向移动该距离。
例如在 Swift 3 中,类似:
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
有以下内容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
}
}
因此,即使相机倾斜并且朝向正北以外的方向,这也会将用户的位置(居中,下部十字准线所在的位置)向上移动 150 像素(上部十字准线所在的位置),产生如下内容:
显然,您应该注意退化的情况(例如,您距离南极 1 公里,并且您尝试将地图向上移动 2 公里;您使用的摄像机角度倾斜到所需的屏幕位置超过地平线;等),但对于实际的、真实的场景,类似上面的东西可能就足够了。显然,如果不让用户改变摄像头的俯仰角,答案就更简单了。
原始答案:用于移动注释n
像素
如果您有 a CLLocationCoordinate2D
,您可以将其转换为 a CGPoint
,将其移动 x 个像素,然后将其转换回 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];
}
您可以通过以下方式调用它:
[self moveCenterByOffset:CGPointMake(0, 100) from:coordinate];
不幸的是,这仅coordinate
在开始之前可见时才有效,因此您可能必须先转到原始坐标,然后调整中心。
可靠地做到这一点的唯一方法是使用以下内容:
- (void)setVisibleMapRect:(MKMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animate
为了做到这一点,给定一个您想要居中的地图区域,您必须将地图区域转换为 MKMapRect。显然,使用边缘填充作为像素偏移。
请参阅此处: 将 MKCoordinateRegion 转换为 MKMapRect
评论:我觉得这是唯一的方法很奇怪,因为 MKMapRect 不是通常与 MKMapView 一起使用的东西——所有的转换方法都是针对 MKMapRegion 的。但是,好的,至少它有效。在我自己的项目中测试。
对于斯威夫特:
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)
}
}
一种简单的解决方案是使地图视图的框架大于可见区域。然后将您的图钉放置在地图视图的中心,并将所有不需要的区域隐藏在另一个视图后面或屏幕边界之外。
让我详细说明。如果我查看您的屏幕截图,请执行以下操作:
你别针和底部之间的距离是 353 像素。因此,让您的地图视图框架的高度是两倍:706 像素。您的屏幕截图高度为 411 像素。将框架定位在 706px - 411px = -293 像素的原点。现在将您的地图视图以图钉的坐标居中,您就完成了。
2014 年 3 月 4 日更新:
我用 Xcode 5.0.2 创建了一个小示例应用程序来演示:http ://cl.ly/0e2v0u3G2q1d
斯威夫特 3 更新
更新了缩放功能
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)
}
在阅读了这个帖子广告之后,尤其是放大注释后,我最终得到了以下程序:
** 以注释为中心:**
- (void) centerOnSelection:(id<MKAnnotation>)annotation
{
MKCoordinateRegion region = self.mapView.region;
region.center = annotation.coordinate;
CGFloat per = ([self sizeOfBottom] - [self sizeOfTop]) / (2 * self.mapView.frame.size.height);
region.center.latitude -= self.mapView.region.span.latitudeDelta * per;
[self.mapView setRegion:region animated:YES];
}
** 放大注释:**
- (void) zoomAndCenterOnSelection:(id<MKAnnotation>)annotation
{
DLog(@"zoomAndCenterOnSelection");
MKCoordinateRegion region = self.mapView.region;
MKCoordinateSpan span = MKCoordinateSpanMake(0.005, 0.005);
region.center = annotation.coordinate;
CGFloat per = ([self sizeOfBottom] - [self sizeOfTop]) / (2 * self.mapView.frame.size.height);
region.center.latitude -= self.mapView.region.span.latitudeDelta * span.latitudeDelta / region.span.latitudeDelta * per;
region.span = span;
[self.mapView setRegion:region animated:YES];
}
-(CGFloat) sizeOfBottom
并且-(CGFloat) sizeOfTop
从布局指南中返回覆盖地图视图的面板的高度
作为公认答案的替代方案,我建议您最初的直觉是正确的。您可以严格在地图视图像素坐标空间内工作以获得偏移和最终定位。然后使用从位置到屏幕视图的转换调用,您可以获得最终位置并设置地图中心。
这将适用于旋转的相机并且相对于屏幕空间。在我的情况下,我需要将地图放在一个大头针上,并使用一个偏移量来说明一个地图抽屉。
这是转换调用
func convert(_ coordinate: CLLocationCoordinate2D, toPointTo view: UIView?) -> CGPoint
func convert(_ point: CGPoint, toCoordinateFrom view: UIView?) -> CLLocationCoordinate2D
这是一个 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)
看看这个方法MKMapView
:
- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated