CLGeocoder returns 当城市名称等于某个国家的名称时(不仅如此),结果错误

CLGeocoder returns wrong results when city name is equal to some country's name (and not only)

在我的一个应用程序中,我需要添加通过名称查找城市的功能。我正在使用 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)")
    }
})

在大多数情况下效果很好。但是,我最近注意到一些失败的情况。以下是一些示例的输出:

正在搜索 'Milan' - 作品

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' - 作品

得克萨斯州有一个小镇叫意大利。如果我输入 '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])

正在搜索 'Italy' - 失败

当我只输入 'Italy' 时,我只会得到国家的地标:

locality: nil
name: Optional("Italy")
country: Optional("Italy")
formatted address: Optional([CountryCode: IT, Name: Italy, FormattedAddressLines: (
    Italy
), Country: Italy])

正在搜索 'Singapore, Singapore' - 作品

这与 'Italy, TX' 的情况类似:

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])

正在搜索 'Singapore' - 失败

同样,似乎与 'Italy' 的情况相似。它只找到一个国家:

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)
})

我认为通过将搜索词限制为城市名称我会得到正确的结果。不幸的是,我得到了完全相同的行为!

然后然后我意识到我不仅在城市名称等于国家名称的情况下遇到问题。

正在搜索 'Tokyo' - 史诗般的失败

locality: nil
name: Optional("Tokyo")
country: Optional("Japan")
formatted address: Optional([CountryCode: JP, Name: Tokyo, State: Tokyo, FormattedAddressLines: (
    Tokyo,
    Japan
), Country: Japan])

结论

不仅可以 CLGeocoder 省略结果(如 'Italy' 的情况),而且还可以告诉我一个地方没有 locality,而事实上,确实如此(我相信上次我查看地图时东京还是一座城市!)。

有谁知道我该如何解决这些问题?

天气应用程序以某种方式执行此操作 - 在那里搜索 'Singapore' 或 'Italy' 会给出正确的结果。如果有任何帮助,我将不胜感激!

P.S。虽然我使用 Swift,但我添加了 Objective-C 作为标签,因为我认为这个问题不是 Swift 特定的。

CLGeocoder 请求有速率限制,如果应用超过阈值,可能无法满足进一步的请求。 CLGeocoder 需要网络连接,这在某些情况下可能会受到限制,并且服务器的响应并不总是即时的。如果你不坚持使用 CLGeocoder,本地数据库可能会给你更多的灵活性和更好的用户体验。

查看 GeoNames 上的 cities???.zip,了解将数据导入本地数据库的替代解决方案。

使用你展示的是完全正确的,我不认为你可以做任何其他事情来改善结果。它仍然只是一些复杂的算法,试图根据一组规则和一些假设对结果进行排序。

虽然很伤心,但我完全理解您的沮丧,因为我去过那里并做到了。问题是地理编码数据库显然不完整,Apple 也不是领导该领域的公司 - Google 才是。在这里,您可以看到介绍 Geocoder 时的技术说明 there are still countries that are not supported,或者只是部分支持。

所以我给你的建议是,如果你想获得最好的结果(尽管仍然不是万无一失的),切换到 Google 地理定位。对于相当多的请求,它是免费的,之后它会花费一些非常小的费用。根据我的经验,所有基本搜索的成功率都在 99% 左右,如果您提供更多信息,它只会变得更好。 API 选项也相当广泛。

这里有 link 地理编码和反向地理编码的开发者文档: https://developers.google.com/maps/documentation/geocoding/intro https://developers.google.com/maps/documentation/javascript/examples/geocoding-reverse

希望至少能有所帮助。

编辑:写完这篇文章后,我做了一些研究,看来我不是唯一 make this claim 的人(也包括同样不受支持的 link)。

您也可以通过 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(巴西),然后我可以通过 邮政编码 找到地址。如果 geocodeAddressString 上的 placemarks return NIL,那么我调用 geocodeAddressDictionary 并收到正确的结果。
该解决方案可以涵盖 BR 的主要邮政编码。这是一个混乱的代码,但现在它可以工作。
我将在未来的项目中使用 Google 地图。