6

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

填写 'region'、'scale'、'size' 和 'mapType' 属性很简单,因为我有一个 MKPolygon 可以使用。棘手的部分是设置“相机”——在我的特殊情况下,我使用独立于 MKMapView 的 MKMapSnapshotter(事实上,甚至在主线程上也没有)。

但是,我更愿意调整快照的方向,使其适合基于非零标题的多边形边界——也就是说,我正在拍照的区域有一个“开始”和一个“结束”我想从结果图像的底部定位到顶部。由于多边形基本上永远不会自然地朝向 0 度方向,因此我需要确定“中心坐标”、“方向”和“高度”。

因为我有多边形的坐标,所以我能够相当容易地得出中心坐标和所需的航向——多边形的第一个坐标与形状的“开始”相关,而结束(或其他坐标,在我的例子中)相关到最后'。

事实证明,确定海拔高度更加困难。我想确保多边形区域最终填充我希望显示的快照图像的纵横比。如何在不依赖 MKMapView 的“setRegion”选择器的情况下计算与 MKMapCamera 一起使用的正确高度?

4

2 回答 2

21

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

1)在确定边界矩形时围绕其中心坐标旋转 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];
}
于 2014-01-10T01:04:51.280 回答
0

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

于 2021-07-07T21:16:26.177 回答