我需要弄清楚如何设置MKMapSnapshotterOptions来拍摄与地球多边形区域相关的航空/卫星图像的快照。
填写"region"、"scale"、"size"one_answers"mapType"属性很简单,因为我要使用MKPolygon。棘手的部分是设置"相机"——在我的特殊情况下,我使用的是独立于MKMapView的MKMapSnapshoter(事实上,甚至不是在主线程上)。
然而,我更喜欢调整快照的方向,使其符合基于非零标题的多边形边界——也就是说,我正在拍摄的区域有一个"开始"和一个"结束",我希望从结果图像的底部到顶部进行调整。由于多边形基本上永远不会在0度航向上自然定向,我需要确定"中心坐标"、"航向"one_answers"高度"。
由于我有多边形的坐标,我能够很容易地导出中心坐标和所需的航向——多边形的第一个坐标与形状的"开始"相关,而结束(或其他坐标,在我的情况下)与"结束"相关。
事实证明,计算海拔高度更加困难;我想确保多边形区域最终填充了我想要显示的快照图像的纵横比。如何在不依赖MKMapView的"setRegion"选择器的情况下计算MKMapCamera使用的正确海拔高度?
为了解决这个问题,我最终做了以下事情:
1) 围绕MKPolygon的中心坐标旋转MKPolygon,以消除确定边界矩形时的航向/旋转问题:如果不向MKPolygon询问其"boundingMapRect",则会返回整个形状周围的最小矩形。如果一个细长的多边形恰好从东北向西南倾斜,那么边界矩形将几乎是正方形。执行旋转可以在确定多边形的纵横比时考虑多边形的方向。
2) 将多边形的标题校正后的边界矩形拟合到快照视口的纵横比中:这可以确保非常"高"的多边形仍然适合宽的纵横比视口,反之亦然。
3)[从我的示例代码中删除]创建一个经过纵横比校正的边界矩形的多边形,并使用多边形的中心坐标将其旋转回原始标题:如果处理大区域,可能需要这样做,因为下一步需要测量水平/垂直边界距离。就我而言,我正在处理非常小的区域,这些区域不应该受到地球曲率的足够影响,从而产生真正的影响。
4) 确定以米为单位的总水平和垂直边界区域
5) 使用两个距离中较大的尺寸(dimension)形成三角形的基本测量,其中a=轴上的最小坐标位置,B=轴上最大坐标位置,C=相机位置(多边形的中心坐标)
在这一点上,我有点困惑于如何在没有至少1个角度的情况下求解得到的三角形的高度。在使用MKMapView实例执行某些测试时,MKMapCamera的光圈似乎约为30度——这与增加视口的纵横比、多边形的纵横比或地球曲率以外的任何其他因素无关。我可能错了这个论断
5) 使用我在测试中观察到的孔径角,使用(dimension/2)/tan(aperture_angle_in_radians/2)计算所需的高度
看到我最终花了多少时间在这上面,我决定在StackOverflow上发布问答组合,希望它能:1) 帮助其他处于同样情况的人2) 由比我聪明的人纠正,并带来更好的解决方案
谢谢!
哦,当然还有代码:
+ (double)determineAltitudeForPolygon:(MKPolygon *)polygon withHeading:(double)heading andWithViewport:(CGSize)viewport {
// Get a bounding rectangle that encompasses the polygon and represents its
// true aspect ratio based on the understanding of its heading.
MKMapRect boundingRect = [[self rotatePolygon:polygon withCenter:MKMapPointForCoordinate(polygon.coordinate) byHeading:heading] boundingMapRect];
MKCoordinateRegion boundingRectRegion = MKCoordinateRegionForMapRect(boundingRect);
// Calculate a new bounding rectangle that is corrected for the aspect ratio
// of the viewport/camera -- this will be needed to ensure the resulting
// altitude actually fits the polygon in view for the observer.
CLLocationCoordinate2D upperLeftCoord = CLLocationCoordinate2DMake(boundingRectRegion.center.latitude + boundingRectRegion.span.latitudeDelta / 2, boundingRectRegion.center.longitude - boundingRectRegion.span.longitudeDelta / 2);
CLLocationCoordinate2D upperRightCoord = CLLocationCoordinate2DMake(boundingRectRegion.center.latitude + boundingRectRegion.span.latitudeDelta / 2, boundingRectRegion.center.longitude + boundingRectRegion.span.longitudeDelta / 2);
CLLocationCoordinate2D lowerLeftCoord = CLLocationCoordinate2DMake(boundingRectRegion.center.latitude - boundingRectRegion.span.latitudeDelta / 2, boundingRectRegion.center.longitude - boundingRectRegion.span.longitudeDelta / 2);
CLLocationDistance hDist = MKMetersBetweenMapPoints(MKMapPointForCoordinate(upperLeftCoord), MKMapPointForCoordinate(upperRightCoord));
CLLocationDistance vDist = MKMetersBetweenMapPoints(MKMapPointForCoordinate(upperLeftCoord), MKMapPointForCoordinate(lowerLeftCoord));
double adjacent;
double newHDist, newVDist;
if (boundingRect.size.height > boundingRect.size.width) {
newVDist = vDist;
newHDist = (viewport.width / viewport.height) * vDist;
adjacent = vDist / 2;
} else {
newVDist = (viewport.height / viewport.width) * hDist;
newHDist = hDist;
adjacent = hDist / 2;
}
double result = adjacent / tan(Deg_to_Rad(15));
return result;
}
+ (MKPolygon *)rotatePolygon:(MKPolygon *)polygon withCenter:(MKMapPoint)centerPoint byHeading:(double)heading {
MKMapPoint points[polygon.pointCount];
double rotation_angle = -Deg_to_Rad(heading);
for(int i = 0; i < polygon.pointCount; i++) {
MKMapPoint point = polygon.points[i];
// Translate each point by the coordinate to rotate around, use matrix
// algebra to perform the rotation, then translate back into the
// original coordinate space.
double newX = ((point.x - centerPoint.x) * cos(rotation_angle)) + ((centerPoint.y - point.y) * sin(rotation_angle)) + centerPoint.x;
double newY = ((point.x - centerPoint.x) * sin(rotation_angle)) - ((centerPoint.y - point.y) * cos(rotation_angle)) + centerPoint.y;
point.x = newX;
point.y = newY;
points[i] = point;
}
return [MKPolygon polygonWithPoints:points count:polygon.pointCount];
}
更新了swift 5的答案,适用于iOS 13和更高版本的
func calculateCenterCoordinateDistance(for zoomLevel: CGFloat) -> CLLocationDistance {
let width = self.frame.size.width
let span = MKCoordinateSpan(latitudeDelta: 0.0, longitudeDelta:
CLLocationDegrees(360 * width / (pow(2, (zoomLevel - 1)) * 256)))
let region = MKCoordinateRegion(center: self.region.center, span: span)
let aspectRatio = Double(self.frame.size.height / self.frame.size.width)
let radianCameraAperture: Double = 30 * .pi / 180
let areaRadius = aspectRatio * region.longitudinalMeters / 2
return areaRadius / tan(radianCameraAperture / 2)
}
它可以用于计算特定缩放级别的最小中心坐标距离。
let minDistance = mapView.calculateCenterCoordinateDistance(for: 12)
mapView.setCameraZoomRange(MKMapView.CameraZoomRange(minCenterCoordinateDistance: minDistance), animated: false)
纵向米计算如下:
extension MKCoordinateRegion {
var east: CLLocation {
return CLLocation(latitude: center.latitude, longitude: center.longitude + span.longitudeDelta / 2)
}
var west: CLLocation {
return CLLocation(latitude: center.latitude, longitude: center.longitude - span.longitudeDelta / 2)
}
var longitudinalMeters: CLLocationDistance {
return east.distance(from: west)
}
}
学分:https://gist.github.com/marmelroy/0fee54bfe69bfbfcbbf7057298fca046