Tuesday, December 29, 2009

The Curious Case of MKReverseGeocoder

By:
Lately I've been working on an iPhone application that includes the use of a Google map by way of the MapKit libraries.

When showing the user's current location, there are a few ways to go about it. If you simply want the MapView instance to take care of it, you can set "showsUserLocation" to true and the MapView instance will use the GPS hardware to locate your position and place the familiar blue dot with the accuracy circle around it on the map. However, if you want to zoom in on that position, it's up to you to manage.

If you want more control or if you're placing a custom "placemark" (pushpin, etc.) onto the map, there are two main ways of doing it. The easiest and most often quoted method in tutorials involves the use of the MKReverseGeocoder library. What this does is allow you create an instance of the MKReverseGeocoder class and initialize it with a CLLocationCoordinate2D object. You then assign a delegate and then tell the MKReverseGeocoder instance to start via its start method.

The two delegate methods you're interested in when dealing with the MKReverseGeocoder library are:
-(void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error
and
-(void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark
What MKReverseGeocoder does is take a given location object (containing geocoordinate information in the form of latitude and longitude) and make a call to Google's API using an HTTP POST in order to determine what the closest address is. If the lookup is successful, didFindPlacemark: is called, but if it's note, didFailWithError: is called.

When I initially started development on this application, I never saw didFailWithError: being called for locations I gave to the MKReverseGeocoder instance. However, about a few weeks ago, I started noticing that I was consistently getting didFailWithError: ONLY after 5PM PST. A coordinate that successfully resolved to an address during the day mysteriously stopped resolving after 5PM PST. The error that was being returned was:
PBRequesterErrorDomain error 6001
While this isn't very descriptive, I did a bit of searching online (using Google ironically) and found that a few other developers have started to see this behavior recently as well.

What turns out to be happening is that the HTTP POST that is being done by the MKReverseGeocoder library to Google's servers is being met with a 503 Service Unavailable error at certain times of the day (for me it's consistently between 5PM and around 9AM PST).

I have verified this by capturing the packets when using either the iPhone simulator or my iPhone while on my local WiFi network or on 3G. The following reconstruction of the packets in question:

POST /glm/mmap HTTP/1.1
Host: www.google.com
User-Agent: Apple iPhone v7D11 Maps v3.1.2
Accept: */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 89
Connection: keep-alive

../+.N.8.Z..en_US.&com.apple.iphone simulator-com.foobar..
3.1.2.7D11>...
.. ............HTTP/1.1 200 OK
Content-Type: application/binary
Content-Length: 13
Date: Mon, 28 Dec 2009 02:51:50 GMT
Expires: Mon, 28 Dec 2009 02:51:50 GMT
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Server: GFE/2.0

..>....
.359.POST /glm/mmap HTTP/1.1
Host: www.google.com
User-Agent: MyApp/1.0 CFNetwork/459 Darwin/9.8.0
Accept: */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 181
Connection: keep-alive

../+.N.8.Z..en_US.&com.apple.iphone simulator-com.foobar..
3.1.2.7D11)...k.i
=
.7D11..iPhone OS.#2:FeAFHCc8yFilvsrw:7TjBQIp9ijRdhsXJ*.en_US.
.com.foobar."...


......P..".
...... .HTTP/1.1 503 Service Unavailable
Content-Type: text/html; charset=UTF-8
Date: Mon, 28 Dec 2009 02:51:54 GMT
Expires: Mon, 28 Dec 2009 02:51:54 GMT
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Server: GFE/2.0
Transfer-Encoding: chunked

a1
Service Unavailable
Error 503
As you can see, what MKReverseGeocoder is actually doing is making an HTTP POST call to http://google.com/glm/mmap.

Now I want to add that I am definitely not hitting some limit on the number of requests that I can be making since I have gone more than a day without testing this and then waited until the evening to make my first request which then failed, but in the morning after, it did not fail.

Now if Google is selectively turning off the web service that MKReverseGeocoder is relying on internally during a specific time period each day, it is something you definitely want to be aware of when using MKReverseGeocoder. Why they might be doing this is anyone's guess. In the meantime, I have submitted a bug report to Apple to see what they say. I have also made supporting posts on both Apple's private developer forum and public message boards where this behavior is also being seen, one of which includes:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/31883-pbrequestererrordomain-errors-reverse-geocoding.html#post155793
I have also verified that other iPhone applications already in the App Store are also exhibiting this behavior. If you download and install the "Gowalla" application, you will see that they are relying on the MKReverseGeocoder library to resolve your current location's address in their gold GPS bar at the bottom of the screen. During the times of 5PM - 9AM PST, I am noticing that it is "stuck" saying "Finding address...". At other times it is successfully resolving the address of your current location as found by the GPS.

What can you do in the meantime? There is another way to place placemarks on the map if you have a geolocation coordinate that doesn't involve the use of MKReverseGeocoder but instead uses the addAnnotation method in the MapView class:
- (void)addAnnotation:(id )annotation
In order to create an annotation object to be added to the map, you'll want to create a MapAnnotation class that inherits from MKAnnotation. The members of this class should include:
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
MapAnnotationType annotationType;
where annotationType is an enumeration where you can customize different placemark graphics for different types.

You also need to define a method with which to use during the object's initialization:
-initWithCoordinate:(CLLocationCoordinate2D)inCoord;
All you then need to do is instantiate your MapAnnotation class, passing the geocoordinate where you want the placemark to appear on the map. Then add this instance to your MapView instance via the MapView's addAnnotation method. The MKMapViewDelegate methods that you should be listening to in the class that you're using for all of this will then message the following method:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation;
At this point, you can check the annotation type to show custom placemark graphics, or you could just place a generic color pin, etc. Either way, you will need to return an instance of the MKAnnotationView class for your placemark to appear on the map.

Unfortunately, if you have a geocoordinate and absolutely need to display the address but don't know what it is, if you're relying on the Google functionality that is used by the MapKit library, I'm not sure what you can do at the moment. There are other alternatives to using MapKit that I won't go into here but beware of using MapKit if you're depending on resolving a physical address given a geocoordinate.

Any input is welcome and I will update this post with any updates that I may find in the future.

Update:

"edpark" on the iPhone dev SDK forum makes a good point:
Here's my theory. We know that Google throttles reverse-geocoding requests by IP. My suspicion is that this throttling is reset at midnight each night and that MKReverseGeocoder passes its requests through a centralized caching proxy; since that proxy would appear as a single IP to Google, it stops working at a certain point during the day.

6 comments:

Anonymous said...

I am also seeing this behaviour, and the reverse geocoding is exactly what I need in my app, I think for more stability, it's better turn to other geocode services.

Des said...

+1

Still happening in the new year. Surprised that neither Apple nor Google have stepped up to address the issue yet!

Anonymous said...

Personally, I think this is completely unacceptable. This is a public, supported method in a core iPhone framework class! The fact that it is something that now only "sometimes" works should be a high priority for Apple to resolve.

Whats next, the Google maps themselves will stop working in the evenings? YouTube videos won't display any more unless you're surfing in off-hours?

The most ridiculous thing here is that apps that rely on this functionality have little remedy to pursue. Short of incurring the development cost of switching to an external reverse geocoding call (i.e. the Google REST services, or likewise), the best we can do is to file bug reports and cross our fingers, hoping that maybe someday this will get fixed...

Anonymous said...

Yeah! I could not figure this out for the life of me for a while either. I finally came across another person saying the same thing as you. Very frustrating, as I do need the reverse geocoding capabilities.

There's a forum for Google, I forget where, that you post any errors/bugs and this one was on there with about a dozen people experiencing the same thing...so hopefully it receives some attention soon, because it definitely is unacceptable.

Bandusch said...

Hi i share the same issue with you guys, but i made a curious discovery with using it.

I tried out the GPS Function and went outside with my Laptop to test the App directly outside.

Here it comes:
If a Device is connected to my Laptop, i always get a working MKReverseGeocoder! If i plug off the device i get the Error... nice work Google!

Bandusch said...

Hi i share the same issue with you guys, but i made a curious discovery with using it.

I tried out the GPS Function and went outside with my Laptop to test the App directly outside.

Here it comes:
If a Device is connected to my Laptop, i always get a working MKReverseGeocoder! If i plug off the device i get the Error... nice work Google!