I have an array of coordinates that I step through with a for loop. I would like to place annotations on a map for each location and have the subtitle for the callout be the address of the coordinate, found by using reverseGeocodeLocation In the for loop, I call the reverseGeocodeLocation method, and inside the completion block I create the annotation and display it on the map. However, when I run the app, only one annotation shows up. I went in the debugger, and the completion block is only getting called once (for two calls to the reverseGeocodeLocation method). Any suggestions to fix this?
My for loop:
for(int i = 0; i < [locations count]; i++)
{
CLLocation *location = [locations objectAtIndex:i];
__block NSString *info;
NSLog(#"Resolving the Address");
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error)
{
NSLog(#"Found placemarks: %#, error: %#", placemarks, error);
if (error == nil && [placemarks count] > 0)
{
placemark = [placemarks lastObject];
info = [NSString stringWithFormat:#"%# %# %# %#, %#",
placemark.subThoroughfare, placemark.thoroughfare,
placemark.postalCode, placemark.locality,
placemark.administrativeArea];
[self remainderOfMethod:location withAddress:info atIndex:i];
}
else
{
NSLog(#"%#", error.debugDescription);
}
} ];
}
And the method called at the completion block:
- (void) remainderOfMethod: (CLLocation *)location withAddress:(NSString *)info atIndex: (int)i
{
MKPointAnnotation* annotation = [[MKPointAnnotation alloc] init];
if (location != nil)
{
[annotation setSubtitle:[NSString stringWithFormat:#"%#", info]];
annotation.coordinate = location.coordinate;
[self.mapView addAnnotation:annotation];
}
}
Thanks!
From the official Apple documentation:
After initiating a reverse-geocoding request, do not attempt to
initiate another reverse- or forward-geocoding request
You can find the docs here: https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLGeocoder_class/Reference/Reference.html#//apple_ref/occ/instm/CLGeocoder/reverseGeocodeLocation:completionHandler:
One way to solve it is to do only one request at a time in a recursive method that pops a location from a stack (or array) on each iteration.
Even in that case, consider what Apple has to say about it:
Geocoding requests are rate-limited for each app, so making too many
requests in a short period of time may cause some of the requests to
fail
So you may want to request geocoding on demand, for example when a user taps on an annotation.
Related
In my application I want the exact lattitude and longitude of given address using forward geocoding in IOS in Objective-C.
I had used forward geocoding in my application but it is not giving the exact address. this is my code of forward geocoding
-(void)detectlocation:(NSString*)address
{
CLGeocoder *geocoder=[[CLGeocoder alloc]init];
[geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error)
{
if(!error)
{
CLPlacemark *placemark = [placemarks objectAtIndex:0];
NSLog(#"%f",placemark.location.coordinate.latitude);
NSLog(#"%f",placemark.location.coordinate.longitude);
self.latitude4=placemark.location.coordinate.latitude;
self.longitude4=placemark.location.coordinate.longitude;
NSLog(#"%#",[NSString stringWithFormat:#"%#",[placemark description]]);
}
}];
}
Thanks In Advance
You can access placemark properties for a more accurate location.
This is swift, but the same is for objective-c
Declare and initiate your CLLocationMager* inside the viewDidLoad method.
I recommend to use:
#property YourCustomLocation* foundedLocation;
in order to "save" data of the place that has been found.
Then try this inside your method:
[self.geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
//if 1+ "places" have been found:
if ([placemarks count] > 0) {
//save the first place that has been found
CLPlacemark *placemark = [placemarks objectAtIndex:0];
//save location from the placemark
CLLocation *location = placemark.location;
//save coordinates from location
CLLocationCoordinate2D coordinate = location.coordinate;
//Do more stuffs...like:
self->_foundedLocation = [[YouCustomLocation alloc]initLocationWithLatitude:coordinate.latitude Longitude:coordinate.longitude];}
}];
Remember that forward-geocoding works with blocks, so unless you use __block directive near the variable you won't be able to "save the location" on a variable declared outside the block.
I'd like to add annotation manually (when user touch in to the particular place in map view) and to get the details of that location (latitude,longitude,address)..
as #iPrabu directed to similar post, with very good and correct answer for your problem... for second part i.e. to get details about that location..you can do something like this
-(void)getAddressFromCurruntLocation:(CLLocation *)location{
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error)
{
if(placemarks && placemarks.count > 0)
{
CLPlacemark *placemark= [placemarks objectAtIndex:0];
NSString *address = [NSString stringWithFormat:#"%# %#",[placemark country],[placemark administrativeArea]];
// you may also get locality,sublocality,subadministrativeare etc
NSLog(#"The Address Is:%#",address);
}
}];
}
Happy Coding :)
I need to convert my latitude and longitude, received from the server to location. As I user Parse API I don't find that's a good idea to convert them in viewDidLoad, so I decided to make it in cellForRowAtIndexPath.
I tried this approach: Synchronization of concurrent processes execution
Here:
...
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.geocoder reverseGeocodeLocation: [[CLLocation alloc] initWithLatitude:meeting.location.latitude
longitude:meeting.location.longitude] completionHandler:
^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
meeting.location.displayName = [placemark.addressDictionary objectForKey:#"Street"];
NSLog(#"%#", meeting.location.displayName);
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
cell.subInfo.text = meeting.location.displayName;
return cell;
But it seems to hang endlessly. Where am I wrong?
UPD: I've tried to move all this stuff back to viewDidLoad. In my for loop, where I parse downloaded from Parse.com information I now have:
...
_location.latitude = ((PFGeoPoint *)entry[#"location"]).latitude;
_location.longitude = ((PFGeoPoint *)entry[#"location"]).longitude;
_semaphore = dispatch_semaphore_create(0);
[self reverseGeocode:_location];
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
...
Where _semaphore is #property (strong) dispatch_semaphore_t semaphore;
reverseGeocode::
[self.geocoder reverseGeocodeLocation: [[CLLocation alloc] initWithLatitude:meetingLocation.latitude
longitude:meetingLocation.longitude] completionHandler:
^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
meetingLocation.displayName = [placemark.addressDictionary objectForKey:#"Street"];
dispatch_semaphore_signal(_semaphore);
}];
Now it seems to hang, but I have no idea where, because it doesn't even enter the geocoded completionHandler block.
You tried to get the value for meeting.location.displayName in the block, and then set cell.subInfo.text with the value of meeting.location.displayName after the block. The problem will be the line cell.subInfo.text = meeting.location.displayName; can be executed before the block is returned. You can try to put the line cell.subInfo.text = meeting.location.displayName; inside the block.
But I don't see why you want to reverse geolocation in this method, the reverse function may be called every time when the user scroll to particular if you don't set the condition when to do reversion. If viewDidLoad is not a good idea for your design, try viewWillAppear.
I am trying to write a method that gets geocoding and returns the place mark as the methods return. I get the location manager portion; it waits to find an location, then runs stop updating. Then I run the geocoding and that is where I have the most questions. First what does that [] block notation mean? I can't seem to return anything out of that ever. So it gets the location in the place mark, but I want to pass that location info back out.
My goal is to have to buttons on a view, one that starts finding the location and writes the starting location to a table, and another button that gets the ending location and writes that to a table. I wanted to reuse the block of code that gets the location.
So in pseudo I want the action code for
button A
placemark objectFoo = call and get my location;
objectFoo write to DB or do whatever
Button B
placemark objectFoo = call and get my location;
objectFoo write to DB or do whatever
Coming from Java I'm confused by what that block of reverseGeocodeLocation:currentLocation is and how I can modify it? I envision an object that goes and gets my location and just hands that back to whatever called it to with it what I please.
- (CLPlacemark *)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"didUpdateToLocation: %#", newLocation);
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
_status.text = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude];
NSLog(#"longitude: %#", [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude]);
_status.text = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude];
NSLog(#"latitude: %#", [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude]);
}
// Stop Location Manager
[_locationManager stopUpdatingLocation];
NSLog(#"Resolving the Address");
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Found placemarks: %#, error: %#", placemarks, error);
if (error == nil && [placemarks count] > 0) {
placemark = [placemarks lastObject];
_startLocation.text = [NSString stringWithFormat:#"%# %#\n%# %#\n%#\n%#",
placemark.subThoroughfare, placemark.thoroughfare,
placemark.postalCode, placemark.locality,
placemark.administrativeArea,
placemark.country];
NSLog(#"%#", placemark.subThoroughfare); // address number
NSLog(#"%#", placemark.thoroughfare); // address St
NSLog(#"%#", placemark.postalCode); // zip
NSLog(#"%#", placemark.locality); // city
NSLog(#"%#", placemark.administrativeArea); // state
NSLog(#"%#", placemark.country); // country
NSLog(#" debug county == %#", placemark.country); // country
} else {
NSLog(#"%#", error.debugDescription);
//return placemark;
}
//return placemark;
} ];
NSLog(#" needs to log this to see placemerk country == %#", placemark.country);
return placemark; //placemark object?
}
The [] notation in Objective C can mean a few things, but in this case I think you are referring to the method invocation on an object. So [myObject doSomethingWithInt:i]; is the same as myObject.doSomethingWithInt(i); in Java. Apple has a guide to Objctive c here and there are a number of other introductions and tutorials available on the info pages for the iOS and Objective-C tags here on SO.
In terms of responding to button presses, it is very similar to the event model in Java. You will need to establish a link between the buttons on your storyboard or xib and an event in your UIViewController instance. Again, it is best to work through some examples and tutorials, but essentially you
create a class that subclasses UIViewController
Declare IBAction methods in the .h file for your subclass
In the view in your storyboard or XIB file you set the view's controller class to your subclass
Once you add the buttons to the view you can then drag from the "touches up inside" event on the button to your View Controller and link it to the appropriate IBAction method.
Objective-C and iOS use the delegate pattern heavily. The method in your question is one of the delegate methods of the CLLocationmanagerDelegate protocol. You can declare some #property variables in the .h file for the class that contains this method and change it to something like -
In your .h file
#property (retain,nonatomic) CLPlacemark *currentPlacemark;
in your .m file
- (CLPlacemark *)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"didUpdateToLocation: %#", newLocation);
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Found placemarks: %#, error: %#", placemarks, error);
if (error == nil && [placemarks count] > 0) {
self.currentPlacemark = [placemarks lastObject];
}
}
}
In your viewController subclass .h file
#property (strong,nonatomic) *MyLocationMonitor *locationMonitor;
In your ..m file
- (void) viewDidLoad
{
[super viewDidLoad];
self.locationMonitor=[[MyLocationMonitor alloc]init];
}
to expose the current location and then when the button is clicked you could do something like
-(IBAction)button1Clicked:(id)sender {
CLLocation *currentLocation=self.locationMonitor.currentPlacemark;
// TODO - Do something
}
I am fetching GPS information of all my images and they are stored in a Dictionary. I would pass on the lat & long values from this dictionary to the reverseGeocodeLocation function call & store the results in my database.
The problem here is that this function is an asynchronous call & I need to synchronize the whole process for my record to get inserted into the table.
Eg: My array read following coordinates: 32.77003,96.87532. It now calls the reverseGeocodeLocation function, passing on these coordinates as CLLocation object. Now before this async function returns me back the geo-coded location name, next request with new set of coordinates is sent to reverseGeocodeLocation function. This causes inconsistency to insert the record into database.
Is there any way to have this whole task turn synchronous?
i.e Make my for-loop wait until reverseGeocodeLocation returns a value and then move on to next record to be geo-coded?
A bit of my code is here:
for (int imgIdx=0; imgIdx<[imageMetaMutArray count]; imgIdx++)
{
NSDictionary *localGpsDict = [[NSDictionary alloc]initWithDictionary: [imageMetaMutArray objectAtIndex:imgIdx]];
imgLatLoc=[localGpsDict valueForKey:#"Latitude"];
imgLongLoc=[localGpsDict valueForKey:#"Longitude"];
dateStamp=[localGpsDict valueForKey:#"DateStamp"];
timeStamp=[localGpsDict valueForKey:#"TimeStamp"];
if(imgLatLoc && imgLongLoc && dateStamp && timeStamp)
{
CLGeocoder *geoCoder=[[CLGeocoder alloc]init];
CLLocation *currentLocation=[[CLLocation alloc]initWithLatitude:[imgLatLoc doubleValue] longitude:[imgLongLoc doubleValue]];
[geoCoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placeMarks, NSError *err){
if(err)
{
NSLog(#"Reverse geo-coding failed because: %#",err);
return;
}
if(placeMarks && placeMarks.count>0)
{
CLPlacemark *placeMarkers=placeMarks[0];
NSDictionary *locationDictionary=placeMarkers.addressDictionary;
NSString *country=[locationDictionary objectForKey:(NSString *)kABPersonAddressCountryKey];
NSString *city=[locationDictionary objectForKey:(NSString *)kABPersonAddressCityKey];
NSString *state=[locationDictionary objectForKey:(NSString *)kABPersonAddressStateKey];
NSLog(#"logged in from:");
if(city)
{
NSLog(#"city: %#",city);
locName = [[NSString alloc]initWithString:city];
if(state)
{
NSLog(#"state: %#",state);
locName=[locName stringByAppendingString:#","];
locName=[locName stringByAppendingString:state];
}
if(country)
{
NSLog(#"country: %#",country);
locName=[locName stringByAppendingString:#","];
locName=[locName stringByAppendingString:country];
}
}
else
{
if(state)
{
NSLog(#"state: %#",state);
locName = [[NSString alloc]initWithString:state];
if(country)
{
NSLog(#"country: %#",country);
locName=[locName stringByAppendingString:#","];
locName=[locName stringByAppendingString:country];
}
}
else
{
NSLog(#"country: %#",country);
locName = [[NSString alloc]initWithString:country];
}
}
}
else
{
NSLog(#"Placemark Error code:: %lu\n%#",(unsigned long)placeMarks.count,placeMarks);
}
[locName retain];
NSLog(#"location decoded is: %#",locName);
/*Call for Insert into Images table*/
[self insertDataImgTbl:locName];
});
}
}
}
The short answer is that you can't make it synchronous.
What you want to do is move the code that goes on to the next object into the completion block of the reverseGeocodeLocation because that is the soonest that you can submit another reverseGeocodeLocation request. Let me see if I can make some pseudocode here... (that is, I haven't compiled this so it might not be exactly right...)
// in place of the original for loop:
[self reverseGeocodeForIndex:0];
// Doing the reverse geocode is in a method so you can easily call it from within the completion block.
// Maybe your parameter is not the imgIdx value but is instead some object -- I'm just hacking your posted code
// The point is that your completion block has to be able to tell when
// it is done and how to go on to the next object when it is not done.
(void) reverseGeocodeForIndex:(int) imgIdx {
NSDictionary *localGpsDict = [[NSDictionary alloc]initWithDictionary: [imageMetaMutArray objectAtIndex:imgIdx]];
imgLatLoc=[localGpsDict valueForKey:#"Latitude"];
imgLongLoc=[localGpsDict valueForKey:#"Longitude"];
dateStamp=[localGpsDict valueForKey:#"DateStamp"];
timeStamp=[localGpsDict valueForKey:#"TimeStamp"];
if(imgLatLoc && imgLongLoc && dateStamp && timeStamp)
{
CLGeocoder *geoCoder=[[CLGeocoder alloc]init];
CLLocation *currentLocation=[[CLLocation alloc]initWithLatitude:[imgLatLoc doubleValue] longitude:[imgLongLoc doubleValue]];
[geoCoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placeMarks, NSError *err){
// completion block
if(err)
{
// error stuff
}
if(placeMarks && placeMarks.count>0)
{
// what happens when you get some data
}
// now see if we are done and if not do the next object
if (imgIdx<[imageMetaMutArray count])
{
[self reverseGeocodeForIndex:imgIdx+1];
} else {
// Everything is done maybe you want to start something else
}
}];
} else {
// Since you might not reverseGeocode an object you need an else case
// here to move on to the next object.
// Maybe you could be more clever and not duplicate this code.
if (imgIdx<[imageMetaMutArray count])
{
[self reverseGeocodeForIndex:imgIdx+1];
} else {
// Everything is done maybe you want to start something else
}
}
}
And, of course, you can't depend on this being done to do anything else except that you might kick something off when you have reverseGeocoded the last object.
This asynchronous programming can drive you nuts.
An alternative approach could be to place a synchronous request to the following URL, which returns reverse geo-coded results in XML format. You can later parse it, convert to JSON or whatever. The best part: 1) You're force synchronizing the whole process of reverse-geo coding
2) There's no restriction in terms of max requests you can make(I think its 50/min in case of calls to reverseGeocodeLocation handler). If exceeded, you get kCLErrorDomain code 2 error. So we avoid all that by the following approach below. Some sample code that works for me:
-(NSString *)giveMeLocName: (double)gpsLat :(double)gpsLong
{
NSString *finalLocation=[[NSString alloc]init];
//Below is URL we need to send request
NSString *reverseGeoCodeLoc = [NSString
stringWithFormat:#"http://nominatim.openstreetmap.org/reverse?format=xml&zoom=18&addressdetails=1&accept-language=en&lat=%lg&lon=%lg",gpsLat,gpsLong];
NSURL *myLocUrl = [NSURL URLWithString:reverseGeoCodeLoc];
ASIHTTPRequest *myLocRequest = [ASIHTTPRequest requestWithURL:myLocUrl];
[myLocRequest setDidFinishSelector:#selector(reverseGeoCodeImg:)];
[myLocRequest setDelegate:self];
[myLocRequest startSynchronous];
NSLog(#"waiting for location info..");
//Do stuff after receiving results here
}
//This block receives HTTP response as XML(containing reverse geo-coded info. I parse this to JSON using XMLReader class(downloadable)
-(void)reverseGeoCodeImg:(ASIHTTPRequest *)request
{
/*Allocations*/
locDict=[[NSDictionary alloc]init];
revGeoCodePart=[[NSDictionary alloc]init];
addressParts=[[NSDictionary alloc]init];
cityName=[[NSString alloc]init];
stateName=[[NSString alloc]init];
countryName=[[NSString alloc]init];
NSLog(#"starting reverse geo-code!!");
NSString *responseString = [request responseString];
NSError *parseError = nil;
locDict=[XMLReader dictionaryForXMLString:responseString error:&parseError];
[locDict retain];
revGeoCodePart=[locDict objectForKey:#"reversegeocode"];
[revGeoCodePart retain];
addressParts=[revGeoCodePart objectForKey:#"addressparts"];
[addressParts retain];
cityName=[[addressParts objectForKey:#"city"] objectForKey:#"text"];
[cityName retain];
stateName=[[addressParts objectForKey:#"state"]objectForKey:#"text"];
[stateName retain];
countryName=[[addressParts objectForKey:#"country"]objectForKey:#"text"];
[countryName retain];
NSLog(#"city: %#\nstate: %#\ncountry: %#",cityName,stateName,countryName);
}