MKMapView MKCircle呈现一个半径过大的圆 [英] MKMapView MKCircle renders a circle with too big radius

查看:576
本文介绍了MKMapView MKCircle呈现一个半径过大的圆的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正面临MKCircle外观的奇怪行为。基本上我试图用一个任意的中心绘制一个半径为8500公里的圆。这里是我的代码:

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

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

  private func configureGestureRecognizer(){
doubleTapGestureRecognizer.addTarget(self,action:Selector(handleDoubleTap:))
doubleTapGestureRecognizer.numberOfTapsRequired = 2
如果让subview = mapView.subviews.first as? UIView {
subview.addGestureRecognizer(doubleTapGestureRecognizer)
}
else {
println(无法添加手势识别器)
}
}

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

结果非常好奇怪:



p>



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

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



编辑

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

$ b $(小圈子jsut代表一个中心)

根据苹果公司的文件 MKCircle :随着纬度值从赤道移向极点,地图之间的物理距离分数变小。这意味着需要更多的地图点来表示相同的距离。因此,当圆的中心点从赤道移向极点时,圆形覆盖的边界矩形会变大。



Warren都提到过,这不是一个错误 - 这是预期的行为。然而,似乎是 boundingMapRect 和<$ c $之间的文档差异c> radius 。文档建议半径是距离中心点的米数,显然在您的示例中不是这样。



我想这里发生的事情是,苹果可能从来没有打算将MKCircle用于你使用它的规模上。 MKCircle 创建了一个2D圈,它可以既不是圆形也不是投影图上的圆形区域的准确表示。

现在如果你想要做的就是创建一个不是圆的圆圈扭曲并且相对于赤道处的长度具有半径,则可以设置cir的长度然后计算当前点的半径比例,如下所示:

  let baseCoord = CLLocationCoordinate2D(纬度:0,经度:0)
让radius:Double = 850000.0
$ b重写func viewDidLoad(){
super.viewDidLoad()
mapView.region = MKCoordinateRegion(
center:baseCoord,
span:MKCoordinateSpan(
latitudeDelta:90,
longitudeDelta:180


mapCenter = baseCoord
让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 ,半径:半径)

var currentRadius = circle.bound ngMapRect.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)$ b $ mapCenter = currentCoord
addPolygon()
}

addPolygon $ b $ pre $ private $ func addPolygon(){
var mapCoords = $ c $,获取你的坐标并设置你的覆盖层:

getCoordinates()
mapView.removeOverlays(mapView.overlays)

let polygon = MKPolygon(坐标:& mapCoords,count:mapCoords.count)
mapView.addOverlay(多边形)给定一个点,一个方位和一个角度距离(坐标之间的距离被划分为一个点,一个方位角和一个角度距离)按地球半径),可以计算出另一个坐标的位置使用以下公式。请务必导入 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

如果让a =注解{
mapView.removeAnnotation(注解)
}

注解= MKPointAnnotation ()
注释!.setCoordinate(currentCoord!)
注解!.title = String(格式:%1.2f°,%1.2f°,lat1,long1)
mapView.addAnnotation (注释)

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

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

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

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

var startingBearing = -180
var endingBearing = 180

如果metersToNorthPole - 半径<= 0 {
endingBearing = 0
startingBearing = -360
}

for var i = startingBearing;我<=结束时; i + =因子{$ ​​b
$ b 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 if == startingBearing&& metersToNorthPole - radius <= 0 {
coordinates.append(CLLocationCoordinate2D(latitude:90,longitude:long2))
} else if if == startingBearing&& metersToSouthPole - radius <= 0 {
coordinates.append(CLLocationCoordinate2D(latitude:-90,longitude:long2))
}

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

如果metersToNorthPole - 半径<= 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))
}

返回坐标
}

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



下面是几个靠近极点的曲线示例,半径为8500km和850 km,分别为:




这里有一个使用额外的 MKGeodesicPolyline 覆盖图(Geodesics表示曲面在球面上的最短可能曲线)显示最终输出的样本,显示实际构建曲线的方式:




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

The results are very strange:

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?

EDIT

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

(Small circle jsut represents a center)

解决方案

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."

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.

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

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

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

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
}

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.

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

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呈现一个半径过大的圆的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆