Постановка задачи
Как известно, один и тот же адрес можно написать различными текстовыми способами, используя сокращения, перестановку, вариации наименований и т.п. Встаёт вопрос: существует ли процедура нормализации, отождествляющая реально одинаковые и по-разному записанные адреса?
Ответ положительный, чему и посвящена данная статья.
Какие средства в принципе есть для решения задачи? Их сейчас два: выделение именованных сущностей (NER) и объекты ГАР ФИАС. NER даёт разбиение на адресные элементы и их нормализацию, ГАР ФИАС может дать уникальные идентификаторы. Задача решается, если в качестве нормализации взять множество строк возможных нормализаций наименований элементов, добавив к ним GUID-идентификаторы ГАР, если получится. Два адреса эквивалентны, если хотя бы одна строка из множеств таких их строк совпадает.
А одними объектами ГАР ФИАС можно обойтись, используя только их идентификаторы? Конечно, нет. Во-первых, это не полный классификатор, особенно в части помещений и строений, хотя и постоянно пополняемый. Во-вторых, в адресах бывают специфические элементы, которые в ГАР отсутствуют (например, Московская область, Можайский район, примерно в 0,1 км по направлению на юг от ориентира середина д.Бараново, или пересечение улиц).
Итак, утверждается, что невозможно обойтись для адреса только одной нормализованной строкой для отождествления в общем случае. Но если таких строк будет несколько, причём сформированных определённым образом, то отождествление будет с очень высокой вероятностью.
Нормализация имён улиц
Если бы наименования адресных элементов писались одинаково, то проблемы бы наполовину не было. И дело даже не в ошибках, которые тоже возможны, а в сокращениях и вариативности. Рассмотрим примеры корректных названий: ул. Жукова, ул. имени Жукова, ул. М.Жукова, ул. Марш.Жукова, ул. Жукова Маршала, ул. Имени Четырежды Героя Советского Союза Жукова Георгия Константиновича (это реальный пример, а не шутка, когда в имени уместилась краткая биография) и т.д. Как бы сделать так, чтобы их отождествить...
Для нормализации я использую Pullenti, который сам же и разрабатываю. Там при извлечении названий улиц используются следующие основные принципы:
Наименование в общем случае состоит из вариантов текстовых значений и возможного номера. Например, "ул. Восьмого Марта" - это 8 и "МАРТА", "2-я ул. Соколиной горы" - это 2 и "СОКОЛИНОЙ ГОРЫ" + "СОКОЛИНАЯ ГОРА" + "ГОРЫ СОКОЛИНОЙ" + "ГОРА СОКОЛИНАЯ", "ул.10-я" - только номер 10. Всякие летия и годовщины тоже переводятся в одно число. Километры транслируются в числа с суффиксом "км" ("на 59 км автодороги Санкт-Петербург - Псков").
Некоторые слова просто отбрасываются: "имени", "им.", "герой Советского Союза/России" и т.п.
Для строкового значения формируется максимальное число возможных вариантов путём как перестановки слов, если их несколько, так и вариацией морфологических окончаний. Например, "на ул. Гороховой" непонятно, это ул. Гороховая, или ул. имени Гороховой (фамилия), поэтому формируются оба варианта: "ГОРОХОВОЙ" + "ГОРОХОВАЯ".
Учитываются стандартные прилагательные (МАЛЫЙ, ВЕРХНИЙ, НОВЫЙ и т.п.) и их возможные сокращения, они всегда помещаются на первое место.
Если имя состоит из 2-х слов, одно из которых имя, профессия или должность, а второе похоже на фамилию, то один из вариантов всегда должен быть этим вторым словом. Например, для "ул. Маршала Жукова" это "ЖУКОВА" + "МАРШАЛА ЖУКОВА" + "ЖУКОВА МАРШАЛА".
Некоторые типы сокращений как разворачиваются, так и оставляются в исходном виде. Например, "ул. М.Жукова" - здесь сокращение может быть "МАЛАЯ", "МИХАИЛА", "МАРШАЛА" или что-либо ещё, заранее мы не знаем. Поэтому закладываем "МАЛАЯ ЖУКОВА", "М.ЖУКОВА", "ЖУКОВА".
Все эти эвристики направлены на то, чтобы на выходе получить максимальное число возможных нормализованных вариантов, по которым впоследствии можно производить сравнение. Теперь понятно, как могут быть отождествлены улицы из начала раздела: у всех них будет общая комбинация - "ЖУКОВА".
Не только названия обладают вариативностью, но и типы. Например, "Измайловский пр." - это проезд или проспект? Оба типа возможны.
Итак, две улицы одинаковы, если у них совпадает тип, хотя бы одна из строк (если есть) и номера (если есть).
Структура адреса
Адрес представляет собой последовательность адресных элементов (далее, АЭ). В принципе, в произвольном порядке, хотя обычно начинают с высокого уровня (страны, региона), заканчивая низким (дома, квартиры). Приведём типизацию АЭ по уровням:
Страна;
Регион: край, область, города Москва, Санкт-Петербург, Севастополь (в ГАР уровень 1);
Административный район (в ГАР уровень 2);
Муниципальный район: городской\муниципальный округ (в ГАР уровень 3, но в иерархии всегда принадлежит региону);
Городское\сельское поселение, внутригородской район, межселенная территория (в ГАР уровень 4, всегда принадлежит муниципальному району);
Город (в ГАР уровень 5);
Населённый пункт: село, посёлок, деревня (в ГАР уровень 6);
Элемент планировочной структуры: кварталы, зоны, массивы, территории садовых товариществ, организаций и т.п. (в ГАР уровень 7);
Элемент улично-дорожной сети (в ГАР уровень 8);
Земельный участок (в ГАР уровень 9);
Здание, сооружение (в ГАР уровень 10);
Относительный указатель (например, "Забайкальский край край, Александрово-Заводский р-н., ориентир 6100 метров на юго-запад от с.Онон-Борзя", в ГАР отсутствует);
Помещение, квартира, комната (в ГАР уровень 11);
Машиноместо (в ГАР уровень 17);
Предположим, что мы умеем отождествлять АЭ каждого из уровней. Чтобы отождествить два адреса, нужно упорядочить АЭ в каждом из адресов и произвести последовательное сравнение. Если самые нижние по уровню АЭ совпадут, то адреса эквивалентны.
Здесь проблема в том, что некоторые АЭ в иерархии могут отсутствовать или наоборот. Например, в ГАР ФИАС в общем случае 2 типа иерархии - по административным районам и по муниципальным районам, и один АЭ может в принципе иметь 2-х разных родителей. Например, "Дагестан район Агульский село Амух" и "Дагестан муниципальный район Агульский сельское поселение сельсовет Амухский село Амух" - один и тот же АЭ в разрезе этих двух иерархий. А если в регионе село с таким именем уникально, то районы могут вообще не указывать: "Дагестан село Амух". Таким образом, для тождественности АЭ должны не только совпадать сами по себе (по типам и наименованиями с вариациями), но и должно учитываться совпадение родительских АЭ вверх по иерархии.
Нормализация других объектов
Выше были описаны принципы нормализации улиц, то есть объектов уровня 8 в терминологии ГАР. Для имён вышележащих объектов используются похожие принципы: также формируются варианты, убираются лишние слова, нормализуются числа. Например, для "поселение Михайловский" (именно так правильно!) нужно "МИХАЙЛОВСКИЙ", "МИХАЙЛОВСКОЕ", "МИХАЙЛОВСКАЯ" (это уже перестраховываемся на всякий случай). Имена также слегка корректируются - убираются твёрдый и мягкий знаки, Ё переводится в Е, две одинаковые рядом буквы - в одну. В дальнейшем сравнение идёт с учётом возможного несовпадения одной буквы и т.д.
Для домов уровня 10 нормализация совсем другая. Здесь номер может в общем случае состоять из трёх элементов:
Номер дома \ владения \ домовладения;
Номер корпуса;
Номер строения \ сооружения \ литера;
Причём номер - это не только цифры, но и дроби, буквы и их комбинации, а бывают и вообще пустые ("б\н"). Здесь вариативность тоже большая. Рассмотрим на примере литеры А варианты написания: "литераА", "литА", "лит. а" или просто прикрепляют букву к номеру дома, сама буква может быть как на кириллице, так и на латинице и т.д. Здесь нормализация - это приведение значений к вышеуказанной 3-х элементной модели, после чего перевод в строку.
Для помещений уровня 11 ситуация попроще: номер один, но встречаются сложные буквенно-цифровые комбинации. Здесь также номер нормализуется и записывается строкой.
Если есть ГАР ФИАС, то информация из него позволяет в ряде случаев избавиться от неоднозначности. Например, понять, что "дом 10А" - это "дом 10 литера А", а "дом 10/1" - это "дом 10 квартира 1". Или "дом 10 к.1" - квартира или корпус.
Использование ГАР ФИАС
В настоящее время в ГАР ФИАС около 1.8 млн. объектов уровней 1-8, 49 млн. домов и участков и 54 млн. помещений. Их можно использовать для нормализации! Если удаётся адресный элемент привязать к объекту ГАР, то его уникальный GUID можно считать дополнительной строкой для сравнения (кстати, возможны несколько GUID из-за дублирования в самой базе). Это также избавит от от некоторых неоднозначностей, упомянутых выше.
При привязке следует использовать такие же алгоритмы нормализации. То есть текстовые описания ГАР объектов прогонять через тот же алгоритм нормализации, формируя множественные варианты написания при построении поискового индекса.
Такой способ позволит отождествить, скажем, переименованную улицу и улицу со старым названием (кстати, в свежей версии ГАР информация о переименовании отсутствует, к сожалению, хотя история объектов есть - а раньше старые названия были, по крайней мере, в ФИАС времён DBF).
Адрессарий - система идентификации адресов
Всё вышеизложенное позволяет выдвинуть идею адресного индекса (Адрессария). В него добавляются адреса, он разбивает адрес на элементы, пытается привязать их к уже существующим, если не получается, то добавляет их, достраивая тем самым внутри себя иерархическую систему адресов. Адресные элементы в Адрессарии имеют уникальные идентификаторы, так что его можно рассматривать как систему идентификации адресов.
Пропущенный через Адрессарий, адрес получает числовые идентификаторы его элементов. Два адреса совпадают, если равны идентификаторы его самых низкоуровневых элементов.
Если есть массив неструктурированных адресов, то его можно прогнать через Адрессарий, назначив адресам числовые идентификаторы, которые могут служить критерием эквивалентности адресов.
И это не в будущем, а есть уже сейчас!
SDK Pullenti Address
Всё вышеизложенное реализовано в SDK Pullenti Address:
выделяет адреса как из произвольных текстов, так и из "текстового поля" с вычислением коэффициентов качества такого выделения;
разбивает на адресные элементы и осуществляет нормализацию;
привязывает к объектам ГАР ФИАС, причём работа с базой ведётся не через API к внешнему ресурсу, а со специально построенным индексом в локальной файловой системе, то есть может автономно работать в "закрытом контуре";
осуществляет поиск объектов ГАР по разным признакам;
поддерживает работу с адресными индексами (Адрессариями);
реализовано в виде SDK для языков C#, Java, Python и Javascript и распространяется в виде исходных кодов, не требующих для своего функционирования никаких сторонних библиотек и ресурсов;
есть online-демонстрация на сайте, там же можно скачать свежие версии;
Автору на протяжении ряда лет несколько раз приходилось сталкиваться с задачей обработки адресов. Результаты этих столкновений оформлены в виде SDK и предлагаются Вашему вниманию.