iosmkmapviewmkoverlaymkpolygon

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.


Solution

  • 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:

    1. The initWithModel method doesn't call [super init] (assuming it's an NSObject subclass).
    2. 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.