Size an MKMapView to Fit its Annotations in iOS Without Futzing with Coordinate Systems

map viewAn oddity of MKMapView is that it always defaults to showing the western hemisphere of the Earth. That’s almost certainly not what you want. Instead, you have probably set an array of id <MKAnnotation> objects which you want to appear as pins on the map. If the pins were evenly distributed over the USA, then great. Otherwise, the MapView is not at all zoomed the way you want. It baffles me a map view doesn’t just zoom itself by default when you set annotations, but it doesn’t.

One approach might be to loop over all of your annotations and figure out what the max and min x and y values are across them all. Then get the center point of the map view and do some math to convert the coordinates and scale the map view.

Alternatively, MapKit has API that is separate from the map view itself for manipulating rectangles and regions which can do the job automatically.

The main problem is that id <MKAnnotation> objects contain a CoreLocation coordinate – CLLocationCoordinate2D – which represents a GPS coordinate while MapView has an MKCoordinateRegion which has a CLLocationCoordinate2D defining its center but uses an MKCoordinateRegion span struct to define the zoom level of the map view in terms of degrees of arc.

MapKit provides a mix of object-oriented API and C functions to convert an array of CLLocationCoordinate2D structs from id <MKAnnotation> objects into an MKCoordinateRegion suitable for setting the region in a MKMapView. These transformations are straightforward and don’t require fiddling with converting between coordinate systems.

Here is the code in a map view controller

#define MINIMUM_ZOOM_ARC 0.014 //approximately 1 miles (1 degree of arc ~= 69 miles)
#define ANNOTATION_REGION_PAD_FACTOR 1.15
#define MAX_DEGREES_ARC 360
//size the mapView region to fit its annotations
- (void)zoomMapViewToFitAnnotations:(MKMapView *)mapView animated:(BOOL)animated
{ 
    NSArray *annotations = mapView.annotations;
    int count = [mapView.annotations count];
    if ( count == 0) { return; } //bail if no annotations
    
    //convert NSArray of id <MKAnnotation> into an MKCoordinateRegion that can be used to set the map size
    //can't use NSArray with MKMapPoint because MKMapPoint is not an id
    MKMapPoint points[count]; //C array of MKMapPoint struct
    for( int i=0; i<count; i++ ) //load points C array by converting coordinates to points
    {
        CLLocationCoordinate2D coordinate = [(id <MKAnnotation>)[annotations objectAtIndex:i] coordinate];
        points[i] = MKMapPointForCoordinate(coordinate);
    }
    //create MKMapRect from array of MKMapPoint
    MKMapRect mapRect = [[MKPolygon polygonWithPoints:points count:count] boundingMapRect];
    //convert MKCoordinateRegion from MKMapRect
    MKCoordinateRegion region = MKCoordinateRegionForMapRect(mapRect);
    
    //add padding so pins aren't scrunched on the edges
    region.span.latitudeDelta  *= ANNOTATION_REGION_PAD_FACTOR;
    region.span.longitudeDelta *= ANNOTATION_REGION_PAD_FACTOR;
    //but padding can't be bigger than the world
    if( region.span.latitudeDelta > MAX_DEGREES_ARC ) { region.span.latitudeDelta  = MAX_DEGREES_ARC; }
    if( region.span.longitudeDelta > MAX_DEGREES_ARC ){ region.span.longitudeDelta = MAX_DEGREES_ARC; }
    
    //and don't zoom in stupid-close on small samples
    if( region.span.latitudeDelta  < MINIMUM_ZOOM_ARC ) { region.span.latitudeDelta  = MINIMUM_ZOOM_ARC; }
    if( region.span.longitudeDelta < MINIMUM_ZOOM_ARC ) { region.span.longitudeDelta = MINIMUM_ZOOM_ARC; }
    //and if there is a sample of 1 we want the max zoom-in instead of max zoom-out
    if( count == 1 )
    { 
        region.span.latitudeDelta = MINIMUM_ZOOM_ARC;
        region.span.longitudeDelta = MINIMUM_ZOOM_ARC;
    }
    [mapView setRegion:region animated:animated];
}

- (void)viewWillAppear:(BOOL)animated
{
    [self zoomMapViewToFitAnnotations:self.mapView animated:animated];
    //or maybe you would do the call above in the code path that sets the annotations array
}
Advertisement

Targeted Marketing Considered Harmful

I’m concerned that the trend of monetization on the Web and in the Android ecosystem is overwhelmingly based on marketing revenue for free services. In these transactions, the product is not the app or website. You are the product being sold. The product you use is the bait to aggregate a lot of attention on the advertising that is sold and displayed through the app or website. The more information the tech company that is offering the free service knows about you, the more precisely they can target advertisements and the larger fee they can command for impressions.

This is bad for us as users.

I’m not particularly concerned about privacy today. Not yet. There may come a day when passive data about your online behavior informs things like what insurance or jobs you are eligible for. That’s not the problem I’m talking about. I’m concerned about something more insidious. What if the marketing works?

In fact, I’m sure that it does work. In order for this model of free services with advertising to work out financially, the cost of the service must be vastly smaller than the cost of the products you buy because of the marketing. Otherwise, the companies doing the marketing would not see a return on investment (ROI) commensurate with the cost of placing the advertising. The fact is, ad-supported services exist because the value of what you purchase due to being exposed to the advertising is far, far greater than the cost of the service in the first place.

The basic premise of advertising is to sell you something that you would not otherwise have purchased. It works by making you feel want something you didn’t want before. In other words, it affects your well-being and happiness. Because you want this new thing, you are less happy until you buy it.

The basic transaction of a free, ad-supported service is not trading “your attention for a free service” as suggested by Leo Laporte. The transaction is that you are trading your sense well-being and (in aggregate) your money – indirectly – for a free service. In aggregate, this is a significant effect but we don’t notice because we are constantly bombarded with advertising. The better and more targeted the advertising, the worse it is. The assertion that more targeted advertising is better for both advertiser and recipient is totally wrong. Its better for the advertiser and worse for the recipient because it is more effective at making you want the thing and therefore less happy with what you have and who you are today.

I noticed this for the first time when I returned to the US from Africa after Peace Corps where I was exposed to essentially zero advertising. I have found that I have been able to greatly reduce stress and anxiety in my own life by doing simple things to limit my exposure to advertising – the most basic was deciding to eliminate cable and broadcast television 12 years ago. We still enjoy TV shows but we buy them on DVD or Amazon streaming which are both essentially ad-free platforms. In general, I prefer freemium services like Flickr or outright pay-for services and apps because the relationship that I want is to be the customer and not the product. With the exception of digital periodicals like the Economist and NY Times apps, pay-for services are almost exclusively ad-free. That makes sense because the user is the customer not the product.

I think its high time the Internet business community comes up with some new and better strategies for monetization than tracking and ever more targeted advertising. Ad-supported is not purely benign. It’s a strategy that turns your users into your product. It puts internet companies in the business of ever more invasive profiling of their users. The pressure to aggregate data about users inevitably leads to breaches of trust and repeated bad press. After a sufficient kerfuffle, governments get involved and will start imposing regulations. Ultimately, it’s a very dangerous game.

%d bloggers like this: