In this three part series, I want to show you how you can easily show a user business listings based on their current location. To do so we’ll be using the CoreLocation and MapKit Frameworks. First, we need a way to actually find nearby businesses. There are a number of third-party mapping API’s out there, some with paid subscription, but for a simpler application there is a simpler solution. For the first part in this series we are going to connect to the Google Maps service directly and retrieve xml output with which we will later use to populate MapKit. First, let’s take a look at the actual Google Maps call. It will look something like this:
http://maps.google.com/maps?q=Apple&mrt=yp&sll=26.1209,-80.1444&output=kml
With the Google Maps API there are a number of parameters you can use to get different information. In this case we are set the “q” param as the search keyword. Next we have the “mrt” param. This specifies what type of search you want to do. In the case we are setting this to “yp” which is short for “Yellow Pages”. This means we’ll be returned a list of business information much like a phone book look-up. Next we set the “sll” or “search latitude and longitude”. Finally, we specify the “output” type as “kml”. KML is based on XML standard and is used specifically to describe geographic data. For more on the available parameters, check out Google’s MapKi documentation.
NOTE: Typically JSON is the data format of choice for iphone because of it’s size and ease to parse. In this case, I found that XML was typically smaller in size, so it was my choice.
The first thing you should do is add the MapKit and CoreLocation frameworks to your project. Then, let’s just right into the MapDataParser class.
@interface MapDataParser : NSObject
{
BOOL isPlacemark;
NSXMLParser *mapsDataParser;
NSMutableString *currentElementValue;
NSMutableArray *locationData;
MapLocationVO *currentMapLocation;
NSMutableString *currentElementName;
}
@property (nonatomic, retain) NSMutableArray *locationData;
-(void) getBusinessListingsByKeyword:(NSString *)keyword atLat:(float)lat atLng:(float)lng;
-(void) parseXMLFileAtURL:(NSString *)URL;
@end
Here we define the NSXMLParser and the set the NSXMLParserDelegate for the class. The locationData array is where other classes will later have access to the parsed data. Finally, we have the getBusinessListingsByKeyword method, which accepts the search term and the center latitude and longitude. Let’s take a look at how this is implemented.
@implementation MapDataParser
@synthesize locationData;
-(void) getBusinessListingsByKeyword:(NSString *)keyword atLat:(float)lat atLng:(float)lng
{
// Construct our maps call.
[self parseXMLFileAtURL:[NSString stringWithFormat:@"http://maps.google.com/maps?q=%@&mrt=yp&sll=%g,%g&output=kml", [keyword stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding], lat, lng]];
}
- (void)parseXMLFileAtURL:(NSString *)URL
{
// parse file at url
NSURL *xmlURL = [NSURL URLWithString:URL];
mapsDataParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
mapsDataParser.delegate = self;
[mapsDataParser setShouldProcessNamespaces:NO];
[mapsDataParser setShouldReportNamespacePrefixes:NO];
[mapsDataParser setShouldResolveExternalEntities:NO];
[mapsDataParser parse];
}
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
// Parser has started so create locationData array to store MapLocation value objects
if(self.locationData)
{
[self.locationData removeAllObjects];
}
else
{
self.locationData = [[NSMutableArray alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
// Unable to reach the service, show error alert
NSString * errorString = [NSString stringWithFormat:@"Unable to get map data. (Error code %i )", [parseError code]];
UIAlertView * errorAlert = [[UIAlertView alloc] initWithTitle:@"Error loading content" message:errorString delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[errorAlert show];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:@"Placemark"])
{
// Placemark item found, prepare to save data to new MapLocationVO
currentMapLocation = [[MapLocationVO alloc] init];
isPlacemark = YES;
}
else
{
// Element other than Placemark found, store it for "foundCharacters" check
[currentElementName release];
currentElementName = nil;
currentElementName = [[NSString alloc] initWithString:elementName];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:@"Placemark"])
{
// Placemark element ended, store data
isPlacemark = NO;
NSLog(@"Map Item %i Added", [self.locationData count]);
NSLog(@"Title: %@", currentMapLocation.locationTitle);
NSLog(@"Snippet: %@", currentMapLocation.locationSnippet);
NSLog(@"Coordinates: %@", currentMapLocation.mapLocation);
[self.locationData addObject: currentMapLocation];
[currentMapLocation release];
currentMapLocation = nil;
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if(isPlacemark)
{
// If we are currently in a Placemark element,
// check if current is something we want to store
if ([currentElementName isEqualToString:@"name"])
{
// Store Location Title
if(!currentMapLocation.locationTitle)
{
currentMapLocation.locationTitle = [[NSMutableString alloc] initWithString:string];
}
else
{
[currentMapLocation.locationTitle appendString: string];
}
}
else if ([currentElementName isEqualToString:@"Snippet"])
{
// Replace line breaks with new line
currentMapLocation.locationSnippet = currentMapLocation.locationSnippet = [[NSMutableString alloc] initWithString:[string stringByReplacingOccurrencesOfString:@"
" withString:@"\n"]];
}
else if ([currentElementName isEqualToString:@"coordinates"])
{
// Create a coordinate array to use when allocating CLLocation.
// We will use the CLLocation object later when populating the map
NSArray *coordinatesList = [string componentsSeparatedByString:@","];
currentMapLocation.mapLocation = [[CLLocation alloc] initWithLatitude:[[coordinatesList objectAtIndex:1] floatValue] longitude:[[coordinatesList objectAtIndex:0] floatValue]];
}
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
// XML document is completely parsed, dispatch an event for app to handle data as needed
[[NSNotificationCenter defaultCenter] postNotificationName:@"mapDataParseComplete" object:nil];
[mapsDataParser release]; mapsDataParser = nil;
}
- (void)dealloc
{
[super dealloc];
mapsDataParser.delegate = nil;
[mapsDataParser release]; mapsDataParser = nil;
[currentMapLocation release]; currentMapLocation = nil;
[locationData release]; locationData = nil;
}
@end
Here’s the basic rundown of how the implementation works:
You can see that we always create a new MapLocationVO in the didStartElement delegate method if the element name is “Placemark”. You can see in the XML that “Placemark” is the wrapping element for a business listing. There is also some additional data in the XML we don’t care about, so we set the isPlacemark boolean to true.
If it is not a “Placemark” element, we store the value to check against in the foundCharacters delegate method. In this method, we are checking for the “title”, “Snippet”, and “coordinates” elements. When we encounter these elements we set them accordingly on the current MapLocationVO.
In the didEndElement delegate method, we set isPlacemark to false becuase have exited the wrapping “Placemark” element. We also add the MapLocationVO to our data array and do some clean up.
After we have gathered all our map data the parser reaches the end of the document and fires the parserDidEndDocument method. Here dispatch the “mapDataParseComplete” event.
You can now use the collected data however you need. In part 2 of this series we’ll take a look at how to use CoreLocation to find the user’s location and get the data based on the coordinates. Then, in part 3 we’ll tie it all together by displaying the business listings in a map view with custom markers.
SOURCE FILES
http://www.zen-sign.com/finding-business-listings-and-displaying-with-mapkit-part-1/