I pull a list of address from a DB. Where the size of the list could be 0-n. But where n is usually 50-60.
I am trying to discover the optimal way to add all of these locations to a map view.
The method that I am currently using goes as follows.
I create a mutableArray (annotations) to hold all of my annotations (I remove and add them back frequently so it is best to keep them all on hand for fast addition/removal).
I run through my loaded list of addresses (from DB) passing the address to a reverse geocoder to obtain the lat and long.
Check to see if the lat and long is 0,0 (meaning I was rate limited by google).
Y - If so I flag that there has been a rate ban and add @"" to the annotations array. It is essential to keep the annotations in order for proper reference when adding and removing.
N - If not then I add that annotation to the map and the annotations array.
IF there has been a rate ban, I run through the annotations array looking for @"", when one is found, proceed with steps 2-5.
I recursively call this function (Step 5) until all annotation are added. But it seems inefficient.
Has anyone discovered a way that would be more time and memory efficient?
This is the initial call the kickoff the annotation loading:
- (void)addLocations {
/*
Setup an array to hold all annotations, this is done for fast removal/addition when filtering which happens often. So far, through testings, has shown to be the optimal way.
*/
annotations = [[NSMutableArray alloc] init];
bool needsReload = NO;
//Loop through all locations parsed from the DB
for (int i = 0; i < [[[[self appDelegate] rssParser]rssItems] count]; i++) {
NSString *temp = [NSString stringWithFormat:@"%@, %@, %@", [[[[[self appDelegate]rssParser]rssItems] objectAtIndex:i] Address], [[[[[self appDelegate] rssParser]rssItems]objectAtIndex:i]City], [[[[[self appDelegate] rssParser]rssItems]objectAtIndex:i]State]];
CLLocationCoordinate2D tempLocation = [self addressLocation:temp];
if (tempLocation.latitude == 0.0) {
//We have been rate banned and flag for a re-run through locations.
[[[[[self appDelegate] rssParser]rssItems]objectAtIndex:i] setLocation:nil];
needsReload = YES;
[annotations addObject:@""];
} else {
/*
Set location of property. Done for property use in other locations of app.
*/
CLLocation *tempLoc = [[CLLocation alloc] initWithLatitude:tempLocation.latitude longitude:tempLocation.longitude];
[[[[[self appDelegate] rssParser]rssItems]objectAtIndex:i] setLocation:tempLoc];
//Create new annotation with proper coordinates
MapAnnotation *addAnnotation = [[MapAnnotation alloc] initWithCoordinate:tempLocation];
addAnnotation.name = [[[[[self appDelegate] rssParser]rssItems]objectAtIndex:i]Address];
// Check to see if location has an image associated with it.
if ([[[[[[self appDelegate] rssParser]rssItems]objectAtIndex:i] imageURLs] count] > 0 )
addAnnotation.thumbURL = [[[[[[self appDelegate] rssParser]rssItems]objectAtIndex:i] imageURLs] objectAtIndex:0];
else
addAnnotation.thumbURL = nil;
addAnnotation.tag = i;
addAnnotation.description = [NSString stringWithFormat:@"%@, %@ | $%@",[[[[[self appDelegate] rssParser]rssItems]objectAtIndex:i]City] , [[[[[self appDelegate] rssParser]rssItems]objectAtIndex:i]State] ,[[[[[[self appDelegate]rssParser]rssItems] objectAtIndex:i] Deposit] stringByReplacingOccurrencesOfString:@".00" withString:@""]];
//Add annotation to "annotations" array for fast addition/removal when filtering.
[annotations addObject:addAnnotation];
[self._mapView addAnnotation:addAnnotation];
//Doesn't seem necessary in ARC but somehow helps with memory on iPhone 3GS
temp = nil;
addAnnotation = nil;
}
}
//Attempt to force a refresh so annotations show up.
[self._mapView setCenterCoordinate:self._mapView.centerCoordinate zoomLevel:ZOOM_LEVEL animated:NO];
/*
If there was a rate ban we need to run through the locations again to get proper coordinates
*/
if (needsReload == YES) {
//Sleep the thread to shake off rate ban from google.
[NSThread sleepForTimeInterval:2.0f];
//Move to the background so user can begin interacting with app
[self performSelectorInBackground:@selector(rateLimited) withObject:nil];
} else {
//All locations were property loaded so we can stop here.
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.radiusControl.hidden = NO;
}
}
If there was a rate ban we move to this to recursively called method to correctly obtain all coordinates.
- (void)rateLimited {
/*
Create an array to hold the newly loaded annotations. For some reason the map will only load annoations if I add this this way. Instead of adding them one at a time as done before. I suppose it has to do with some refresh issue.
*/
NSMutableArray *tempReload = [[NSMutableArray alloc] init];
bool needsReload = NO;
/*
Run through the parsed list of property objects again looking for @""
*/
for ( int i = 0; i < [annotations count]; i++) {
if ([[annotations objectAtIndex:i] isEqualToString:@""]) {
Property *tempProp = (Property*) [[[self.appDelegate rssParser] rssItems] objectAtIndex:i];
NSString *temp = [NSString stringWithFormat:@"%@, %@, %@", [tempProp Address], [tempProp City], [tempProp State]];
CLLocationCoordinate2D tempLocation = [self addressLocation:temp];
//There was another rate ban so we need to run through list again.
if (tempLocation.latitude == 0.0) {
needsReload = YES;
} else {
CLLocation *tempLoc = [[CLLocation alloc] initWithLatitude:tempLocation.latitude longitude:tempLocation.longitude];
[[[[[self appDelegate] rssParser]rssItems]objectAtIndex:i] setLocation:tempLoc];
MapAnnotation *addAnnotation = [[MapAnnotation alloc] initWithCoordinate:tempLocation];
addAnnotation.name = [tempProp Address];
if ([[tempProp imageURLs] count] > 0 )
addAnnotation.thumbURL = [[tempProp imageURLs] objectAtIndex:0];
else
addAnnotation.thumbURL = nil;
//Have to keep tags corresponding to the inital listLocation assigned when DB is parsed from server. Done so when properties are added/removed they can be referenced correctly.
addAnnotation.tag = [tempProp listLocation];
//Set description
addAnnotation.description = [NSString stringWithFormat:@"%@, %@ | $%@",[tempProp City] , [tempProp State] ,[[tempProp Deposit] stringByReplacingOccurrencesOfString:@".00" withString:@""]];
//Repace the @"" with the proper annotation.
[annotations replaceObjectAtIndex:i withObject:addAnnotation];
//Add annotation for bulk addition after loop as finished.
[tempReload addObject:addAnnotation];
temp = nil;
addAnnotation = nil;
}
}
}
//Bulk add new annoations
[self._mapView addAnnotations:tempReload];
//If we need to reload, we recusivly call this method until all locations have proper coordinates.
if (needsReload == YES){
needsReload = NO;
tempReload = nil;
//The recusive call
[self rateLimited];
} else {
//All locations have property loaded now we can finish. *wew*
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.radiusControl.hidden = NO;
}
}
Here is my method for reverse geocoding, perhaps someone may have a better way of going about it.
-(CLLocationCoordinate2D)addressLocation:(NSString*)_address {
NSString *urlString = [NSString stringWithFormat:@"http://maps.google.com/maps/geo?q=%@&output=csv",
[_address stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSError* error = nil;
NSString *locationString = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlString ] encoding:NSASCIIStringEncoding error:&error];
NSArray *listItems = [locationString componentsSeparatedByString:@","];
double latitude = 0.0;
double longitude = 0.0;
if([listItems count] >= 4 && [[listItems objectAtIndex:0] isEqualToString:@"200"]) {
latitude = [[listItems objectAtIndex:2] doubleValue];
longitude = [[listItems objectAtIndex:3] doubleValue];
}
else {
NSLog(@"error");
}
CLLocationCoordinate2D location;
location.latitude = latitude;
location.longitude = longitude;
return location;
}