1
votes

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.

  1. 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).

  2. I run through my loaded list of addresses (from DB) passing the address to a reverse geocoder to obtain the lat and long.

  3. Check to see if the lat and long is 0,0 (meaning I was rate limited by google).

  4. 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.

  5. N - If not then I add that annotation to the map and the annotations array.

  6. 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;
}
1

1 Answers

0
votes

I would set up each location as its own object then just make that object itself responsible for figuring out and setting its own location. (and retrying after a small delay if there was a problem)

Then you can just create them as needed and let them do their thing - only displaying them on your map once they let you know they are ready.