I’ve started working a little with the iPad simulator and with its large display it makes sense presenting information on a map view. Because I haven’t found a good example/API for forward geocoding I’ve decided to publish my own. There are a few players offering geocoding services, Yahoo, CloudeMade, Tele Atlas and of course Google. Yahoo and Google are both free but I’ve decided on using Google.
This sample iPad application contains a search bar and a large UIMapView. Search results are visualized on the map with a placemark and by clicking the placemark the map will zoom to the viewport returned from the geocoding service. When I started working with the geocoding service it was still in version two, this last week Google launched version three. Version two will be depricated of course but I had already written the parser for version two so I’ve included it as well in my sample. Of course the API works for iPhone as well. Because the API contains quite a lot of code you will find the code inside the sample project at the bottom of this page.

Using my Forward geocoding API
Using BSForwardGeocoder is pretty straight forward. Example below:
- (void) searchBarSearchButtonClicked:(UISearchBar *)theSearchBar {
NSLog(@"Searching for: %@", searchBar.text);
if(forwardGeocoder == nil)
{
forwardGeocoder = [[BSForwardGeocoder alloc] initWithDelegate:self];
}
// Forward geocode!
[forwardGeocoder findLocation:searchBar.text];
}
-(void)forwardGeocoderFoundLocation
{
if(forwardGeocoder.status == G_GEO_SUCCESS)
{
int searchResults = [forwardGeocoder.results count];
// Add placemarks for each result
for(int i = 0; i < searchResults; i++)
{
BSKmlResult *place = [forwardGeocoder.results objectAtIndex:i];
// Add a placemark on the map
CustomPlacemark *placemark = [[CustomPlacemark alloc] initWithRegion:place.coordinateRegion];
placemark.title = place.address;
[mapView addAnnotation:placemark];
NSArray *countryName = [place findAddressComponent:@"country"];
if([countryName count] > 0)
{
NSLog(@"Country: %@", ((BSAddressComponent*)[countryName objectAtIndex:0]).longName );
}
[countryName release];
}
if([forwardGeocoder.results count] == 1)
{
BSKmlResult *place = [forwardGeocoder.results objectAtIndex:0];
// Zoom into the location
[mapView setRegion:place.coordinateRegion animated:TRUE];
}
// Dismiss the keyboard
[searchBar resignFirstResponder];
}
}
When search has executed the geocoder contains the result and a status code. The status code is from Google and to support both version 2 and 3 of the API there’s an enum containing response codes. If everything goes well the status should be “G_GEO_SUCCESS” and the “results” property will contain an array of BSKmlResult objects which contain the location information returned for the query. Here’s an example query for my home town Stockholm (Google geocoding service version 3) : http://maps.google.com/maps/api/geocode/xml?address=stockholm&sensor=false.
The details for the service can be found here: http://code.google.com/apis/maps/documentation/geocoding/.
There are multiple arguments you can pass to the search url, you should read the geocoding documentation and check the search url in the application before you implement this in your own app.
BSForwardGeocoder
#import
#import "BSGoogleV2KmlParser.h"
#import "BSGoogleV3KmlParser.h"
// Enum for geocoding status responses
enum {
G_GEO_SUCCESS = 200,
G_GEO_BAD_REQUEST = 400,
G_GEO_SERVER_ERROR = 500,
G_GEO_MISSING_QUERY = 601,
G_GEO_UNKNOWN_ADDRESS = 602,
G_GEO_UNAVAILABLE_ADDRESS = 603,
G_GEO_UNKNOWN_DIRECTIONS = 604,
G_GEO_BAD_KEY = 610,
G_GEO_TOO_MANY_QUERIES = 620
};
@protocol BSForwardGeocoderDelegate
@required
-(void)forwardGeocoderFoundLocation;
@optional
-(void)forwardGeocoderError:(NSString *)errorMessage;
@end
@interface BSForwardGeocoder : NSObject {
NSString *searchQuery;
NSString *googleAPiKey;
int status;
NSArray *results;
iddelegate;
}
-(id) initWithDelegate:(id)del;
-(void) findLocation:(NSString *)searchString;
@property (assign) iddelegate;
@property (nonatomic, retain) NSString *searchQuery;
@property (nonatomic, readonly) int status;
@property (nonatomic, retain) NSArray *results;
@end
BSKmlResult
The result class is the same for both version 2 and 3 of the service. A big difference in the returned information between the versions is the way address components are returned, in version three more information is returned and therefore I’ve created another class to store the address component information. The properties: countryNameCode, countryName, subAdministrativeAreaName and localityName are for version two only. For version three all address information is stored in the “addressComponents” array (contains BSAddressComponent objects). Because there is really no reason to use version 2 anymore you probably want to remove this code for your own application.
#import
#import
#import "BSAddressComponent.h"
@interface BSKmlResult : NSObject {
NSString *address;
NSString *countryNameCode;
NSString *countryName;
NSString *subAdministrativeAreaName;
NSString *localityName;
float viewportSouthWestLat;
float viewportSouthWestLon;
float viewportNorthEastLat;
float viewportNorthEastLon;
float boundsSouthWestLat;
float boundsSouthWestLon;
float boundsNorthEastLat;
float boundsNorthEastLon;
float latitude;
float longitude;
float height;
NSInteger accuracy;
NSArray *addressComponents;
}
@property (nonatomic, retain) NSString *address;
@property (nonatomic, assign) NSInteger accuracy;
@property (nonatomic, retain) NSString *countryNameCode;
@property (nonatomic, retain) NSString *countryName;
@property (nonatomic, retain) NSString *subAdministrativeAreaName;
@property (nonatomic, retain) NSString *localityName;
@property (nonatomic, retain) NSArray *addressComponents;
@property (nonatomic, assign) float latitude;
@property (nonatomic, assign) float longitude;
@property (nonatomic, assign) float viewportSouthWestLat;
@property (nonatomic, assign) float viewportSouthWestLon;
@property (nonatomic, assign) float viewportNorthEastLat;
@property (nonatomic, assign) float viewportNorthEastLon;
@property (nonatomic, assign) float boundsSouthWestLat;
@property (nonatomic, assign) float boundsSouthWestLon;
@property (nonatomic, assign) float boundsNorthEastLat;
@property (nonatomic, assign) float boundsNorthEastLon;
@property (readonly) CLLocationCoordinate2D coordinate;
@property (readonly) MKCoordinateSpan coordinateSpan;
@property (readonly) MKCoordinateRegion coordinateRegion;
-(NSArray*)findAddressComponent:(NSString*)typeName;
@end
To make it somewhat simple to find address components I’ve added a method that will search for components for you. Using version 3 of the geocoding service you will get the country name using this code:
NSArray *countryName = [BSKmlResultPlace findAddressComponent:@"country"];
if([countryName count] > 0)
{
NSLog(@"Country: %@", ((BSAddressComponent*)[countryName objectAtIndex:0]).longName );
}
[countryName release];
There are also properties to make the result simple to use with a MKMapView. The “coordinate” property returns a CLLocationCoordinate2D object, “coordinateSpan” calculates and returns a MKCoordinateSpan object for setting the map viewport. The “coordinateRegion” combines both coordinate and coordinateSpan returning a MKCoordinateRegion object that can be used to directly move your MKMapView to the right place.
BSAddressComponent
#import
@interface BSAddressComponent : NSObject {
NSString *longName;
NSString *shortName;
NSArray *types;
}
@property (nonatomic, retain) NSString *longName;
@property (nonatomic, retain) NSString *shortName;
@property (nonatomic, retain) NSArray *types;
Learned from[url]http://blog.sallarp.com/ipad-iphone-forward-geocoding-api-google/[/url]
No comments:
Post a Comment