How to Draw circle around current Location like city mapper. (iOS 8 -Objective C)
Finally i have managed to create Circle.
MKCircle *circleoverlay = [MKCircle circleWithCenterCoordinate:mapView.userLocation.coordinate radius:100];
[circleoverlay setTitle:@"Circle"];
[mapView addOverlay:circleoverlay];
and than Rendering
- (MKOverlayRenderer *) mapView:(MKMapView *)mapView rendererForOverlay:(id)overlay
{ if([overlay isKindOfClass:[MKCircle class]])
{
MKCircleRenderer* aRenderer = [[MKCircleRenderer
alloc]initWithCircle:(MKCircle *)overlay];
aRenderer.fillColor = [[UIColor blueColor] colorWithAlphaComponent:0.0];
aRenderer.strokeColor = [[UIColor grayColor] colorWithAlphaComponent:0.9];
aRenderer.lineWidth = 2;
aRenderer.lineDashPattern = @[@2, @5];
aRenderer.alpha = 0.5;
return aRenderer;
}
else
{
return nil;
}
}
Now i only want to Overlay Text on the circle.
but i dont have any idea :(
MKMapView MKCircle renders a circle with too big radius
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:
How to draw circle overlay on MapKit that surrounds several annotations/coordinates?
I came up with MKCoordinateRegion
initializer, which provides the region of the coordinates, the extension has a computed property to provide the radius of the region.
extension MKCoordinateRegion {
init?(from coordinates: [CLLocationCoordinate2D]) {
guard coordinates.count > 1 else { return nil }
let a = MKCoordinateRegion.region(coordinates, fix: { $0 }, fix2: { $0 })
let b = MKCoordinateRegion.region(coordinates, fix: MKCoordinateRegion.fixMeridianNegativeLongitude, fix2: MKCoordinateRegion.fixMeridian180thLongitude)
guard (a != nil || b != nil) else { return nil }
guard (a != nil && b != nil) else {
self = a ?? b!
return
}
self = [a!, b!].min(by: { $0.span.longitudeDelta < $1.span.longitudeDelta }) ?? a!
}
var radius: CLLocationDistance {
let furthest = CLLocation(latitude: self.center.latitude + (span.latitudeDelta / 2),
longitude: center.longitude + (span.longitudeDelta / 2))
return CLLocation(latitude: center.latitude, longitude: center.longitude).distance(from: furthest)
}
// MARK: - Private
private static func region(_ coordinates: [CLLocationCoordinate2D],
fix: (CLLocationCoordinate2D) -> CLLocationCoordinate2D,
fix2: (CLLocationCoordinate2D) -> CLLocationCoordinate2D) -> MKCoordinateRegion? {
let t = coordinates.map(fix)
let min = CLLocationCoordinate2D(latitude: t.min { $0.latitude < $1.latitude }!.latitude,
longitude: t.min { $0.longitude < $1.longitude }!.longitude)
let max = CLLocationCoordinate2D(latitude: t.max { $0.latitude < $1.latitude }!.latitude,
longitude: t.max { $0.longitude < $1.longitude }!.longitude)
// find span
let span = MKCoordinateSpanMake(max.latitude - min.latitude, max.longitude - min.longitude)
// find center
let center = CLLocationCoordinate2D(latitude: max.latitude - span.latitudeDelta / 2,
longitude: max.longitude - span.longitudeDelta / 2)
return MKCoordinateRegion(center: fix2(center), span: span)
}
private static func fixMeridianNegativeLongitude(coordinate: CLLocationCoordinate2D) -> CLLocationCoordinate2D {
guard (coordinate.longitude < 0) else { return coordinate }
let fixedLng = 360 + coordinate.longitude
return CLLocationCoordinate2D(latitude: coordinate.latitude, longitude: fixedLng)
}
private static func fixMeridian180thLongitude(coordinate: CLLocationCoordinate2D) -> CLLocationCoordinate2D {
guard (coordinate.longitude > 180) else { return coordinate }
let fixedLng = -360 + coordinate.longitude
return CLLocationCoordinate2D(latitude: coordinate.latitude, longitude: fixedLng)
}
}
Usage:
let coordinates: [CLLocationCoordinate2D] = self.mapView.annotations.map{ $0.coordinate }
if let region = MKCoordinateRegion(from: coordinates) {
self.mapView.add(MKCircle(center: region.center, radius: region.radius))
}
Result is exactly what I want, with ability to handle coordinates crossing 180th meridian:
How to add circle around map pin in Swift?
The trouble with this question is that the code in the question does not represent faithfully the real code. Fortunately, you also posted the real code:
https://github.com/kcapretta/RadiusSocialNetworkingApp/blob/master/RadiusMap/RadiusLocationViewController
In that version of the code, we see clearly that your delegate method
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay)
-> MKOverlayRenderer {
is buried inside your viewDidLoad
. Thus it is purely a local function, invisible to all other code. You need to get it out of there so that it is a method of the view controller.
To demonstrate, here's a slight variant on your code, leaving out the unnecessary stuff about the region monitoring and the user's current location and concentrating just on the annotation and the circle overlay:
class ViewController: UIViewController, MKMapViewDelegate {
@IBOutlet var mapView : MKMapView!
let coordinate = CLLocationCoordinate2DMake(33.97823607957177, -118.43823725357653)
// this is a _method_
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
mapView.region = MKCoordinateRegion(center: coordinate, latitudinalMeters: 1000, longitudinalMeters: 1000)
let title = "Marina Bar Hop"
let restaurantAnnotation = MKPointAnnotation()
restaurantAnnotation.coordinate = coordinate
restaurantAnnotation.title = title
mapView.addAnnotation(restaurantAnnotation)
let regionRadius = 300.0
let circle = MKCircle(center: coordinate, radius: regionRadius)
mapView.addOverlay(circle)
}
// this is a _method_
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let circleRenderer = MKCircleRenderer(overlay: overlay)
circleRenderer.strokeColor = UIColor.red
circleRenderer.lineWidth = 1.0
return circleRenderer
}
}
The result:
Scroll MKMapView Underneath Circle Drawn MKMapView
I solved it!
Step 1:
I created a UIView
and added it as a subview to the map view. It is important to note that I made sure to center the UIView
on the map view. This is important because you will use the centerCoordinate
property of the MKMapView
to calculate the radius.
self.region = [[UIView alloc] initWithFrame:centerOfMapViewFrame];
self.region.contentMode = UIViewContentModeRedraw;
self.region.userInteractionEnabled = NO;
self.region.alpha = 0.5;
self.region.layer.cornerRadius = widthOfView/2;
self.region.backgroundColor = [UIColor blueColor];
[self.mapView addSubview:self.region];
The image below shows the circular UIView
added as a subview to the mapView
.
Step 2:
Calculate the radius based off of the center coordinate of map view and the edge coordinate of the UIView.
CLLocationCoordinate2D edgeCoordinate = [self.mapView convertPoint:CGPointMake((CGRectGetWidth(self.region.bounds)/2), 0) toCoordinateFromView:self.region]; //self.region is the circular UIView
CLLocation *edgeLocation = [[CLLocation alloc] initWithLatitude:edgeCoordinate.latitude longitude:edgeCoordinate.longitude];
CLLocation *centerLocation = [[CLLocation alloc] initWithLatitude:self.mapView.centerCoordinate.latitude longitude:self.mapView.centerCoordinate.longitude];
CGFloat radius = [edgeLocation distanceFromLocation:centerLocation]; //is in meters
The image below shows an annotation on the edgeLocation
and on the centerLocation
.
Related Topics
How to Get Image Metadata in iOS
Synchronous Url Request on Swift 2
Auto Login Dropbox Account on Core API Without Login Prompt
Presenting Camera Permission Dialog in iOS 8
How to Show Uialertcontroller from Appdelegate
How to Load Resource in Cocoapods Resource_Bundle
Could Not Load the "" Image Referenced from a Nib in the Bundle with Identifier
Using Secrandomcopybytes in Swift
Exc_Bad_Access Error When Trying to Change Bool Property
How to Use Core Location/Gps Without Any Internet Connection/Disabled Cellular Network
Smart-Search for Parse Usernames in Swift Not Working
What Is a "Delegate" in Objective C's iPhone Development
iOS 5.1 with Xcode 4.2 and Retina in iPad 3
Changed Project Name in Xcode Causing Naming Error
How to Maintain Presenting View Controller's Orientation When Dismissing Modal View Controller
Instantiate View Controller from Storyboard VS. Creating New Instance