Какую структуру данных следует использовать для геокодирования?


Я пытаюсь создать скрипт Python, который будет принимать адрес в качестве входных данных и выплевывать его широту и долготу, или широты и долготы в случае нескольких совпадений, совсем как Nominatim.

Таким образом, возможные входы и выходы могут быть: -

  1. в Нью-Йорк, США => из: Нью-Йорк (широта:долгота Х1:У1)
  2. в Нью-Йорк => из: Нью-Йорк (широта:долгота Х1:У1)
  3. В: Жемчужина Улица, Нью-Йорк, США => Out: Pearl Street (lat: x2 lon: y2)
  4. в Перл-Стрит, США => отсюда: Жемчужная улица (широта:долгота Х2:У2), Перл-Стрит (широта:долгота Х3:У3)
  5. в Перл-Стрит => из: Жемчужная улица (широта:долгота Х2:У2), Перл-Стрит (широта:долгота Х3:У3)
  6. In: 103 Alkazam, New York, USA = > Out: New York (lat:x1 lon: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, то наш algo должен быть в состоянии найти адрес, так как он знает, что Pearl Street имеет New York в качестве родителя и USA является его родителем.

Обновление 3 (Избыточный Случай)

Предположим, что пользователь запрашивает 101 C, Alley A, Pearl Street, New York. Также предположим, что наши данные знают о 101 C, но не о Alley A. Согласно ему 101 C является непосредственным потомком Pearl Street. Даже в этом случае он должен быть в состоянии найти адрес.

3 8

3 ответа:

Как насчет использования карты хранения ключ-значение и полнотекстового поиска?

  • ключ для строки местоположения
  • значение для данных location_level и lat&lon.
  • поиск по:
    • разделить строку ввода пользователя на отдельные слова местоположения (не только через запятую)
    • поиск каждого слова в карте
    • возвращает lat&lon наименьшего уровня местоположения

Питон.дикт, memcached, mongodb .... удовлетворит вашу потребность.

  • Если у вас слишком много слов расположения, разделите location_level как новая карта, два поиска ускорят
  • забудьте уровни локации, traet как полнотекстовый поиск
  • огромные данные? хэш-ключ к короткой строке или числам

Некоторые вопросы для рассмотрения:

  • Как хранить данные в базе данных
  • как инициализировать дерево поиска из данных, если таковые имеются
  • Как расширить / отредактировать дерево поиска во время выполнения
  • отказоустойчивость для ввода / хранения
  • место для хранения > скорость? или скорость > хранилище?

Итак, более пригодный тестовый случай для пользовательского ввода

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

Для ситуации :

  • быстрая скорость с огромными данными;
  • полная отказоустойчивость;
  • легко настраивается с помощью хранения и времени выполнения

Лучшее решение : (также сложное-est)

  • хранилище плоских карт ключ-значение
  • полнотекстовый поиск
    • или хэш-ключ с поиском по B-дереву

Ваша программа / сайт может быть, способен работать так же быстро, как google.

Спасибо всем за их ответы, их ответы были полезны, но не касались всего, что мне было нужно. В конце концов я нашел подход, который взял на себя все мои дела. Этот подход является модифицированной версией того, что я предложил в вопросе.

Основной Подход

Здесь я буду ссылаться на что-то под названием "узел", это объект класса, который будет содержать геоинформацию, такую как широта, долгота, возможно, измерение объекта и его полный адрес.

Если адрес объекта - "101 C, Pearl Street, New York, USA", то это означает, что наша структура данных будет иметь по крайней мере четыре узла для - "101 C", "Pearl Street", "New York" и "USA". Каждый узел будет иметь 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 с, Нью-Йорк" вернет не только узлы "101 С" в "Нью-Йорке", но и в "Индии". Это происходит потому, что algo будет использовать только name, то есть '101 C' здесь, для поиска узлов. Позже мы можем оценить качество результата, измерив разницу address узла от запрошенного адреса. Мы не используем точное совпадение так как пользователю разрешается указывать неполный адрес, как и в данном случае.

Сортировка Результатов Поиска

Для оценки качества результата мы можем использоватьсамую длинную общую подпоследовательность . Случаи "упущения" и "избытка" хорошо рассматриваются в этом algo.

Лучше всего, если я позволю коду говорить. Ниже приведена реализация 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) поддерживает срез Штатов в "США". Таким образом, узлы под "Нью-Йорком" не включают название "Нью-Йорк" или " США " в их address. То же самое касается и других регионов. Отношение иерархии неявно определяет полный адрес.
  • '101 C' address также включает в себя родительские name, поскольку они не разрезаны.

Возможности Масштабирования

Там, где есть ведро (пул), существует неявная возможность масштабирования. Мы (скажем) разделяем географические данные для " США " на две группы. И то и другое может быть в разных системах. Таким образом, совершенно нормально, если пул "Нью-Йорк" находится в системе а, а пул "Калифорния" - в системе в, поскольку они не обмениваются никакими данными, кроме родителей курс.

Вот предостережение. Нам нужно дублировать родителей, которые всегда будут кусочком. Поскольку срезы должны быть ограничены в количестве, чтобы иерархия не была слишком глубокой, поэтому дублирование их не должно быть слишком избыточным.

Рабочий Код

Пожалуйста, обратитесь к моему GitHub длярабочего демонстрационного кода Python .

Если вы попытаетесь создать структуру данных для этой проблемы, я думаю, что у вас будет избыточность данных. Вместо этого вы можете использовать дерево / график и попытаться реализовать алгоритм поиска, который ищет слова из входных данных Пользователя по значениям узлов. нечеткое сопоставление может помочь вам получить наиболее вероятные результаты, и вы можете предложить/показать пользователю несколько лучших из них в соответствии с уровнем доверия их сходства.

Это также может позаботиться об орфографических ошибках и т. д.