According to Apple MKCircle documentation: “As latitudes move away from the equator and to the poles, the physical distance between map points is reduced. This means that more map points are required to display the same distance. As a result, the bounding box of the circle overlay becomes larger because the center point this circle departs from the equator and to the poles. "
Since Anna and Warren mentioned this, this is not a mistake - this is the intended behavior. However, there seems to be a discrepancy in documentation between boundingMapRect and radius . The documentation assumes that the radius is a measure in meters from the center point, which clearly does not match your example.
I think what is happening here is that Apple probably never imagined MKCircle to be used at the scale at which you use it. MKCircle creates a 2D circle that cannot be either a circle or an accurate representation of a circular region on a projection map.
Now, if all you want to do is create a homogeneous circle that is not 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, for example:
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 needs to accurately cover the entire space within x meters of click, it's a little more complicated. First you take the click coordinate in a double click, and then use it as the center of the polygon.
@objc private func handleDoubleTap(sender: UITapGestureRecognizer) { let point = sender.locationInView(mapView) currentCoord = mapView.convertPoint(point, toCoordinateFromView: mapView) mapCenter = currentCoord addPolygon() }
In addPolygon get the 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 the point, bearing, and angular distance (the distance between the coordinates, divided by the radius of the earth), you can calculate the location of another coordinate using the following formula. Be sure to import Darwin so that you can access the trigonometric function library
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 into radians, and then add a few more fixing coordinates if our radius is greater than the distance to the north or south poles.
Here are some examples of curves near the pole with radii of 8500 km and 850 km, respectively:


Here is a sample of the final output with an additional overlay of MKGeodesicPolyline (geodesics are the shortest possible curve over a spherical surface), which shows how the curve is constructed:
