CLGeocoder возвращает неправильные результаты, когда название города совпадает с названием какой-либо страны (и не только)
В одном из моих приложений мне нужно добавить возможность найти город по названию. я использую CLGeocoder
чтобы добиться этого, и я хочу, чтобы поведение было идентичным погодному приложению iOS.
Ниже приведен код, который у меня есть:
CLGeocoder().geocodeAddressString(searchBar.text!, completionHandler:{ (placemarks, error) -> Void in
guard let nonNilMarks = placemarks else {return}
for placemark in nonNilMarks {
print("locality: \(placemark.locality)")
print("name: \(placemark.name)")
print("country: \(placemark.country)")
print("formatted address: \(placemark.addressDictionary)")
}
})
Это работает очень хорошо в большинстве случаев. Однако недавно я заметил несколько случаев, когда это не удается. Ниже приведен вывод для некоторых примеров:
В поисках "Милана" - РАБОТЫ
locality: Optional("Milan")
name: Optional("Milan")
country: Optional("Italy")
formatted address: Optional([SubAdministrativeArea: Milan, State: Lombardy, CountryCode: IT, Country: Italy, Name: Milan, FormattedAddressLines: (
Milan,
Italy
), City: Milan])
Это правильный вывод
В поисках 'Италия, Техас' - РАБОТАЕТ
В Техасе есть город под названием Италия. Если я наберу 'Italy, TX', то получится:
locality: Optional("Italy")
name: Optional("Italy")
country: Optional("United States")
formatted address: Optional([SubAdministrativeArea: Ellis, State: TX, CountryCode: US, Country: United States, Name: Italy, FormattedAddressLines: (
"Italy, TX",
"United States"
), City: Italy])
В поисках 'Италии' - FAILS
Когда я печатаю только "Италия", я получаю только знак места для страны:
locality: nil
name: Optional("Italy")
country: Optional("Italy")
formatted address: Optional([CountryCode: IT, Name: Italy, FormattedAddressLines: (
Italy
), Country: Italy])
Поиск "Сингапур, Сингапур" - РАБОТАЕТ
Это похоже на случай с "Италия, Техас":
locality: Optional("Singapore")
name: Optional("Singapore")
country: Optional("Singapore")
formatted address: Optional([City: Singapore, CountryCode: SG, Name: Singapore, State: Singapore, FormattedAddressLines: (
Singapore,
Singapore
), Country: Singapore])
В поисках "Сингапур" - FAILS
Опять же, похоже на случай с "Италией". Он только находит страну:
locality: nil
name: Optional("Singapore")
country: Optional("Singapore")
formatted address: Optional([CountryCode: SG, Name: Singapore, FormattedAddressLines: (
Singapore
), Country: Singapore])
Предварительное предположение
На этом этапе я думал, что, возможно, geocodeAddressString:
прекращает поиск, если находит страну с именем, равным параметру поиска, поэтому я попытался изменить свой код на:
let addrDict = [kABPersonAddressCityKey as NSString: searchBar.text!]
CLGeocoder().geocodeAddressDictionary(addrDict, completionHandler:{ (placemarks, error) -> Void in
print(placemarks)
})
Я думал, что, ограничив поисковый запрос названием города, я получу правильные результаты. К сожалению, я получаю точно такое же поведение!
И тогда я понял, что у меня проблемы не только со случаями, когда название города равно названию страны.
В поисках 'Токио' - EPIC FAIL
locality: nil
name: Optional("Tokyo")
country: Optional("Japan")
formatted address: Optional([CountryCode: JP, Name: Tokyo, State: Tokyo, FormattedAddressLines: (
Tokyo,
Japan
), Country: Japan])
Заключение
Не только может CLGeocoder
пропустить результаты (как в случае "Италии"), но это также может сказать мне, что место не имеет locality
когда, на самом деле, так и есть (полагаю, в прошлый раз, когда я проверял карту, Токио все еще был городом!).
Кто-нибудь знает, как я могу решить любую из этих проблем?
Погода приложение делает это как-то - поиск "Сингапур" или "Италия" там дает правильные результаты. Буду благодарен за любую помощь!
PS Хотя я использую Swift, я добавляю Objective-C в качестве тега, так как считаю, что этот вопрос не является специфичным для Swift.
3 ответа
Использование того, что вы показали, совершенно правильно, и я не думаю, что есть что-то еще, что вы можете сделать, чтобы улучшить результаты. Это все еще простой алгоритм, который пытается отсортировать результаты на основе набора правил и некоторого предположения.
Это грустно, я понимаю ваше разочарование полностью, потому что я был там и сделал это. Проблема в том, что база данных геокодирования явно не полна, и Apple не является компанией, которая возглавляет это поле, а Google. Здесь вы можете увидеть техническую заметку с момента появления геокодера о том, что есть страны, которые не поддерживаются или поддерживаются частично.
Поэтому я предлагаю вам, чтобы, если вы хотите получить наилучшие результаты (хотя и не безотказно), переключитесь на Google Geolocation. Для довольно большого количества запросов это бесплатно, и после этого это стоит очень незначительные суммы. Показатель успеха составляет около 99% для всех базовых поисков, что я испытал, и он только улучшится, если вы предоставите больше информации. Параметры API также довольно обширны.
Вот ссылки на документацию для разработчиков по геокодированию и обратному геокодированию: https://developers.google.com/maps/documentation/geocoding/intro https://developers.google.com/maps/documentation/javascript/examples/geocoding-reverse
Надеюсь, это помогло хоть немного.
РЕДАКТИРОВАТЬ: После того, как я написал это, я провел небольшое исследование, и кажется, что я не единственный человек, чтобы сделать это заявление (включая ту же неподдерживаемую ссылку).
Запросы CLGeocoder ограничены по скорости, и если приложение переходит порог, дальнейшие запросы могут не выполняться. CLGeocoder требует сетевого подключения, которое может быть ограничивающим при некоторых обстоятельствах, и ответ от сервера не всегда мгновенный. Если вы не настаиваете на CLGeocoder, локальная база данных может предоставить вам большую гибкость и, возможно, лучший пользовательский опыт.
Взгляните на города???. Zip на GeoNames, чтобы найти альтернативное решение, в котором вы бы импортировали данные в локальную базу данных.
Вы также можете сделать это с помощью Google API, как это
//chakshu
var urlString = "https://maps.googleapis.com/maps/api/place/autocomplete/json?input=\(searchString)&sensor=true&key=\(Google_Browser_Key)"
var linkUrl:NSURL = NSURL(string:urlString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)!
if(countElements(urlString)>0 && !urlString.isEmpty)
{
// if let fileData = String(contentsOfURL: NSURL(string: urlString)!, encoding: NSUTF8StringEncoding, error: nil)
if let fileData = String(contentsOfURL: linkUrl, encoding: NSUTF8StringEncoding, error: nil)
{
println(fileData)
var data = fileData.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false)
var localError: NSError?
if(data != nil)
{
var json: AnyObject! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &localError)
}
}
}
Я тоже столкнулся с этой проблемой здесь, в Бразилии, поэтому я нашел для этого ужасное решение:
public func getLatLonFrom(address: String, completion:@escaping ((CLLocation?, NSError?)->Void)) {
//LCEssentials.printInfo(title: "LOCATION", msg: "CEP: \(address)")
let geoCoder = CLGeocoder()
let params: [String: Any] = [
CNPostalAddressPostalCodeKey: address,
CNPostalAddressISOCountryCodeKey: "BR"
]
geoCoder.geocodeAddressString(address) { (placemarks, error) in
//LCEssentials.printInfo(title: "LOCATION", msg: "PLACEMARKS FIRST: \(placemarks?.debugDescription)")
if let locais = placemarks {
guard let location = locais.first?.location else {
let error = NSError(domain: LCEssentials().DEFAULT_ERROR_DOMAIN, code: LCEssentials().DEFAULT_ERROR_CODE, userInfo: [ NSLocalizedDescriptionKey: "Address not found" ])
completion(nil, error)
return
}
completion(location, nil)
}else{
geoCoder.geocodeAddressDictionary(params) { (placemarks, error) in
//LCEssentials.printInfo(title: "LOCATION", msg: "PLACEMARKS: \(placemarks?.debugDescription)")
guard let founded = placemarks, let location = founded.first?.location else {
let error = NSError(domain: LCEssentials().DEFAULT_ERROR_DOMAIN, code: LCEssentials().DEFAULT_ERROR_CODE, userInfo: [ NSLocalizedDescriptionKey: "Address not found" ])
completion(nil, error)
return
}
completion(location, nil)
}
}
}
}
Я ввожу код страны в BR (Бразилия), а затем могу найти адрес по почтовому индексу. Если он вернет NIL для меток на geocodeAddressString
тогда я звоню geocodeAddressDictionary
и я получаю правильный результат.
Это решение может охватывать основной почтовый индекс от BR. Это код беспорядка, но пока он работает.
Будущие проекты я буду использовать Google Maps.