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

Posted

技术标签:

【中文标题】当城市名称等于某个国家/地区的名称(不仅如此)时,CLGeocoder 返回错误的结果【英文标题】:CLGeocoder returns wrong results when city name is equal to some country's name (and not only) 【发布时间】:2015-07-10 01:38:28 【问题描述】:

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

它在大多数情况下都非常有效。但是,我最近注意到一些失败的情况。以下是一些示例的输出:

搜索“米兰” - WORKS

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

搜索“意大利” - 失败

当我只输入“意大利”时,我只会得到国家的地标:

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

搜索“新加坡,新加坡” - WORKS

这与“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])

搜索“新加坡” - 失败

再次,似乎与“意大利”的情况相似。它只找到一个国家:

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,而事实上它确实存在(我相信上次我检查地图东京仍然是一个城市!)。

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

天气应用程序以某种方式执行此操作 - 在那里搜索“新加坡”或“意大利”会得到正确的结果。如有任何帮助,我将不胜感激!

附:尽管我使用的是 Swift,但我将 Objective-C 添加为标签,因为我认为这个问题不是 Swift 特有的。

【问题讨论】:

只是检查一下,您是否使用任何解决方案解决了您的问题? 【参考方案1】:

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

很伤心,我完全理解你的沮丧,因为我一直在那里并且做到了。问题是地理编码数据库显然并不完整,而且苹果不是领导该领域的公司——谷歌才是。在这里,您可以看到引入地理编码器时的技术说明,即 there are still countries that are not supported,或者只是部分支持。

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

以下是地理编码和反向地理编码的开发者文档链接: https://developers.google.com/maps/documentation/geocoding/intro https://developers.google.com/maps/documentation/javascript/examples/geocoding-reverse

希望它至少有一点帮助。

编辑:写完这篇文章后,我做了一些研究,似乎我不是make this claim 的唯一人(还包括相同的不受支持的链接)。

【讨论】:

是的,这很好用!不过,我现在遇到了另一个困难,因为我需要在收到地理编码 API 的响应后调用时区 API。如果您对如何最好地做到这一点有任何想法,请查看我的新问题***.com/questions/32144089/…【参考方案2】:

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

查看GeoNames 上的城市???.zip,以获得将数据导入本地数据库的替代解决方案。

【讨论】:

这如何解释东京的情况?【参考方案3】:

你也可以像这样通过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)





【讨论】:

【参考方案4】:

我在巴西也遇到过这个问题,所以我为此做了一个糟糕的解决方案:

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 返回NIL,那么我调用geocodeAddressDictionary 并收到正确的结果。 该解决方案可以覆盖 BR 的主要邮政编码。这是一个混乱的代码,但现在它可以工作。 未来的项目我将使用谷歌地图。

【讨论】:

以上是关于当城市名称等于某个国家/地区的名称(不仅如此)时,CLGeocoder 返回错误的结果的主要内容,如果未能解决你的问题,请参考以下文章

在附属机构中查找城市名称,并将它们与其对应的国家/地区添加到数据框的新列中

CoreData 检查多对多关系是不是包含对象

我如何获得某些国家/地区的城市列表?

如何为多个城市创建架构设置?

根据列中的国家/地区名称插入国家/地区官方语言

任何国家/地区城市基域重定向