【问题标题】:Which data structure should I use for geocoding?我应该使用哪种数据结构进行地理编码?
【发布时间】:2012-04-24 19:48:04
【问题描述】:

我正在尝试创建一个 Python 脚本,该脚本将地址作为输入并吐出它的纬度和经度,或者在多个匹配的情况下吐出纬度和经度,就像Nominatim

因此,可能的输入和输出可能是:-

  1. 输入: 美国纽约 => 输出: 纽约(纬度:x1 经度:y1)
  2. 输入: 纽约 => 输出: 纽约(lat:x1 lon:y1)
  3. 输入: 美国纽约珍珠街 => 输出: 珍珠街(纬度:x2 经度:y2)
  4. 输入: 美国珍珠街 => 输出: 珍珠街 (lat:x2 lon:y2),珍珠街 (lat:x3 lon:y3)
  5. 进: 珍珠街=> 出: 珍珠街(lat:x2 lon:y2),珍珠街(lat:x3 lon:y3)
  6. 输入: 103 Alkazam,纽约,美国 => 输出: 纽约(纬度:x1 经度:y1)

在上面的6中,由于没有找到地址为103 Alkazam, New York, USA的地方,所以返回了纽约,但它至少可以找到New York, USA

最初我想构建一棵树来表示兄弟姐妹按字母顺序排序的层次关系。可能是这样的:-

                                     GLOBAL
                                       |
                   ---------------------------------------------
                   |            | ...
                  USA
             ---------------
             |        | ...
         CALIFORNIA  NEW YORK 
            |         |
     -----------    -------------
     |        |..   |          |....
 PEARL STREET      PEARL STREET

但问题是用户可以提供不完整的地址,如 2、4 和 5。

所以,我接下来想到了使用搜索树并将完全限定的地址存储在每个节点中。但这也很糟糕,因为:-

  • 这将在每个节点中存储高度冗余的数据。由于这将是一个非常大的数据,因此空间保护很重要。
  • 它将无法利用用户缩小搜索空间这一事实。

我有一个附加要求。我需要检测拼写错误。我想这必须作为一个单独的问题来处理,并且可以将每个节点视为通用字符串。

更新 1

稍微详细一点。输入将是一个列表,其中较低索引的项目是较高索引项目的父项;他们当然可能是也可能不是直接的父母或孩子。因此对于查询 1,输入将是 ["USA", "NEW YORK"]。所以,USA, New York 不返回结果是完全没问题的。

如果用户有地址并且我们的数据非常详细,他应该能够找到建筑物。

更新 2(遗漏案例)

如果用户查询Pearl Street, USA,那么我们的算法应该能够找到地址,因为它知道Pearl StreetNew York 作为父级,USA 是它的父级。

更新 3(盈余案例)

假设用户查询101 C, Alley A, Pearl Street, New York。还假设我们的数据确实知道101 C,但不知道Alley A。据它说101 CPearl Street 的直系孩子。即使在这种情况下,它也应该能够找到地址。

【问题讨论】:

  • 那么唯一的位置是街道,还是街道和城镇/城市,或者是街道(即珍珠街 63 号)、街道和城镇/城市,还是更多?
  • 可以是平号、街道、城镇/城市、州、国家。任何部分都可能丢失。
  • 我认为标签 [missing-data] 在这里比较合适。
  • 通过丢失数据我的意思是它在用户的查询中丢失。例如,检查上面的查询 4。它没有New York。我们的数据可能非常详细,也可能不非常详细。所以,在这种情况下,用户说,找我Pearl Street,它在USA,这应该可以工作,因为我们的数据知道虽然它不是直接在USA,但通过New York它是。
  • @AppleGrew - 我试图在给出太多答案与剥夺解决问题的所有乐趣之间取得平衡,或者没有提供足够的帮助,并造成挫败感(每个人都有他们自己的哲学)。你 :-)'d 的事实表明,也许我说得对,你有一个“尤里卡”时刻:-)

标签: python openstreetmap geocoding large-data-volumes large-data


【解决方案1】:

感谢大家的回答,他们的回答很有帮助,但并没有解决我需要的所有问题。我终于找到了一种处理我所有情况的方法。该方法是我在问题中建议的修改版本。

基本方法

这里我会提到一个叫做“节点”的东西,它是一个类对象,它包含地理信息,比如一个地方实体的纬度、经度,也可能是维度,以及它的完整地址。

如果实体的地址是'101 C, Pearl Street, New York, USA',那么这意味着我们的数据结构将至少有四个节点 - '101 C'、'Pearl Street'、'New York '和'美国'。每个节点将有一个name 和一个address 部分。对于“101 C”,name 将是“101 C”,地址将是“Pearl Street, New York, USA”。

基本思想是拥有这些节点的搜索树,其中节点name 将用作搜索的关键字。我们可能会得到多个匹配,所以稍后我们需要根据节点的address 与查询的匹配程度对结果进行排名。

                                    EARTH
                                      |
                ---------------------------------------------
                |                                           |
               USA                                        INDIA
                |                                           |
        ---------------------------                     WEST BENGAL
        |                         |                         |
     NEW YORK                 CALIFORNIA                 KOLKATA
        |                         |                         |
   ---------------            PEARL STREET              BARA BAZAR
   |             |                                          |
PEARL STREET   TIME SQUARE                                 101 C
   |             |
  101 C         101 C

假设我们有一个如上的地理数据。因此,搜索“101 C, NEW YORK”不仅会返回“NEW YORK”中的“101 C”节点,还会返回“INDIA”中的节点。这是因为算法将只使用name,即此处的“101 C”来搜索节点。稍后我们可以通过测量节点的address与查询地址的差异来对结果的质量进行评分。我们没有使用完全匹配,因为允许用户提供不完整的地址,就像在这种情况下一样。

评分搜索结果

我们可以使用Longest Common Subsequence 对结果的质量进行评分。这个算法很好地处理了“遗漏”和“剩余”的情况。

最好让代码来说话。以下是为此目的量身定制的 Python 实现。

def _lcs_diff_cent(s1, s2):
    """
    Calculates Longest Common Subsequence Count Difference in percentage between two strings or lists.

    LCS reference: http://en.wikipedia.org/wiki/Longest_common_subsequence_problem.
    Returns an integer from 0-100. 0 means that `s1` and `s2` have 0% difference, i.e. they are same.
    """
    m = len(s1)
    n = len(s2)

    if s1 == s2:
        return 0
    if m == 0: # When user given query is empty then that is like '*'' (match all)
        return 0
    if n == 0:
        return 100

    matrix = [[0] * (n + 1)] * (m + 1)
    for i in range(1, m+1):
        for j in range(1, n+1):
            if s1[i-1] == s2[j-1]:
                matrix[i][j] = matrix[i-1][j-1] + 1
            else:
                matrix[i][j] = max(matrix[i][j-1], matrix[i-1][j])

    return int( ( 1 - float(matrix[m][n]) / m ) * 100 )

优化方法

我放弃了上述(基本)方法,因为它强制冗余,并且它无法减少这样一个事实,即如果用户在他的查询中提供了“美国”,那么我们不需要查看“印度”中的节点。

这种优化的方法在很大程度上解决了上述两个问题。解决方案不是拥有一棵大搜索树。我们可以将搜索空间划分为“美国”和“印度”。稍后我们可以进一步按状态重新划分这些搜索空间。这就是我所说的“切片”。

在下图中 - SearchSlice 表示“切片”,SearchPool 表示搜索树。

                            SearchSlice()
                                  |
            ---------------------------------------------
            |                                           |
        SearchSlice(USA)                           SearchSlice(INDIA)
            |                                           |
    ---------------------------                  SearchPool(WEST BENGAL)
    |                         |                   |
 SearchPool(NEW YORK)     SearchPool(CALIFORNIA)  |- KOLKATA
    |                         |                   |- BARA BAZAR, KOLKATA
    |- PEARL STREET           |- PEARL STREET     |- 101 C, BARA BAZAR, KOLKATA
    |- TIME SQUARE
    |- 101 C, PEARL STREET
    |- 101 C, TIME SQUARE

上面需要注意的几个关键点...

  • 每个切片只有一层深度。嗯,这在上面并不是很明显。
  • 切片级别的名称不会出现在其子级的地址中。例如,SearchSlice(USA) 在 'USA' 中维护一个州的切片。因此,“NEW YORK”下的节点在其address 中不包含名称“NEW YORK”或“USA”。其他地区也一样。层次关系隐式定义了完整地址。
  • '101 C'address 也包括其父级的name,因为它们没有被切片。

扩展可能性

哪里有桶(池),哪里就有隐式扩展的可能性。我们(比如说)将“美国”的地理数据分为两组。两者都可以在不同的系统上。因此,如果 'NEW YORk' 池在系统 A 上,而 'CALIFORNIA' 池在系统 B 上,则完全没问题,因为它们不共享任何数据,当然除了父母。

这里是警告。我们需要复制始终是切片的父母。由于切片的数量是有限的,所以层次不会太深,所以复制它们不应该太冗余。

工作代码

请参考我的 GitHub 获取 working demo Python code

【讨论】:

    【解决方案2】:

    如何使用键值存储映射和全文搜索。

    • 位置字符串的键
    • location_level 和 lat&lon 数据的值。
    • 搜索:
      • 将用户输入的字符串拆分为单个位置的单词(不仅是逗号)
      • 在地图中搜索每个单词
      • 返回最小位置级别的纬度和经度

    python.dict,memcached,mongodb .... 将满足您的需求。

    • 如果你的位置词太多,将location_level拆分为新地图,两次搜索会加快速度
    • 忘记位置级别,将其视为全文搜索
    • 大量数据?短字符串或数字的哈希键

    需要考虑的一些问题:

    • 如何将数据存储到数据库中
    • 如何从数据中初始化搜索树(如果有)
    • 如何在运行时扩展/编辑搜索树
    • 输入/存储容错
    • 存储空间>速度?还是速度 > 存储?

    所以,更多可用的测试用例供用户输入

    101 C, Time Square, New York, US
    101 C, Pearl street, New York, US
    
    101 C, Time Square, SomeCity, Mars
    101 C
    101 C, US
    101 C, New York, US
    
    101 C, New York, Time Square, US
    
    North Door, 101 C, Time Square, New York, US
    South Door, 101 C, Time Square, New York, US
    

    针对情况

    • 大数据速度快;
    • 完全容错;
    • 通过存储和运行时间轻松调整

    最佳解决方案:(也是最复杂的)

    • 平面键值映射存储
    • 全文搜索
      • 或使用 B 树搜索的哈希键

    您的程序/网站可能能够像谷歌一样快速运行。

    【讨论】:

    • 您的意思是键是完整的位置字符串?请注意,根据数据的“完整位置”实际上可能不是完整地址。 (请参阅“更新 3”)。
    • @AppleGrew 我把事情弄得太复杂了。你有你的可运行的解决方案。
    【解决方案3】:

    如果你尝试为这个问题创建一个数据结构,我想你会有数据冗余。相反,您可以使用树/图并尝试实现一种搜索算法,该算法根据节点值搜索用户输入中的单词。 模糊匹配可以帮助您生成最可能的结果,并且您可以根据相似度的置信度向用户建议/显示其中的前几个。

    这也可以解决拼写错误等问题。

    【讨论】:

      猜你喜欢
      • 2012-07-21
      • 2013-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多