ios - MKMapView MKCircle 渲染一个半径太大的圆

标签 ios swift geolocation mkmapview

我正面临 MKCircle 外观的奇怪行为。基本上我想画一个半径为 8500 公里的圆,圆心是任意的。这是我的代码:

private func addCircle() {
    mapView.removeOverlays(mapView.overlays)
    let circle = MKCircle(centerCoordinate: mapCenter, radius: 8500000.0)
    mapView.addOverlay(circle)
}

我还有一个自定义的双击手势处理程序,它会覆盖 map View 的标准手势处理程序,并允许通过双击 map View 来更改 map 中心:

private func configureGestureRecognizer() {
    doubleTapGestureRecognizer.addTarget(self, action: Selector("handleDoubleTap:"))
    doubleTapGestureRecognizer.numberOfTapsRequired = 2
    if let subview = mapView.subviews.first as? UIView {
        subview.addGestureRecognizer(doubleTapGestureRecognizer)
    }
    else {
        println("Can't add a gesture recognizer")
    }
}

@objc private func handleDoubleTap(sender: UITapGestureRecognizer) {
    let point = sender.locationInView(mapView)
    let location = mapView.convertPoint(point, toCoordinateFromView: mapView)
    mapCenter = location
    addCircles()
}

结果很奇怪:

Center in New York City

Center in far north from NYC

您可能会注意到这两个半径之间存在显着差异:第二个半径比第一个大很多!

这是怎么回事,我该如何让它们正确显示?

编辑

感谢@blacksquare,我可以更接近解决方案,但北极仍然存在问题:

enter image description here

(小圆只是代表一个中心)

最佳答案

根据 Apple 的 MKCircle 文档:“随着纬度值从赤道向两极移动, map 点之间的物理距离变小。这意味着需要更多的 map 点来表示相同的距离。因此,当圆的中心点远离赤道并移向两极时,圆叠加层的边界矩形会变大。”

因此,正如 Anna 和 Warren 都提到的,这不是错误——这是预期的行为。但是,boundingMapRectradius 之间的文档似乎存在差异。文档表明半径是从中心点开始的测量值(以米为单位),在您的示例中显然不是这种情况。

我认为这里发生的事情是 Apple 可能从未打算在您使用它的规模上使用 MKCircle。 MKCircle 创建一个 2D 圆,它不能既是圆又是投影图上圆形区域的准确表示。

现在,如果您想做的只是创建一个均匀的圆,该圆没有扭曲并且半径相对于赤道处的长度,您可以将赤道处的圆的长度设置为基本半径,然后计算当前点的半径比例是这样的:

let baseCoord = CLLocationCoordinate2D(latitude: 0, longitude: 0)
let radius: Double = 850000.0

override func viewDidLoad() {
    super.viewDidLoad()
    mapView.region = MKCoordinateRegion(
        center: baseCoord,
        span: MKCoordinateSpan(
            latitudeDelta: 90,
            longitudeDelta: 180
        )
    )
    mapCenter = baseCoord
    let circle = MKCircle(centerCoordinate: mapCenter, radius: radius)
    baseRadius = circle.boundingMapRect.size.height / 2

    mapView.delegate = self
    configureGestureRecognizer()
}

private func addCircle() {

    mapView.removeOverlays(mapView.overlays)
    let circle = MKCircle(centerCoordinate: mapCenter, radius: radius)

    var currentRadius = circle.boundingMapRect.size.height / 2
    let factor = baseRadius / currentRadius
    var updatedRadius = factor * radius

    let circleToDraw = MKCircle(centerCoordinate: mapCenter, radius: updatedRadius)
    mapView.addOverlay(circleToDraw)
}

但如果您的计划是准确覆盖点击后 x 米范围内的所有空间,那就有点棘手了。首先,您将在双击操作中获取点击坐标,然后将其用作多边形的中心。

@objc private func handleDoubleTap(sender: UITapGestureRecognizer) {
    let point = sender.locationInView(mapView)
    currentCoord = mapView.convertPoint(point, toCoordinateFromView: mapView)
    mapCenter = currentCoord
    addPolygon()
}

addPolygon 中,获取坐标并设置叠加层:

private func addPolygon() {
    var mapCoords = getCoordinates()
    mapView.removeOverlays(mapView.overlays)

    let polygon = MKPolygon(coordinates: &mapCoords, count: mapCoords.count)
    mapView.addOverlay(polygon)
}

给定一个点、一个方位角和一个角距离(坐标之间的距离除以地球半径),您可以使用以下公式计算另一个坐标的位置。请务必导入 Darwin,这样您就可以访问三角函数库

let globalRadius: Double = 6371000
let π = M_PI

private func getCoordinates() -> [CLLocationCoordinate2D] {
    var coordinates = [CLLocationCoordinate2D]()

    let lat1: Double = (currentCoord!.latitude)
    let long1: Double = (currentCoord!.longitude) + 180
    let factor = 30

    if let a = annotation {
        mapView.removeAnnotation(annotation)
    }

    annotation = MKPointAnnotation()
    annotation!.setCoordinate(currentCoord!)
    annotation!.title = String(format: "%1.2f°, %1.2f°", lat1, long1)
    mapView.addAnnotation(annotation)

    var φ1: Double = lat1 * (π / 180)
    var λ1: Double = long1 * (π / 180)
    var angularDistance =  radius / globalRadius

    var metersToNorthPole: Double = 0
    var metersToSouthPole: Double = 0

    for i in Int(lat1)..<89 {
        metersToNorthPole = metersToNorthPole + 111132.92 - (559.82 * cos(2 * φ1)) + (1.175 * cos(4 * φ1))
    }

    for var i = lat1; i > -89; --i {
        metersToSouthPole = metersToSouthPole + 111132.92 - (559.82 * cos(2 * φ1)) + (1.175 * cos(4 * φ1))
    }

    var startingBearing = -180
    var endingBearing = 180

    if metersToNorthPole - radius <= 0 {
        endingBearing = 0
        startingBearing = -360
    }

    for var i = startingBearing; i <= endingBearing; i += factor {

        var bearing = Double(i)

        var bearingInRadians: Double = bearing * (π / 180)

        var φ2: Double = asin(sin(φ1) * cos(angularDistance)
            + cos(φ1) * sin(angularDistance)
            * cos(bearingInRadians)
        )

        var λ2 = atan2(
            sin(bearingInRadians) * sin(angularDistance) * cos(φ1),
            cos(angularDistance) - sin(φ1) * sin(φ2)
        ) + λ1

        var lat2 = φ2 * (180 / π)
        var long2 = ( ((λ2 % (2 * π)) - π)) * (180.0 / π)

        if long2 < -180 {
            long2 = 180 + (long2 % 180)
        }

        if i == startingBearing && metersToNorthPole - radius <= 0 {
            coordinates.append(CLLocationCoordinate2D(latitude: 90, longitude: long2))
        } else if i == startingBearing && metersToSouthPole - radius <= 0 {
            coordinates.append(CLLocationCoordinate2D(latitude: -90, longitude: long2))
        }

        coordinates.append(CLLocationCoordinate2D(latitude: lat2, longitude: long2))
    }

    if metersToNorthPole - radius <= 0 {
        coordinates.append(CLLocationCoordinate2D(latitude: 90, longitude: coordinates[coordinates.count - 1].longitude))
    } else if metersToSouthPole - radius <= 0 {
        coordinates.append(CLLocationCoordinate2D(latitude: -90, longitude: coordinates[coordinates.count - 1].longitude))
    }

    return coordinates
}

getCoordinates 中,我们将度数转换为弧度,然后在我们的半径大于到北极或南极的距离的情况下再添加一些锚定坐标。

以下是半径分别为 8500 公里和 850 公里的极点附近的几个曲线示例:

enter image description here enter image description here

这是最终输出的示例,带有附加的 MKGeodesicPolyline 叠加层(测地线表示球面上可能的最短曲线),显示了曲线的实际构建方式:

enter image description here

关于ios - MKMapView MKCircle 渲染一个半径太大的圆,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28872162/

相关文章:

ios - NIB 的 UITableViewCell 中的子类 UIView

ios - 由于权利文件或配置文件存在问题,无法构建 Ionic/Cordova iOS

iphone - UINavigationBar titleView 的最大尺寸是多少?

java - GPS 和地理定位如何在 Android 设备上发挥作用

mongodb - 查询以匹配包含点的多边形

android - Android phonegap GPS 精度

ios - IOS 上的 Hpple XPath 查询问题

swift - 如何防止 SceneKit 中的 SpriteKit 覆盖拉伸(stretch)?

ios - UIImagePicker 给出运行时错误

iOS堆栈 View 约束问题