Обратное геокодирование по заданным координатам (не текущее местоположение, а предоставленное вручную)
Я получаю информацию GPS обо всех моих изображениях, и они хранятся в словаре. Я бы передал значения lat & long из этого словаря в reverseGeocodeLocation
вызов функции и сохранение результатов в моей базе данных.
Проблема здесь в том, что эта функция является асинхронным вызовом, и мне нужно синхронизировать весь процесс, чтобы моя запись была вставлена в таблицу.
Например: мой массив прочитал следующие координаты: 32.77003,96.87532. Теперь он называет reverseGeocodeLocation
функция, передавая эти координаты как CLLocation
объект. Теперь, прежде чем эта асинхронная функция вернет мне геокодированное имя местоположения, следующий запрос с новым набором координат будет отправлен reverseGeocodeLocation
функция. Это вызывает несоответствие для вставки записи в базу данных.
Есть ли способ сделать эту задачу синхронной?
т.е. заставь мой цикл пока ждать reverseGeocodeLocation
возвращает значение и затем перейти к следующей записи для геокодирования?
Немного моего кода здесь:
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];
});
}
}
}
2 ответа
Короткий ответ: вы не можете сделать это синхронно.
Что вы хотите сделать, это переместить код, который переходит к следующему объекту, в блок завершения reverseGeocodeLocation, потому что это самый быстрый способ отправить другой запрос reverseGeocodeLocation. Дайте мне посмотреть, смогу ли я сделать здесь какой-нибудь псевдокод... (то есть я не скомпилировал это, так что это может быть не совсем правильно...)
// 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
}
}
}
И, конечно, вы не можете зависеть от того, что вы сделали, чтобы сделать что-то еще, кроме того, что вы можете начать что-то, когда вы перевернули геокодирование последнего объекта.
Это асинхронное программирование может свести вас с ума.
Альтернативным подходом может быть размещение синхронного запроса по следующему URL-адресу, который возвращает обратные геокодированные результаты в формате XML. Вы можете позже разобрать его, преобразовать в JSON или что-то еще. Лучшая часть: 1) Вы принудительно синхронизируете весь процесс обратного гео-кодирования. 2) Нет никаких ограничений в отношении максимального количества запросов, которые вы можете сделать (я думаю, это 50/ мин в случае вызовов reverseGeocodeLocation
обработчик). Если превышено, вы получите kCLErrorDomain
ошибка кода 2 Таким образом, мы избегаем всего этого с помощью следующего подхода ниже. Пример кода, который работает для меня:
-(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);
}