How to overlay a circle on an iOS map - ios

I've got a radius and a location.
This is how I'm trying to get the bounding rectangle of the circle.
- (MKMapRect)boundingMapRect{
CLLocationCoordinate2D tmp;
MKCoordinateSpan radiusSpan = MKCoordinateRegionMakeWithDistance(self.coordinate, 0, self.radius).span;
tmp.latitude = self.coordinate.latitude - radiusSpan.longitudeDelta;
tmp.longitude = self.coordinate.longitude - radiusSpanSpan.longitudeDelta;
MKMapPoint upperLeft = MKMapPointForCoordinate(tmp);
MKMapRect bounds = MKMapRectMake(upperLeft.x, upperLeft.y, self.radius * 2, self.radius * 2);
return bounds;
}
MKMapRectMake(...) seems to want width and height measured in Map points. How do I convert the radius to that?
In the end I'm rendering it like this:
MKMapRect theMapRect = [self.overlay boundingMapRect];
CGRect theRect = [self rectForMapRect:theMapRect];
CGContextAddEllipseInRect(ctx, theRect);
CGContextFillPath(ctx);
The radius doesn't seem to equal meters on the map in the end and also the distance doesn't seem to be measured correctly. How to do it right?
I would be really thankful for every hint.

You should be using MKCircle instead. Do something like:
CLLocationDistance fenceDistance = 300;
CLLocationCoordinate2D circleMiddlePoint = CLLocationCoordinate2DMake(yourLocation.latitude, yourLocation.longitude);
MKCircle *circle = [MKCircle circleWithCenterCoordinate:circleMiddlePoint radius:fenceDistance];
[yourMapView addOverlay: circle];
And adopt the MKMapViewDelegate Method below and do something like this:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
MKCircleView *circleView = [[[MKCircleView alloc] initWithCircle:(MKCircle *)overlay] autorelease];
circleView.fillColor = [[UIColor redColor] colorWithAlphaComponent:0.9];
return circleView;
}

For iOS7 :
Same as tdevoy in viewDidLoad :
CLLocationDistance fenceDistance = 300;
CLLocationCoordinate2D circleMiddlePoint = CLLocationCoordinate2DMake(yourLocation.latitude, yourLocation.longitude);
MKCircle *circle = [MKCircle circleWithCenterCoordinate:circleMiddlePoint radius:fenceDistance];
[yourMapView addOverlay: circle];
But the delegate method (because mapView:viewForOverlay: is deprecated) :
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id < MKOverlay >)overlay
{
MKCircleRenderer *circleR = [[MKCircleRenderer alloc] initWithCircle:(MKCircle *)overlay];
circleR.fillColor = [UIColor greenColor];
return circleR;
}

Related

How to change MKCircle radius on mapview with animation?

I want to change the circle radius with animation like this:
https://www.youtube.com/watch?v=j0s5DUnEy3k
Here's my code:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
if (locations != nil) {
userLocation = locations.firstObject;
CLLocationDegrees lat = 0.05;
CLLocationDegrees lon = 0.05;
MKCoordinateSpan span = MKCoordinateSpanMake(lat, lon);
CLLocationCoordinate2D location = CLLocationCoordinate2DMake(userLocation.coordinate.latitude, userLocation.coordinate.longitude);
MKCoordinateRegion region = MKCoordinateRegionMake(location, span);
[self.myMapView setRegion:region animated:true];
MKCircle *circle = [MKCircle circleWithCenterCoordinate:userLocation.coordinate radius:1000];
[self.myMapView addOverlay:circle];
}
}
I create user's location annotation and add a circle at user's location center
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay
{
MKCircleRenderer *circleView = [[MKCircleRenderer alloc] initWithOverlay:overlay];
circleView.strokeColor = [UIColor redColor];
circleView.fillColor = [[UIColor redColor] colorWithAlphaComponent:0.3];
circleView.lineWidth = 0.2;
return circleView;
}
and customized the circle at rendererForOverlay function
So, how can I change the circle radius with animation?
Anyone have idea to do this?

Why MKCircle is not displayed on MKMapView iOS8

I create new overlays like this:
MKCircle *circle = [MKCircle circleWithCenterCoordinate:region.coordinate radius:region.radius];
[self.mapView addOverlay:circle];
also I implemented delegate method:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
MKCircleRenderer *circleRenderer = [[MKCircleRenderer alloc] init];
circleRenderer.fillColor = [UIColor greenColor];
circleRenderer.alpha = 1.f;
return circleRenderer;
}
both parts of code are called, mapView != nil at that moment, it's delegate set,
but I cannot see the circle on my map.
What am I doing wrong?
As per #Rob suggestion you need to init MKCircleRenderer using other method initWithCircle.
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
MKCircleRenderer *circleRenderer = [[MKCircleRenderer alloc] initWithCircle:overlay];
circleRenderer.fillColor = [UIColor greenColor];
circleRenderer.alpha = 1.f;
return circleRenderer;
}
Also make sure that fence distance is proper enough to visible the circle in map.
For example:
CLLocationDistance fenceDistance = 100000;
MKCircle *circle = [MKCircle circleWithCenterCoordinate:region.coordinate radius:fenceDistance];
[self.mapView addOverlay:circle];
Rather than init, call the MKCircleRenderer method initWithCircle.
Obviously, make sure the delegate of the map view is set, that your code that adds the overlay and that instantiates the renderer is called at all, etc., but initWithCircle is the likely culprit.

MKOverlayRenderer not rendering

I'm having trouble getting an MKPolygonRenderer to appear over my map. I have a class MapViewController that contains an MKMapView, and create CustomMapOverlay instances to render over the MKMapView.
MapViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
self.mapView.delegate = self;
self.mapView.showsUserLocation = YES;
}
// ...
// Later, I retrieve some models and generate CustomMapOverlay instances from them...
for (Model *model in models) {
CustomMapOverlay *customMapOverlay = [[CustomMapOverlay alloc] initWithModel:model];
[self.mapView addOverlay:customMapOverlay];
}
// ...
// Implement MKMapViewDelegate protocol
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
MKPolygonRenderer *polygonRenderer = [[MKPolygonRenderer alloc] initWithOverlay:overlay];
polygonRenderer.lineWidth = 2;
polygonRenderer.strokeColor = [UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:1.0];
polygonRenderer.fillColor = [UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.5];
return polygonRenderer;
}
CustomMapOverlay.m:
#implementation CustomMapOverlay
// Required by MKOverlay protocol
#synthesize coordinate,
boundingMapRect;
- (instancetype)initWithModel:(Model *)model {
coordinate = CLLocationCoordinate2DMake(model.latitude, model.longitude);
double radiusInPoints = MKMapPointsPerMeterAtLatitude(model.latitude) * model.radius;
boundingMapRect = MKMapRectMake(model.latitude, model.longitude, radiusInPoints, radiusInPoints);
return self;
}
#end
mapView:rendererForOverlay is getting called, and inspecting the overlay in the debugger I see a coordinate within the map's current on-screen bounds and what seems like a reasonable boundingMapRect (though I'm not sure what "map points" are, I'm trusting that MKMapPointsPerMeterAtLatitude method to do what it says it does).
But, no polygons appear on the map.
UPDATE:
I realize now that I'm attempting to render polygons without creating them. Instead of CustomMapOverlay, then, I'm now generating MKPolygon overlays like this:
CLLocationCoordinate2D centerCoordinate = CLLocationCoordinate2DMake(model.latitude, model.longitude);
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(centerCoordinate, model.radius, model.radius);
int numCoords = 4;
CLLocationCoordinate2D *coords = malloc(sizeof(CLLocationCoordinate2D) * numCoords);
coords[0] = CLLocationCoordinate2DMake((region.center.longitude - 0.5*region.span.longitudeDelta), (region.center.latitude + 0.5*region.span.latitudeDelta));
coords[1] = CLLocationCoordinate2DMake((region.center.longitude + 0.5*region.span.longitudeDelta), (region.center.latitude + 0.5*region.span.latitudeDelta));
coords[2] = CLLocationCoordinate2DMake((region.center.longitude + 0.5*region.span.longitudeDelta), (region.center.latitude - 0.5*region.span.latitudeDelta));
coords[3] = CLLocationCoordinate2DMake((region.center.longitude - 0.5*region.span.longitudeDelta), (region.center.latitude - 0.5*region.span.latitudeDelta));
MKPolygon *polygon = [MKPolygon polygonWithCoordinates:coords count:numCoords];
free(coords);
[self.mapView addOverlay:polygon];
However, now mapView:rendererForOverlay is no longer getting called at all.
In the updated code which creates an MKPolygon, the coordinates in the coords array are backwards. For example, this line:
coords[0] = CLLocationCoordinate2DMake(
(region.center.longitude - 0.5*region.span.longitudeDelta),
(region.center.latitude + 0.5*region.span.latitudeDelta));
should be:
coords[0] = CLLocationCoordinate2DMake(
(region.center.latitude + 0.5*region.span.latitudeDelta,
(region.center.longitude - 0.5*region.span.longitudeDelta));
In the CLLocationCoordinate2DMake function, the first parameter is latitude, then longitude.
Because the coordinates are backwards, they may either be completely invalid or in the wrong location.
The rendererForOverlay delegate method will only get called if the overlay's boundingMapRect (which MKPolygon will automatically define based on the given coordinates) is in the currently displayed area of the map. But if the coordinates are invalid or in the wrong location, the boundingMapRect will be invalid as well.
By the way, in the original code which used CustomMapOverlay, there were at least these two issues:
The initWithModel method doesn't call [super init] (assuming it's an NSObject subclass).
The boundingMapRect is not calculated correctly. The MKMapRectMake function takes MKMapPoint values but the code is passing latitude and longitude in degrees. An MKMapPoint is not the same as a CLLocationCoordinate2D. You can convert a CLLocationCoordinate2D to an MKMapPoint using the MKMapPointForCoordinate function. See MKMapPointForCoordinate returning invalid coordinates and Map Coordinate Systems in the documentation for some more info.

Incorrect user location shown on MKMapView after scaling the map programmatically

I have MKMapView which have UserTrackingMode = MKUserTrackingModeFollow,
and I have adding a circle overlay to show a region of the certain diameter at user location.
Also user can change the diameter of the region so I want to scale the map to insure whole region/circle is shown on that portion of the map.
The problem I have now is that scaling the map number of times by setting region results in incorrect user location annotation - it is moved from the correct location.
I cannot understand why is that happens, I see in the debugger that the mapView.userLocation property have correct coordinates.
But once new update is happens or I move the map manually - the annotation jumps to the correct place.
This is the code:
- (void)addCircleWithRadius:(double)radius andCoordinate:(CLLocationCoordinate2D)coordinate
{
_regionCircle = [MKCircle circleWithCenterCoordinate:coordinate radius:radius];
[_regionCircle setTitle:#"Region"];
MKCoordinateRegion region;
region.center.latitude = coordinate.latitude;
region.center.longitude = coordinate.longitude;
region.span.latitudeDelta = 0.00002 * _regionCircle.radius;
region.span.longitudeDelta = 0.00002 * _regionCircle.radius;
MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits: region];
[self.mapView setRegion:adjustedRegion animated:TRUE];
_circleView = nil;
[self.mapView addOverlay:_regionCircle];
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
if(!_circleView)
{
_circleView = [[MKCircleView alloc] initWithCircle:overlay];
_circleView.strokeColor = [UIColor blueColor];;
_circleView.fillColor = [UIColor blueColor];
_circleView.alpha = 0.25;
_circleView.lineWidth = 2.0;
}
return _circleView;
}
- (IBAction)regionSliderValueChanged:(id)sender
{
[self updateRadiusCircle];
}
- (void) updateRadiusCircle
{
[self.mapView removeOverlays:self.mapView.overlays];
CLLocationCoordinate2D myCoordinate = {_currentLocation.coordinate.latitude, _currentLocation.coordinate.longitude};
[self addCircleWithRadius:self.radiusSlider.value andCoordinate:myCoordinate];
}
I have published the video on YouTube to better understand the issue.
https://www.youtube.com/watch?v=474gdjkGwJA

MKCircle is not updating radius but it's translating

I've to draw an MKCicle into an MKMapView. Then I've to re-draw it when user, through a slider, change the radius. I remove it and I re-create it, re-adding it to the map.
But instead of do what I'm expecting, I see the MKCircle translating over the map, maintaining the same size.
Here's my code:
- (MKOverlayView *)mapView:(MKMapView *)map viewForOverlay:(id)overlay
{
MKOverlayView* overlayView = nil;
if(overlay == self.circle)
{
//if we have not yet created an overlay view for this overlay, create it now.
if(nil == self.circleView)
{
self.circleView = [[[MKCircleView alloc] initWithCircle:self.circle] autorelease];
self.circleView.fillColor = [UIColor blueColor];
self.circleView.strokeColor = [UIColor blueColor];
self.circleView.alpha = 50;
self.circleView.lineWidth = 2;
}
overlayView = self.circleView;
}
return overlayView;
}
-(void)drawPolygonWithLocation
{
[self.mapView removeOverlay: self.circle];
MKCoordinateRegion region;
region.center.latitude = self.geofenceLocation.latitude;
region.center.longitude = self.geofenceLocation.longitude;
region.span.latitudeDelta = 0.005;
region.span.longitudeDelta = 0.005;
MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits: region];
[self.mapView setRegion:adjustedRegion animated:TRUE];
self.radius = (double)(slRadius.value);
NSLog(#"Raggio: %f", self.radius);
NSLog(#"Lat: %f, Lon: %f", region.center.latitude, region.center.longitude);
self.circle = [MKCircle circleWithCenterCoordinate:self.geofenceLocation.coordinate radius: self.radius];
NSLog(#"CIRCLE: radius %f Lat: %f, Lon: %f", self.circle.radius, self.circle.coordinate.latitude, self.circle.coordinate.longitude);
[self.mapView addOverlay:self.circle];
}
-(IBAction)updateRadius:(id)sender
{
[self drawPolygonWithLocation];
}
The NSLog is writing into the console right values, the center doesn't change and the radius changes according to the user input.
But, again, the MKCircle translates going on the north-west.
Thanks in advance,
Samuel Rabini
Fixed.
I just add
self.circleView = nil;
before the
[self.mapView addOverlay:self.circle];
in this way it works fine.
Samuel

Resources