Предназначение

SDK Pullenti Address предназначено для выделения из текстов адресов, заданных в свободной форме, их нормализации, привязки выделяемых адресов к объектам ГАР ФИАС, а также поиск по названиям, Guid и другим атрибутам объектов ГАР. Дополнительно реализован адресный индекс (Адрессарий), куда можно записывать адреса, которым система назначает уникальные числовые идентификаторы, совпадающие для эквивалентных адресов.

Продукт Non-Commercial Freeware & Commercial Software, то есть бесплатен для некоммерческого использования и небесплатен для коммерческого. Для некоммерческого варианта привязка ограничивается объектами 77-го региона (Москва), для коммерческого - объекты всех регионов России.

Объекты ГАР берутся из xml-файлов выгрузки с сайта https://fias.nalog.ru/Updates и преобразуются во внутренний индекс посредством специальной утилиты (входит в коммерческую версию). SDK Address работает с внутренним индексом, который представляет собой набор из файлов внутреннего формата, оптимизированного для поисковых задач. Тем самым обеспечивается независимость использующих SDK Address систем от внешних сервисов - работа идёт с файлами локальной папки. Само SDK для своей работы не требует никаких дополнительных установок или внешнего ПО, а ограничивается стандартными библиотеками используемого языка программирования.

SDK предлагает 2 основные функции обработки. Первая функция на вход получает текст одного адреса (например, из поля ввода), и разбирает его структуру, привязывая элементы к объектам ГАР. Вторая функция обрабатывает произвольные тексты, находя в них адреса, которые также разбираются на элементы, привязываемые к ГАР. В первой функции действуют более сложные алгоритмы, которые понимают сокращения, нижний регистр имён, пропуск ключевых слов и пр. Вторая функция ориентирована на другую задачу, поэтому и алгоритмы немного другие.

Помимо этого, SDK содержит ряд полезных функций по работе с объектами ГАР:

Независимо от наличия или отсутствия ГАР, в SDK есть адресный индекс, названный Адрессарием. Его задача - сохранять внутри себя адреса, отождествляя эквивалентные с точностью до вариации написания, присваивая им числовые идентификаторы. Внешеняя система может использовать данную возможность для "уникализации" своих адресов, то есть присваивая через Адрессарий идентификаторы своим адресам. Адрессарий - это набор файлов проприетарного формата в одной директории. Одновременно можно работать с любым количеством Адрессариев.

SDK представлено функционально эквивалентным кодом на различных языках программирования: C#, Java, Python и Javascript. Исходный код вместе с этой документацией генерируется автоматически из исходного кода C# с помощью специально разработанного конвертера UniSharping. SDK работает на всех операционных системах и платформах, где поддержаны вышеуказанные языки. Свежую версию SDK можно скачать с сайта Garfias. Там же на сайте выкладываются свежие версии индекса для 77 региона.


 Подключение SDK в C#

Для использования SDK нужно добавить в своё решение (solution) проект Address.Net.csproj для .NET Framework 4+ или Address.Core.csproj для .NET Core 2+ в зависимости от платформы, а в своём конечном проекте поставить ссылку на данный проект.

Для демо-примера необходимо скачать с сайта Pullenti архив Gar77.zip с индексом, распаковать его в папку Bin\Debug\Gar77 для Framework или Bin\Debug\netcoreapp2.0\Gar77 для Core.


 Быстрый старт

Основным является статический класс AddressService, содержащий ключевые функции. В самом начале работы необходимо вызвать метод инициализации Initialize и указать папку с индексом ГАР методом SetGarIndexPath. Только после этого SDK готово к работе (в многопоточном режиме). Отметим, что если ГАР-индекс не указать, то всё будет работать, но без привязки выделяемых из текста адресов к объектам ГАР.


AddressService.Initialize();
AddressService.SetGarIndexPath("../Gar77");

Анализ текста производится двумя методами: ProcessText и ProcessSingleAddressText.

В первом случае анализу подлежит любой текст с произвольным количеством адресов. Например, договор. Функция ProcessText ищет все адреса и возвращает список найденных объектов типа TextAddress, каждый соответствует одному найденному адресу. Этот адрес содержит список элементов адреса Items типа AddrObject, которые и привязываются к ГАР-объектам через их список Gars (список потому, что в принципе возможны несколько подходящих объектов ГАР).


List<TextAddress> addrs = AddressService.ProcessText(text);
foreach (var addr in addrs)
{
    Console.WriteLine("Address: {0}", addr.GetFullPath(", "));
    /// детализируем элементы адреса
    foreach (AddrObject item in addr.Items)
    {
         Console.WriteLine("  {0}: {1}", item.Level, item.ToString());
         if (item.Gars.Count > 0)
             /// привязанных объектов ГАР в принципе может быть несколько
             foreach (var gar in item.Gars)
                 Console.WriteLine("   Gar: {0} ({1})", gar.ToString(), gar.Guid);
     }
}

Функция ProcessSingleAddressText предназначена для обработки текста, который заведомо содержит один адрес в свободном виде, и ничего кроме. Например, текст из поля ввода адреса. Вместо списка адресов, функция всегда возвращает один адрес TextAddress. Даже если адрес не выделен вообще, то список элементов Items будет пустым. Вычисляется коэффициент качества выделения Coef: значение 100 соответствует идеальному качеству, когда в тексте отсутствуют лишние символы (неизвестные конструкции) и все элементы однозначно привязались к ГАР-объектам.


text = "Москва, ул. 16-я Парковая д.2 кв.3 и какой-то мусор";
TextAddress saddr = AddressService.ProcessSingleAddressText(text);
Console.WriteLine("\nAnalyze single address: {0}", text);
Console.WriteLine("Coefficient: {0}", saddr.Coef);
if (saddr.ErrorMessage != null)
    Console.WriteLine("Message: {0}", saddr.ErrorMessage);
foreach (var item in saddr.Items)
{
    Console.Write("Item: {0}", item.ToString());
    if (item.Gars.Count > 0)
       foreach (var gar in item.Gars)
           Console.Write(" (Gar: {0}, GUID={1})", gar.ToString(), gar.Guid);
     Console.WriteLine("");
}

У обработки ProcessSingleAddressText есть большое преимущество по сравнению с результатом через ProcessText. Происходит более точный и качественный анализ в случае ошибок, сокращений, пропуска ключевых слов, использования нижнего регистра и т.п. Например, Ряз.обл,рязанский, рязань, пужкина,18 во втором случае обработает правильно, а в первом не поймёт никакой из элементов этого адреса. Но если нужно выделять адреса из произвольных текстов, то второй способ не сработает в отличие от первого.

Метод GetObjects возвращает список дочерних ГАР-объектов по идентификатору родительского объекта (если null, то возвращает список самого верхнего уровня, то есть регионы). Метод GetObject вернёт ГАР-объект по его идентификатору, что пригодится для движения по иерархии вверх.

Поиск по тексту наименований ГАР-объектов и по реквизитам производится через функцию SearchObjects, параметры поиска оформляются классом SearchParams, результатом является класс SearchResult с ограниченным списком найденных объектов (их в принципе может быть много) и общим количеством подходящих объектов.

Если в тексте адреса не задан населённый пункт, а только улица с домами, то можно указать дефолтовый ГАР-объект, используемый для таких случаев, что будет учитываться при обработке. Задаётся объект функцией SetDefaultObject, получить текущий дефолтовый объект можно функцией GetDefaultObject. Это может пригодиться при обработке адресов конкретного города, который подразумевается и который могут не указывать в тексте.


 Модель адреса

Адрес состоит из нескольких элементов, упорядоченных по уровню в порядке убывания. На верхнем уровне находятся страны и регионы, на нижнем - дома и помещения. ГАР предлагает свою систему уровней, которая оформляется перечислением GarLevel и взята за основу уровней AddrLevel для адресов из текста. Приведём соотношение этих уровней и их описание.

Адресный уровень AddrLevelГАР уровень GarLevelОписание
CountryНетСтрана (для РФ не задаётся)
RegionCityRegionГород регионального значение (Москва, Сантк-Петербург, Севастополь)
RegionAreaРегионы РФ (области, республики, края)
DistrictAdminAreaАдминистративный район
MunicipalAreaМуниципальный район
SettlementSettlementСельское/городское поселение
CityCityГород
НетDistrictРайоны города (устарело)
LocalityLocalityНаселенный пункт
TerritoryAreaЭлемент планировочной структуры (кварталы, территории)
StreetStreetЭлемент улично-дорожной сети (улицы)
BuildingPlotЗемельный участок
BuildingЗдание (сооружение)
ApartmentRoomПомещение, квартира
RoomКомната
НетCarplaceМашино-место
SpecialНетСпецифический объект (сейчас указатель)

В зависимости от уровня, элемент может иметь свой набор атрибутов. Базовый класс наборов атрибутов - пустой. Наследными являются классы AreaAttributes, HouseAttributes, RoomAttributes и SpecialAttributes. Общая диаграмма представлена на рисунке.

Атрибуты объектов уровней с верхнего до улиц оформляются классом AreaAttributes. Они имеют один или несколько типов Types, несколько имён Names (в том числе могут их и не иметь), а также может быть номер Number.

Для уровня "здание" - класс HouseAttributes. В общем случае здание имеет номер Number, корпус BuildNumber и строение StroenNumber. Дом и строение имеют дополнительные типы Typ и StroenTyp.

Для помещений и комнат класс для атрибутов RoomAttributes содержит только номер Number и тип Typ.

Для Special класс SpecialAttributes содержит тип Typ и параметр Param. Сейчас этим классом моделируются детализации относительно ориентиров, в будущем добавятся и другие элементы. Например, Московская область, Можайский район, примерно в 0,1 км по направлению на юг от ориентира середина д.Бараново содержит такой объект с типом South и параметром 100 (приводится в метры). В ГАР такие объекты, естественно, отсутствуют.


 Объекты ГАР

Объекты ГАР реализованы классом GarObject, имеющим поля:

У каждого ГАР-объекта есть словарь, в котором находятся различные реквизиты типа почтового индекса, кадастрового номера и др. - список типов параметров определятся перечислением GarParam. Получить такой словарь можно фунцией GetParams, либо получить конкретный реквизит функцией GetParamValue.

Предвосхищаем вопрос: а почему нельзя было вместо Id использовать Guid? Потому, что с ними очень неэффективно работать в условии, когда частой операцией является получение объекта по его Id, например, при построении иерархии вверх. Пришлось вводить внутренние Id для осуществления быстрого доступа. ВНИМАНИЕ! Внешним системам не следует привязываться к этим идентификаторам, так как в очередной версии индекса они вообще говоря изменяются - для внешней идентификации следует использовать Guid.

Объект по его идентификатору Id (не Guid!) можно получить функцией GetObject, список дочерних объектов извлекается функцией GetObjects, передавая на вход идентификатор родителя или null для верхнего уровня иерархии (регионов). Это сделано намеренно, так как такое получение связано с обращением к индексу ГАР и, вообще говоря, требует некоторых вычислительных ресурсов.

Поиск ГАР-объектов производится функцией SearchObjects, на вход подаются параметры поиска, оформленные классом SearchParams, на выходе возвращается объект SearchResult:

Возможны 2 вида поиска:

  1. По реквизиту: тип реквизита GarParam задать в ParamTyp, само значение поместить в ParamValue, причём значение задавать ПОЛНОСТЬЮ, так как поиск идёт по строгому совпадению. В результате вернёт список подходящих ГАР-объектов, содержащих искомый реквизит.
  2. По названиям: комбинация из кода региона Region, названия района Area, населённого пункта City и улицы Street. Здесь можно указывать не полные имена, а начала имён. Также для составных названий допустимы сокращения стандартных прилагательных. Например, для улицы "Большая Каретная" можно "Б карет" или "каретн бол". Подстроки не с начала названия недопустимы. Возможна одна ошибка в написании - пропуск\вставка буквы и замена одной буквы кроме первой и последней, но при этом имя должно быть написано целиком. Например, "моск", "масква", "мосва" привяжет к Москве, а "маскв" уже нет.

Поиск по названиями может помочь в реализации полей ввода, когда по первым введённым символам система предлагает пользователю выбор из возможных вариантов, причём на фоне уже выбранных объектов более высоких уровней.


 Адреса из текста

Адрес, выделяемый из текста, оформляется классом TextAddress. Он содержит список адресных элементов Items класса AddrObject, упорядоченный по убыванию уровня, а также ряд дополнительных атрибутов. Такие адреса формируются или функцией ProcessText, возвращающей список TextAddress, либо функцией ProcessSingleAddressText, возвращающей один адрес.

Элемент адреса AddrObject имеет набор атрибутов Attrs - экземпляр от наследного класса от BaseAttributes, уровень Level и список привязанных ГАР-объектов Gars, которых в общем случае может быть несколько или вообще не быть. Взаимосвязь классов дана на диаграмме.

Класс адреса TextAddress содержит ещё и такие атрибуты:

Независимо от ГАР, у текстовых объектов AddrObject можно использовать функцию ToString() для получения нормализованного представления. Отметим, что это только один из нормальных вариантов, которого для практических задач может вполне хватить. Но функция ToString() выводит только текущий объект. Для нормального вывода всего адреса целиком можно вызвать GetFullPath, параметром задавая разделитель.

У объектов AddrObject есть ещё одно поле - ссылка на другой объект CrossObject, которым оформляются случаи задания объектов на пересечении улиц. Например, гор.Тула ул.Кауля/Р.Лозинского д.82/35. В этом случае объект для улицы оформляется для "ул.Рауля", а CrossObject - для "ул.Р.Лозинского", и каждый из них привязывается к своим объектам ГАР. Также и на уровне домов объект для "д.82" и CrossObject для "д.35" с привязкой к ГАР-объектам по своим улицам каждый.

Также бывают ситуации, когда в адресе указываются несколько домов или квартир, через запятую или диапазоном. В этом случае в список Items попадает только первый из них, а остальные заносятся в список AdditionalItems. Например, "... дом 10 кв.1,2 и 3-6" в основной список попадёт квартира 1, а квартиры 2, 3, 4, 5 и 6 в дополнительный список, причём эти объекты также будут привязываться к ГАР.


 Адрессарий

Адресный индекс (Адрессарий) предназначен для хранения адресов и отождествления похожих. Для работы с ним есть класс AddressRepository. После создания экземпляра, нужно вызвать функцию Open, передав параметром имя локальной папки (если не существует, то будет создана), в конце работы нужно вызвать Dispose.

Добавляются адреса функцией Add, где параметром является не текст адреса, а уже разобранный адрес TextAddress. Добавлению подлежат все его элементы AddrObject. При этом у добавляемых элементов AddrObject в случае успеха устанавливается ссылка RepObject на экземпляр репозиторного объекта RepaddrObject, который либо создаётся и добавляется в Адрессарий, либо берётся существующий в случае отождествления. Если нужно только искать существующие элементы без добавления, то используется функция Search.

Если поле RepObject у адресного элемента получилось null, то по каким-то причинам адресный элемент не может быть добавлен. Например, при нарушении принципа иерархии - указана квартира без дома, для адреса "Москва, дом 10" дом не будет добавлен. Также объект самого верхнего уровня должен быть город, регион или страна, иначе это не считается полноценным адресом.

Замечание: мы не рекомендуем добавлять адреса с коэффициентом Coef меньше 80, так как качество таких адресов - сомнительное.

При добавлении некоторой порции адресов (500-1000) и в конце добавления нужно вызывать функцию Commit, чтобы несохранённые данные записались на диск. Если этого не сделать, то индекс может испортиться и не работать корректно. В конце загрузки большого количества адресов (сотен тысяч) не обязательно, но желательно вызвать Optimize, которая уменьшит общий объём файлов индекса и оптимизирует его для функций поиска.

Объект Адрессария представлен классом RepaddrObject. Он имеет:

Внутри Адрессария объекты образуют иерархию, но не жёсткую, так как в принципе объект иерархически может законно относиться к разным элементам. Например, в ГАР ФИАС подобное можно наблюдать из-за несовпадающей административной и муниципальной иерархий. В адресе могут какие-то второстепенные элементы опускать, или наоборот - вставлять. Поэтому у объекта может быть несколько потенциальных родительских объекта.

Для получения объекта по идентификатору используйте функцию GetObject(id), для списка дочерних элементов GetObjects.

А как связан Адрессарий с ГАР? Дело в том, что адрессарий для добавляемых элементов использует не только разные формы записи наименований, чтобы отождествлять с точностью до таких вариаций, но и Guid объектов ГАР, если таковая привязка имела место, то есть идентификатор ГАР используется как дополнительный вариант наименования. Это повышает качество привязки. Например, в случае переименования улицы её Guid-сохранится, хотя наименование изменится. Так что ГАР не обязателен, но желателен (для российских адресов).

ВНИМАНИЕ! В текущей реализации работа с экземпляром Адрессария однопоточная, то есть нельзя из разных потоков добавлять и из других искать. Если потребуется, в будущем поддержим многопоточность.


 Индекс ГАР

Исходные данные ГАР ФИАС выкладываются на сайте ФНС, и представляют собой zip-архив около 36Гб (280Гб после распаковки) с файлами формата xml, в которых и находится вся информация.

Для преобразования в индекс, используемый в SDK Address, разработана специальная утилита gar2pullenti.exe, преобразующая xml-файлы в несколько десятков файлов проприетарного формата, оптимизированного для решения поисковых задач. Эта утилита входит в коммерческую версию SDK. Поскольку процесс трансформации занимает около 11 часов (плюс несколько часов на скачивание архива и около часа на разархивирование), то планируется ежемесячное обновления преобразованного индекса ГАР, чтобы пользователи не тратили на это свои ресурсы. Полный индекс ГАР доступен для коммерческой версии, для бесплатной выкладывается индекс 77-го региона (Москва).

Утилита запускается в пакетном режиме и управляется следующими аргументами командной строки:

Например, если индекс планируется использовать только вплоть до улиц в поле ввода, то нет смысла грузить строения и помещения, на порядок увеличивающие размер индекса.


 Рекомендации по обработке

Выделяемые из текста адреса оформляются объектами TextAddress, содержащие суммарные коэффициент качества Coef. В дальнейшую обработку адрес рекомендуется брать только если этот коэффициент больше некоторого порога, зависящего от конечной задачи. Мы рекомендуем игнорировать адреса с коэффициентом ниже 80.

Если нужно обрабатывать тексты, которые содержат только адреса (например, из полей ввода), то используйте функцию ProcessSingleAddressText. Примерная скорость обработки на компьютере средней мощности: 100-200 адресов в секунду при использовании SDK C# и Java.

Если у вас в файле много адресов в заранее известном структурированном виде (например, CSV-файл или текстовой файла с адресом в каждой строке), то нужно самим разбить текст на фрагменты с адресами и обрабатывать каждый через ProcessSingleAddressText, а не пытаться прогнать всё через ProcessText. Во втором случае адреса будут выделены, но качество может оказаться хуже, так как в первом случае используются дополнительные алгоритмы коррекции и анализа. ProcessText следует использовать только для неструктурированных текстов, в которых заранее неизвестны места расположения адресов, например, для Html-страниц или договоров.

Для SDK Python скорость раз в 20 ниже, но здесь поможет сервер Address.Server.dll, входящий в коммерческую версию и реализованный на .NET Core. Он запускается в локальной сети или на том же компьютере, а SDK взаимодействует с ним по TCP-IP. Если вызвать функцию SetServerConnection, подав на вход uri сервера (по умолчанию, http://localhost:2222), то все функции AddressService будут отрабатывать не через логику текущего приложения, а отправляться на сервер для обработки. Это может на порядок улучшить производительность. В данном случае вместо ProcessSingleAddressText лучше использовать ProcessSingleAddressTexts, обрабатывая адреса порциями по 200-300 штук. Кстати, при работе через сервер не нужно инициализировать Initialize.

Для SDK Javascript скорость раз в 5 ниже, чем для C# и Java, и здесь также можно использовать сервер для ускорения.

После обработки адреса мы получаем иерархический список его элементов в терминах уровней ГАР, с привязкой к ГАР-объектам или без. Эту информацию можно далее сохранять в свою БД, каким-то образом учитывая эту иерархию и отождествляя с уже существующими элементами, что тоже непростая задача. Порекомендуем использовать Адрессарий, который и предназначен для решения такой специфической задачи.

Если нужно быстро обработать адреса из какого-нибудь поля CSV-файла, то для пользователей Windows в стенде Address.Textdesk есть такая возможность на вкладке "Обработка CSV" (см. далее).


 Визуализация обработки (стенд)

SDK Address в своём составе не содержит никаких средств визуализации, так как является кросс-платформенной и мультиязычной. Некоторая визуализация обработанных данных есть на сайте Pullenti в разделе Online-demo. Для пользователей Windows есть стенд, в котором можно провести анализ и получить результаты в наглядном виде, а также поработать с объектами ГАР.

Архив стенда Address.TestDesk нужно скачать с сайта, распаковать в директорию и запустить исполняемый файл Address.Testdesk.exe. Также нужно скачать и распаковать предлагаемый индекс gar77.zip, указав эту папку в стенде через кнопку "Открыть индекс". На вкладке "Индекс ГАР" выводится иерархия объектов, в правом окне отображаются реквизиты и параметры текущего объекта. Фактически это дерево реализовано через функцию GetObjects.

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

На вкладке "Обработка текста" демонстрируется функция ProcessText и визуализация результата:

В левом верхнем окне задаётся текст, после нажатия на кнопку "Обработать" в таблице выводятся текстовые объекты AddrObject, в разрезе уровней ГАР (серым подсвечены непривязанный к ГАР элементы). Выделенные адреса выделяются жирным шрифтом в обработанном тексте. В правом окне выводятся те объекты ГАР, к которым привязались текстовые элементы (при этом добавляются и все ГАР-объекты вверх по иерархии, даже если они явно не присутствовали в тексте).

На вкладке "Обработка адреса" демонстрируется функция ProcessSingleAddressText и визуализация результата аналогично вкладке "Обработка текста", только выделяемый адрес всегда один и используются более мощные алгоритмы.

На вкладке "Обработка CSV" можно обработать множество адресов из CSV-файла и сохранить результат в CSV, добавив в него поля с новыми данными.

На вход стенд понимает CSV-файл с разделителями ';' в кодировке UTF-8 или Windows-1251. Это также может быть текстовой файл, у которого в каждой строке находится адрес и ничего кроме. Слишком много адресов стенд может не уместить в памяти, поэтому в файле не должно быть более нескольких сотен тысяч адресов. После загрузки файла в случае нескольких полей система пытается найти то, которое содержит адреса, и выводит это поле в выпадающем списке на панели. Если адреса содержит другое поле, то его нужно выбрать из списка. Кстати, xls и xlsx файлы можно преобразовать в CSV утилитой file2csv.exe, входящей в архив со стендом. В архив также добавлен файл demo77.csv с московскими адресами для демонстрации этого функционала.

Обработка производится по нажатию на кнопку "Обработать". Процесс можно прервать кнопкой "Стоп" рядом с бегущей строкой. На панели отображается статистика - сколько адресов имеют коэффициент 100, сколько от 90 до 99, сколько от 80 до 99 и все остальные. В таблице адреса с коэффициентом 100 выделяются зелёным фоном, сам коэффициент выводится в первой колонке. Нажатие на колонку приводит к сортировке по этому значению. Кнопкой "Убрать 100" можно убрать все зелёные адреса, оставив "плохие" (их можно сохранить в файл и отправить разработчику для совершенствования алгоритма).

Сохранить результат можно кнопкой "Сохранить", в результирующий файл попадут все поля из исходного файла, при этом к ним добавятся дополнительные поля:

Здесь если элемент некоторого уровня привязался к ГАР-объекту, то в первом поле выводится его (ГАР-объекта) текстовое представление, а во второе поле его GUID. Если привязались несколько объектов, то выводится только первый (случайно взятый). Если ни одного объекта не привязалось, то в первое поле выводится нормализованное значение этого текстового элемента, второе поле пустое. Если для уровня элемент отсутствует, то оба поля пустые.