I am using MapKit and CoreLocation to find the users location and then display it on a map.
I am using this code to display the City and State (which is what I am interested in, not the EXACT location).
// this delegate is called when the app successfully finds your current location
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
CLGeocoder *geocoder = [[CLGeocoder alloc] init] ;
[geocoder reverseGeocodeLocation:self.locationManager.location
completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"reverseGeocodeLocation:completionHandler: Completion Handler called!");
if (error){
NSLog(#"Geocode failed with error: %#", error);
return;
}
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocationCoordinate2D zoomLocation;
zoomLocation.latitude = placemark.region.center.latitude;
zoomLocation.longitude= placemark.region.center.longitude;
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, METERS_PER_MILE * 2, METERS_PER_MILE * 2);
[self.mapView setRegion:viewRegion animated:NO];
NSString *location = [NSString stringWithFormat:#"%#, %#", placemark.locality, placemark.administrativeArea];
NSLog(#"%#", location);
}];
}
This works great, but what I really want to do is zoom in to the center of the city itself, not the users location.
Is there some kind of property I can access to do this?
Otherwise, is anyone aware of a work around? That would be great, thanks!
not directly. There is no api like MKGetClosestCapitalTo(userLocation)
BUT
you can use the user's location for reverse geocoding and THEN use the city name to geocode the address => that will get you the city center.
so:
address = reversegeocode(userLocation)
loc = geocode(address.cityName)
zoom map to that loc
(you already have 1 so just do 2 and 3 ;))
I would recommend Daij-Djan's approach of doing two geocodes to essentially "lose resolution" in the query. For example, if you find that the user is at a certain address in Austin, TX, then you would get just "Austin, TX" out of the results and ask for the coordinate of that. Then you will have the center of what Apple considers Austin.
Another approach might be to tie into an external database or API. For example, Wikipedia has a center coordinate for each city it contains that you might be able to query & use.
If you want to center around particular city, you can specify the coordinate of the city in following code and the map view will be centred around it.
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance("SPECIFY THE COORDINATE OF THE CITY YOU WANT TO CENTER AROUND HERE", 800, 800);
[self.mapView setRegion:[self.mapView regionThatFits:region] animated:YES];
Related
I am working on a project which can fetch the list of nearby places (from my current place). I use Google Places API and what I have tried is shown below. I see new view which has a mapview with mark position of most prominent places and table which contains list of these places. I need to fetch list of the places so that I can render it on my tableview.
- (IBAction)pickPlace:(UIButton *)sender {
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(51.5108396, -0.0922251);
CLLocationCoordinate2D northEast = CLLocationCoordinate2DMake(center.latitude + 0.001, center.longitude + 0.001);
CLLocationCoordinate2D southWest = CLLocationCoordinate2DMake(center.latitude - 0.001, center.longitude - 0.001);
GMSCoordinateBounds *viewport = [[GMSCoordinateBounds alloc] initWithCoordinate:northEast coordinate:southWest];
GMSPlacePickerConfig *config = [[GMSPlacePickerConfig alloc] initWithViewport:viewport];
_placePicker = [[GMSPlacePicker alloc] initWithConfig:config];
[_placePicker pickPlaceWithCallback:^(GMSPlace *place, NSError *error) {
if (error != nil) {
NSLog(#"Pick Place error %#", [error localizedDescription]);
return;
}
if (place != nil) {
NSLog(#"Place name %#", place.name);
NSLog(#"Place address %#", place.formattedAddress);
NSLog(#"Place attributions %#", place.attributions.string);
} else {
NSLog(#"No place selected");
}
}];
}
Then you shouldn't use GMSPlacePicker, because it offers the UI to you for your user to make a single selection. If you want a list of places that you can display yourself then you should use the REST interface provided by google to get the required details and update your UI.
It is doable to get list of nearby places (from my current place) with Google Places API. I used the following method to achieve the same:
[GMSPlaceClient currentPlaceWithCallback:]
An array of nearby places with likelihood is returned.
There are two classes that help regarding map plotting and determining user location - MKMapView and CLLocationManager.
MKMapView has a delegate “didUpdateUserLocation” that tells the user’s current location. At the same time, CLLocationManger has a delegate “didUpdateToLocation” and it also does the same thing.
My question is when to use MKMapView and CLLocationManager. I am able to get current location of the device from MKMapView then why and when should I use CLLocationManager? I tried to get it but I am still not sure.
I think you are confusing the MKMapView property showsUserLocation with CLLocationManager.
As a convenience MKMapView's allow you to simply enable a property to show the users current location on the map UI. This is really handy if you only need to show the user where they are on a map.
However, there are demonstrably many other use cases where simply showing location on a map is not enough and this is where CLLocationManager comes in.
Consider for example a running/training application, where a record of user locations is required to calculate running distance, or even an example from one of my own applications, where by I needed to find the users location (lat/long) to calculate distance to various train stations in real time to identify which was closest for the user. In these examples there is no need for a MapView so using a LocationManager is the right choice.
Anytime you need to programmatically interact with the users location and don't require a map UI basically!
I prefer to use CLLocationManager which is used internally by the MKMapView, so if you don't need to use the map, just use the below code from the location manager.
locationManager = [[CLLocationManager alloc] init];
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate = self;
[locationManager startUpdatingLocation];
Just don't forget to store the locationManager instance somewhere in your class and you can implement the delegate like this.
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"Error detecting location %#", error);
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation* location = (CLLocation*)locations.lastObject;
NSLog(#"Longitude: %f, Latitude: %f", location.coordinate.longitude, location.coordinate.latitude);
}
EDIT
You can get the address of the user based on their location using Apple's Geo coder
// Use Apple's Geocoder to figure the name of the place
CLGeocoder* geoCoder = [[CLGeocoder alloc] init];
[geoCoder reverseGeocodeLocation:location completionHandler: ^(NSArray* placemarks, NSError* error) {
if (error != nil) {
NSLog(#"Error in geo coder: %#", error);
}
else {
if (placemarks.count == 0) {
NSLog(#"The address couldn't be found");
}
else {
// Get nearby address
CLPlacemark* placemark = placemarks[0];
// Get the string address and store it
NSString* locatedAt = [[placemark.addressDictionary valueForKey:#"FormattedAddressLines"] componentsJoinedByString:#", "];
location.name = locatedAt;
NSLog(#"The address is: %#", locatedAt);
}
}
}];
I have a MKMapView and want to grab the cities name based on the center of the MKMapView. I do not want to drop any pins or annotations at all. Just automatically grab the data based on center of MKMapView location. I know there is this method '[placemarkName locality]'. But is there a way without using a placemark?
You could just do a reverseGeocodeLocation (effective iOS 5+) from the centerCoordinate of the map view. For example, I could do something like:
CLLocationCoordinate2D center = self.mapView.centerCoordinate;
CLGeocoder *coder = [[CLGeocoder alloc] init];
[coder reverseGeocodeLocation:[[CLLocation alloc] initWithLatitude:center.latitude longitude:center.longitude] completionHandler:^(NSArray *placemarks, NSError *error) {
self.cityLabel.text = [[placemarks firstObject] locality];
}];
This admittedly, returns an array of placemarks, but you don't have to do anything with them after you extract the city's name.
What I want to accomplish in my app is to get the current user location and display it onscreen in a UILabel. I would like to have an NSString of current user's location with a format similar to this: #"City, State/Country". It would be a one-time operation at the start of the app launch.
I have no prior experience with location in iOS and I would like to get some advice on this one - I'm sure it's quite a simple task.
The process is as follows:
Add CoreLocation.framework to your project. See Linking to a Library or a Framework. If you want to use the address book constants that I use below, you might want to add the AddressBook.framework to your project, too.
Start location services. For this purpose, the "significant change" service (less accurate, but lower power consumption) is probably sufficient for city-level accuracy.
When the location manager informs you of the user's location, then perform a reverse geocode of that location.
Stop location services.
Thus, that might look like:
#import <CoreLocation/CoreLocation.h>
#import <AddressBook/AddressBook.h>
#interface ViewController () <CLLocationManagerDelegate>
#property (nonatomic, strong) CLLocationManager *locationManager;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self startSignificantChangeUpdates];
}
- (void)startSignificantChangeUpdates
{
if ([CLLocationManager locationServicesEnabled])
{
if (!self.locationManager)
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startMonitoringSignificantLocationChanges];
}
}
- (void)stopSignificantChangesUpdates
{
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *location = [locations lastObject];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = placemarks[0];
NSDictionary *addressDictionary = [placemark addressDictionary];
NSString *city = addressDictionary[(NSString *)kABPersonAddressCityKey];
NSString *state = addressDictionary[(NSString *)kABPersonAddressStateKey];
NSString *country = placemark.country;
self.label.text = [NSString stringWithFormat:#"%#, %#, %#", city, state, country];
}];
[self stopSignificantChangesUpdates];
}
Note, the location manager's notification of the location is contingent upon the user electing to share that with your app and will happen, even in the best case scenario, asynchronously. Likewise the reverse geocode happens asynchronously.
See Getting User Location from the Location Awareness Programming Guide.
Use -reverseGeocodeLocation:completionHandler: of CLGeocoder.
Try this code snippet, the only trick is that the CLPlacemark (see the Documentation for available info) you get back from the Geocoder has a bunch of info which isn't always consistent, this was one of my tries from an older project, trying to test for location, street name etc... test with your usage case to find a good match:
- (void)getLocationStringForCoordinates:(CLLocationCoordinate2D)coordinates {
if ( CLLocationCoordinate2DIsValid(coordinates) ) {
CLLocation *photoLocation = [[CLLocation alloc] initWithLatitude:coordinates.latitude longitude:coordinates.longitude];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:photoLocation
completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark *locationPlacemark = [placemarks lastObject];
// Location (popular name, street, area)
NSString *location = locationPlacemark.subLocality ? locationPlacemark.subLocality : (locationPlacemark.name ? locationPlacemark.name : locationPlacemark.thoroughfare);
// sometimes the location can be the same
// as the city name (for small villages), if so
// make sure location is nil to skip it
// else if
// the location name is not being used but is very short 9less then 20 letters, use that instead
if([locationPlacemark.name isEqualToString:locationPlacemark.locality] && [location isEqualToString:locationPlacemark.name])
location = #"";
else if ( ![locationPlacemark.name isEqualToString:location] && locationPlacemark.name.length < 20 )
location = locationPlacemark.name;
// city
NSString *city = locationPlacemark.subAdministrativeArea ? locationPlacemark.subAdministrativeArea : locationPlacemark.locality;
city = city.length > 0 ? [#", " stringByAppendingString:city] : city;
NSString *locationName = [NSString stringWithFormat:#"%#%#", location, city];
}];
}
}
I've found a really nice and simple to follow tutorial on this topic - http://www.appcoda.com/how-to-get-current-location-iphone-user/
Hope it will be helpful to others!
Take a look at the reverseGeocodeLocation:completionHandler: method for CLGeocoder:
http://developer.apple.com/library/ios/#documentation/CoreLocation/Reference/CLGeocoder_class/Reference/Reference.html#//apple_ref/doc/uid/TP40009573
First you will have to use a CLLocationManager to get a CLLocation representing the user's current position.
I am trying to Reverse geocode location from Lat/Long value that I get earlier in the App and I would like from this coordinate to find the city name, country name and ISO.
I am currently using CLLocationManager to get actual location information with the folowing code:
//Auto geolocation and find city/country
locationManager.delegate=self;
//Get user location
[locationManager startUpdatingLocation];
[self.geoCoder reverseGeocodeLocation: locationManager.location completionHandler:
^(NSArray *placemarks, NSError *error) {
//Get nearby address
CLPlacemark *placemark = [placemarks objectAtIndex:0];
//String to hold address
locatedAtcountry = placemark.country;
locatedAtcity = placemark.locality;
locatedAtisocountry = placemark.ISOcountryCode;
//Print the location to console
NSLog(#"Estas en %#",locatedAtcountry);
NSLog(#"Estas en %#",locatedAtcity);
NSLog(#"Estas en %#",locatedAtisocountry);
[cityLabel setText:[NSString stringWithFormat:#"%#,",locatedAtcity]];
[locationLabel setText:[NSString stringWithFormat:#"%#",locatedAtcountry]];
//Set the label text to current location
//[locationLabel setText:locatedAt];
}];
It is working perfectly but, It is possible to do the same from Long/Lat value that I had already saved in the device and not with the current location like on the actual code ?
Update and solution:
Thanks Mark for the answer, I finally use the following code to get info from saved coordinate:
CLLocation *location = [[CLLocation alloc] initWithLatitude:37.78583400 longitude:-122.40641700];
[self.geoCoder reverseGeocodeLocation: location completionHandler:
^(NSArray *placemarks, NSError *error) {
//Get nearby address
CLPlacemark *placemark = [placemarks objectAtIndex:0];
//String to hold address
locatedAtcountry = placemark.country;
locatedAtcity = placemark.locality;
locatedAtisocountry = placemark.ISOcountryCode;
//Print the location to console
NSLog(#"Estas en %#",locatedAtcountry);
NSLog(#"Estas en %#",locatedAtcity);
NSLog(#"Estas en %#",locatedAtisocountry);
[cityLabel setText:[NSString stringWithFormat:#"%#",locatedAtcity]];
[locationLabel setText:[NSString stringWithFormat:#"%#",locatedAtcountry]];
//Set the label text to current location
//[locationLabel setText:locatedAt];
}];
Yes. Create a CLLocation object using the initWithLatitude:longitude: method using your saved lat/lon values, and pass that to reverseGeocodeLocation:.
I am surprised that you say this is working (although, if you're on the simulator, location services are simulated anyway, which might be the reason) because when you call startUpdatingLocation, your implementation of CLLocationManagerDelegate methods like locationManager:didUpdateToLocation:fromLocation: get called. (You've implemented these right?) It is only when this (and other) delegate method is called that you can be certain that you have successfully determined the user's location.
You may want to read up on the CLLocationManagerDelegate protocol and on Location Services best practices as documented by Apple.