如何确定MKMapCamera专注于MKPolygon的正确高度

时间:2014-01-10 01:04:51

标签: ios mapkit mkmaprect cllocationcoordinate2d mkmapsnapshotter

我需要弄清楚如何设置MKMapSnapshotterOptions来拍摄与地球多边形区域相关的航空/卫星图像的快照。

填写'region','scale','size'和'mapType'属性是微不足道的,因为我有一个MKPolygon可以使用。棘手的部分是设置'相机' - 在我的特定情况下,我使用MKMapSnapshotter独立于MKMapView(事实上,甚至不在主线程上)。

但是,我更喜欢定位快照,使其符合基于非零标题的多边形边界 - 也就是说,我正在拍摄的区域有一个'start'和''结束'我想从结果图像的底部到顶部定向。由于多边形基本上永远不会在0度标题上自然定向,我需要确定'centerCoordinate','heading'和'altitude'。

由于我有多边形的坐标,我能够相当容易地导出中心坐标和所需的标题 - 多边形的第一个坐标与形状的“开始”和结束(或其他坐标,在我的中)相关联case)与'end'相关联。

确定高度更加困难;我想确保多边形区域最终填满我想要显示的快照图像的宽高比。如何在不依赖MKMapView的'setRegion'选择器的情况下计算与MKMapCamera一起使用的正确高度?

2 个答案:

答案 0 :(得分:18)

为了解决这个问题,我最终做了以下事情:

1)在确定边界矩形时围绕它的中心坐标旋转MKPolygon以消除标题/旋转问题:向MKPolygon询问它的'boundingMapRect',如果不这样,将返回围绕整个形状的任何最小矩形。如果一个长而瘦的多边形碰巧从东北向西南方向倾斜,则边界矩形几乎是方形的。执行旋转允许在确定多边形的纵横比时考虑多边形的标题。

2)将多边形的经过标题校正的边界矩形拟合到快照视口的纵横比中:这可确保非常“高”的多边形仍能在宽视角视口中正确拟合,反之亦然。

3) [从我的示例代码中删除] 创建生成的经方面修正的边界矩形的多边形,并使用多边形的中心坐标将其旋转回原始标题:如果与大区域一起工作,下一步涉及水平/垂直边界距离之间的测量。在我的情况下,我正在处理非常小的区域,这些区域不应受到地球曲率的影响,从而产生真正的差异。

4)以米为单位确定总水平和垂直边界区域

5)使用两个距离的较大尺寸(尺寸)来形成三角形的基础测量,其中A =轴上的最小坐标位置,B =轴上的最大坐标位置,C =相机位置(中心坐标)多边形)

此时,我有点难以理解如何在没有至少一个角度的情况下求解所得三角形的高度。在使用MKMapView实例执行某些测试时,看起来MKMapCamera的光圈大约是30度 - 这无论是增加视口的宽高比,多边形的宽高比,还是任何其他因素比地球的曲率。我对这个断言可能是错的。

5)使用我的测试中观察到的孔径角,使用(尺寸/ 2)/ tan(aperture_angle_in_radians / 2)计算所需的高度

看到我花了多少时间花在这上面,我决定在StackOverflow上发布问题/答案组合,希望它: 1)在相同的情况下帮助其他人 2)通过比我更聪明的方式纠正,并导致更好的解决方案

谢谢!

OH,当然还有代码:

+ (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];
}

答案 1 :(得分:0)

更新了适用于 iOS 13 及更高版本的 swift 5 的答案

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