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

时间:2023-03-21
本文介绍了MKMapView MKCircle 渲染一个半径太大的圆的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

我正面临 MKCircle 外观的奇怪行为.基本上,我正在尝试绘制一个半径为 8500 公里且具有任意中心的圆.这是我的代码:

I'm facing with a strange behaviour of MKCircle appearance. Basically I'm trying to draw a circle with a radius of 8500 km with an arbitrary center. Here is my code:

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

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

I also have a custom double tap gesture handler, which overwrites the standard one for map view and allows to change the map center by double tapping on the map view:

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()
}

结果很奇怪:

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

You may notice a significant difference between those two radiuses: the second one is a way bigger than the first one!

发生了什么,如何让它们正确显示?

What's going on and how do I make them appear correctly?

编辑

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

Thanks to @blacksquare I could get closer to solution, but still have an issue with the north pole:

(小圆圈代表一个中心)

(Small circle jsut represents a center)

推荐答案

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

According to Apple's documentation of MKCircle: "As latitude values move away from the equator and toward the poles, the physical distance between map points gets smaller. This means that more map points are needed to represent the same distance. As a result, the bounding rectangle of a circle overlay gets larger as the center point of that circle moves away from the equator and toward the poles."

正如 Anna 和 Warren 都提到的,这不是错误——这是预期的行为.但是,boundingMapRectradius 之间的文档似乎存在差异.文档表明,半径是以米为单位的距离中心点的度量,在您的示例中显然不是这种情况.

So as Anna and Warren both mentioned, this isn't a bug--this is the intended behavior. There seems, however, to be a discrepancy in the documentation between boundingMapRect and radius. The documentation suggests that the radius is the measure in meters from the center point, which is clearly not the case in your example.

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

I think what's going on here is that Apple probably never intended MKCircle to be used on the scale that you're using it on. MKCircle creates a 2D circle, which can't be both a circle and an accurate representation of a circular area on a projection map.

现在,如果您要做的只是创建一个不变形且半径相对于赤道处长度的均匀圆,您可以将赤道处的圆的长度设置为基本半径,然后计算当前点的半径比例如下:

Now if all you want to do is create a uniform circle that isn't distorted and has a radius relative to its length at the equator, you can set the length of the circle at the equator as the base radius and then calculate the proportion of the radius at the current point like this:

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 米内的所有空间,那就有点棘手了.首先,您将在双击操作中获取单击坐标,然后将其用作多边形的中心.

But if your plan is to accurately cover all space within x meters of the click, it's a bit trickier. First you'll grab the click-coordinate in the double-click action and then use that as the center of a polygon.

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

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

In addPolygon, get your coordinates and set up your overlays:

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

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

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

Given a point, a bearing, and an angular distance (distance between coordinates divided by the earth's radius), you can calculate the location of another coordinate using the following formula. Be sure to import Darwin so you can have access to a library of trigonometric functions

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 中,我们将度数转换为弧度,然后在我们的半径大于到北极或南极的距离时添加更多锚定坐标.

In getCoordinates we translate degrees to radians, and then add a few more anchoring coordinate in the event that our radius is greater than the distance to the north or south poles.

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

Here are a couple examples of curves near the pole with radiuses of 8500km and 850km, respectively:

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

Here's a sample of the final output with an additional MKGeodesicPolyline overlay (Geodesics represent the shortest possible curve over a spherical surface), that shows how the curve is actually being built:

这篇关于MKMapView MKCircle 渲染一个半径太大的圆的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

上一篇:在 Google 地图上制作可点击的多边形(适用于 Android) 下一篇:Location.getTime() 总是返回没有毫秒的时间戳

相关文章