Using Yandex Maps API & Core Location In iOS To Determine City Of a User By Reverse Geocoding

No comments on Using Yandex Maps API & Core Location In iOS To Determine City Of a User By Reverse Geocoding

Written by

in

My goal was to determine the city or town name of a user to fill a region text field automatically in iOS app. At first I tried to use Core Location and MapKit, but I got only city names in English, for example, ‘Ufa’.  I had to get city names in Russian, so I decided to use Yandex Maps API. This is a final result – interface with a button to get city name automatically in Russian and a textField with a city name:

First, we add CLLocationManagerDelegate interface and an instance variable of CLLocationManager to get coordinates to our View Controller:

@interface MyViewController : UIViewController 
{
  CLLocationManager *_locationManager;
}

@end

I created in my ViewController two helper methods to show and hide spinner without writing implementation of it:

- (void)showSpinner
{
  self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
}

- (void)hideSpinner
{
  [self.hud hide:YES];
}

I have a button on my ViewController. When it’s clicked a TextField is filled by a city name automatically after it is determined.

#pragma mark - Location determination

// Button click handler
- (void)determineRegionClicked:(id)sender
{
  [self showSpinner];

  // We create CLLocationManager and start  updating location
  _locationManager = [[CLLocationManager alloc] init];
  _locationManager.delegate = self;
  _locationManager.distanceFilter = kCLDistanceFilterNone;
  _locationManager.desiredAccuracy = kCLLocationAccuracyBest;
  [_locationManager startUpdatingLocation];
}

Here we tell, that our ViewController is a delegate of CLLocationManager.

This is a delegate method, that does all the remaining work. It is called after coordinates are determined. I used SBJson framework to parse JSON.

#import 

...

#define YANDEX_MAPS_API_DOMAIN @"http://geocode-maps.yandex.ru/1.x/"

...

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
  // Switching off location updates
  [_locationManager stopUpdatingLocation];

  // Reverse geocoding using Yandex Maps API
  CLLocationCoordinate2D coordinate = newLocation.coordinate;
  NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", coordinate.longitude, coordinate.latitude];

  // Creating HTTP request. Here kind=locality means that we will be searching for a city.
  NSString *urlString = [NSString stringWithFormat:@"%@?geocode=%@&results=1&lang=ru-RU&format=json&kind=locality", YANDEX_MAPS_API_DOMAIN, coordinatesString];
  NSURL *url = [NSURL URLWithString:urlString];
  NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

  // Sending synchronous request in background queue using dispatch queues
  dispatch_queue_t backgroundQueue = dispatch_queue_create("bgQueue", nil);
  dispatch_async(backgroundQueue, ^{
    __autoreleasing NSHTTPURLResponse *urlResponse = nil;
    __autoreleasing NSError *error = nil;
    NSData *resultData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&urlResponse error:&error];

    // Parsing a result
    if (! error) {
      NSLog(@"Status Code: %d %@", [urlResponse statusCode], [NSHTTPURLResponse localizedStringForStatusCode:[urlResponse statusCode]]);

      NSString *resultString = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
      // NSLog(@"resultString:%@", resultString);

      SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
      NSDictionary *jsonObject = [jsonParser objectWithString:resultString];

      // If count is zero, then region is not found
      int count = [jsonObject[@"response"][@"GeoObjectCollection"][@"metaDataProperty"][@"GeocoderResponseMetaData"][@"found"] integerValue];
      if (count == 0) {
        dispatch_async(dispatch_get_main_queue(), ^{
          [self showRegionNotFoundError];
        });
        return;
      }

      // Searching in a JSON
      NSString *city = [jsonObject[@"response"][@"GeoObjectCollection"][@"featureMember"] firstObject][@"GeoObject"][@"name"];
      NSLog(@"Yandex API City returned: %@", city);

      dispatch_async(dispatch_get_main_queue(), ^{
        // Here I have a code that updates my UI after city is determined
      });

    }
    else {
      // If failed to get data from Yandex API
      NSLog(@"An error occured, Status Code: %i", urlResponse.statusCode);
      NSLog(@"Description: %@", [error localizedDescription]);
      NSLog(@"Response Body: %@", [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding]);

      dispatch_async(dispatch_get_main_queue(), ^{
        [self showRegionNotFoundError];
      });
    }

    dispatch_async(dispatch_get_main_queue(), ^{
      [self hideSpinner];
    });

  });
}

This is an error handling for Core Location:

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
  [_locationManager stopUpdatingLocation];

  [self showRegionNotFoundError];
}

This method shows an error if we failed to determine city automatically:

- (void)showRegionNotFoundError
{
  [self hideSpinner];

  UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Failed to determine your city automatically, please, select the one from a list." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
  [alertView show];
}

The same goal can be achieved using MapKit instead of Yandex Maps API for English city names:

#import 
#import 
#import 

... 

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {

... 

  CLGeocoder *geocoder = [[CLGeocoder alloc] init] ;
  
  [geocoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks, NSError *error) {
    
    [self hideSpinner];
    
    if (! error )
    {
      CLPlacemark *placemark = [placemarks objectAtIndex:0];
      NSString *city = [[placemark addressDictionary] objectForKey:(NSString *)kABPersonAddressCityKey];
      NSLog(@"determinedRegion: %@", city);
    
      NSLog(@"Full placemark address dictionary: %@", placemark.addressDictionary);
    }
    else
    {
      [self showRegionNotFoundError];
      
      NSLog(@"Geocode failed with error %@", error);
      NSLog(@"\nCurrent Location Not Detected\n");
    }
  }];

Finally, some test locations. Replace the appropriate string to simulate another coordinates. This could be done in Simulator settings also in Debug/Geo section.

  // Test for Novgorod
  // NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", 44.0000f, 56.3333f];

  // Test for Ufa city
  // NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", 55.9667f, 54.7500f];

  // Test for a small town Meleuz in the Republic of Bashkortostan, Russia
  // NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", 55.9333f, 52.9500f];

  // Test for a random location on a map
  // NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", 56.9333f, 53.9500f];

  // Test for Moscow
  // NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", 37.6167f, 55.7500f];

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *