Часть 1. Трагическая. “За что мне все это?!”
Столкнулись мы как-то с необходимостью ввода корректной информации о местонахождении (прописке, регистрации) пользователей, и с тем, что проблема эта решается не совсем так легко и просто, как бы нам хотелось. Сначала мы попробовали КЛАДР, в его бесплатной ипостаси. Не то чтобы нам прямо-таки решительно все не понравилось, но было как минимум одно весьма раздражающее обстоятельство — некоторые адреса отсутствовали в справочнике. Например, дом 10 есть, а 10к1 — извините, не завезли. Вообще КЛАДР был привлекателен тем, что у него есть простой API и плагины (jQuery в частности), которые можно легко встроить в приложение, но отталкивал наполнением. Мы задумались — если нет такого ресурса, содержащего полную и наиболее актуальную адресную информацию, с API и плагинами, то единственный выход — создать такой ресурс самим.
Часть 2. Искательская. “Где собака зарыта?!”
И прослышали мы про ФИАС. О том как он бесконечно полон и прекрасен. А это выход! На сайте ФИАС есть базы, и все что нужно молодой растущей информационной системе! Правда, размер базы составляет более 4 Gb, ну да ладно, это же вся Россия! Обновляется база регулярно, так что есть где разгуляться. Дело за малым — развернуть базу, прикрутить API и плагины. Надо оговориться, что существует несколько релевантных и важных статей. К примеру, цикл вот этих статей, которые изначально очень помогли.
Часть 3. «Как это работает»
Проблемы начались с того, что база данных ФИАС поставляется в формате .dbf, данные из которого надо было как-то перенести в PostgreSQL, который было решено использовать в качестве БД.
Нужно отметить, что кроме формата .dbf, база представлена в формате xml. Как известно, человеку хочется всего и сразу. Чтобы включил и работает. Но разобраться сразу с тем, что же дает ФИАС довольно проблематично.
Итак, по адресу fias.nalog.ru в разделе обновлений имеется следующее:
- Информация про обновления: по официальным данным — выходят не менее раза в неделю. Специфика следующая. Иногда выходят битые обновления. Их размер примерно 1,2 Mb и при скачивании дается битый архив, поэтому торопиться настраиваться только на последнее обновление — вызывает вопросы.
- Обновления в виде fias_delta_. — в форматах .dbf и .xml.
- Собственно база данных fias_. — в форматах .dbf и .xml.
- База данных в формате КЛАДР. Наименование файлов “Base.*” arj или 7z Отличается тем, что отсутствуют многие поля (касательно налоговых данных и т.д.), поиск осуществляется по КЛАДРовскому id. Изначально присутствует соблазн использовать именно данный формат, так как он более легок для понимания и последующего внедрения. Но при более внимательном изучении портала, обнаружится, что данная версия будет поддерживаться только до конца 2017 года, а затем будет deprecated.
Примечание: для работы с .dbf файлами необходимо установить стороннее расширение для php_dbase.dll.
Так какую же все таки выбрать версию для скачивания .xml или .dbf? Отличия следующие:
- xml — каждый файл в архиве — отдельная таблица. В чем состоит проблема. К примеру, размер файла “HOUSEXX” где хранятся дома, составляет более 20 Gb в разархивированном виде. Как его парсить? С одной стороны, если у вас более 30 Gb RAM, то это не составит проблемы. Также, если вы знаток грамотных библиотек, таких как SAX Parser для Java, которые не загружают файл целиком в память, а парсят частями, то может быть что-то и получится.
- dbf — файлы не только разделяются как таблицы, но и подразделяются по регионам. Окажется более приемлемым вариантом, если вам не нужны адреса необъятной Родины, а только одного региона (как в нашем конкретном случае).
Итак, распаковали файлы. Какая информация нам нужна? Видим следующий набор файлов:
- ADDROBХХ.DBF, где ХХ — номер региона — содержит данные непосредственно о регионе, автономных округах, городах и прочих населенных пунктах, улицах.
- HOUSEХХ.DBF, где ХХ — номер региона — содержит информацию о номерах домов.
- NORDOCХХ.DBF, где ХХ — номер региона — содержит информацию о причинах изменений в различных записях.
- ROOMХХ.DBF, где ХХ — номер региона — содержит информацию о помещениях.
- STEADХХ.DBF, где ХХ — номер региона — содержит информацию о земельных участках.
- SOCRBASE.DBF — содержит информацию о сокращениях.
- STRSTAT.DBF — содержит информацию о типе строения.
Кроме данных таблиц существует еще ряд других — служебных таблиц, которые содержат информацию о сокращениях в других таблицах.
В большинстве случаев достаточно сформировать адрес вплоть до дома. Хотя, если кому надо, то можно углубиться и дальше.
Таким образом, создадим 2 таблицы в БД postgresql.
Таблица с адресами:
CREATE TABLE addrs
(
"ACTSTATUS" integer,
"AOGUID" character varying(36) COLLATE pg_catalog."default",
"AOID" character varying(36) COLLATE pg_catalog."default",
"AOLEVEL" integer,
"AREACODE" integer,
"AUTOCODE" integer,
"CENTSTATUS" integer,
"CITYCODE" integer,
"CODE" character varying(20) COLLATE pg_catalog."default",
"CURRSTATUS" integer,
"ENDDATE" timestamp,
"FORMALNAME" character varying(120) COLLATE pg_catalog."default",
"IFNSFL" integer,
"IFNSUL" integer,
"NEXTID" character varying(36) COLLATE pg_catalog."default",
"OFFNAME" character varying(120) COLLATE pg_catalog."default",
"OKATO" VARCHAR(11),
"OKTMO" VARCHAR(11),
"OPERSTATUS" integer,
"PARENTGUID" character varying(36) COLLATE pg_catalog."default",
"PLACECODE" integer,
"PLAINCODE" character varying(20) COLLATE pg_catalog."default",
"POSTALCODE" integer,
"PREVID" character varying(36) COLLATE pg_catalog."default",
"REGIONCODE" integer,
"SHORTNAME" character varying(15) COLLATE pg_catalog."default",
"STARTDATE" timestamp,
"STREETCODE" integer,
"TERRIFNSFL" integer,
"TERRIFNSUL" integer,
"UPDATEDATE" timestamp,
"CTARCODE" integer,
"EXTRCODE" integer,
"SEXTCODE" integer,
"LIVESTATUS" integer,
"NORMDOC" character varying(36) COLLATE pg_catalog."default",
"PLANCODE" integer,
"CADNUM" integer,
"DIVTYPE" integer
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE address
OWNER to postgres;
Таблица с номерами домов:
CREATE TABLE hous
(
"AOGUID" character varying(36) COLLATE pg_catalog."default",
"BUILDNUM" character varying(10) COLLATE pg_catalog."default",
"ENDDATE" timestamp,
"ESTSTATUS" integer,
"HOUSEGUID" character varying(36) COLLATE pg_catalog."default",
"HOUSEID" character varying(36) COLLATE pg_catalog."default",
"HOUSENUM" character varying(15) COLLATE pg_catalog."default",
"STATSTATUS" integer,
"IFNSFL" integer,
"IFNSUL" integer,
"OKATO" VARCHAR(11),
"OKTMO" VARCHAR(11),
"POSTALCODE" integer,
"STARTDATE" timestamp,
"STRUCNUM" VARCHAR(15),
"STRSTATUS" integer,
"TERRIFNSFL" integer,
"TERRIFNSUL" integer,
"UPDATEDATE" timestamp,
"NORMDOC" character varying(36) COLLATE pg_catalog."default",
"COUNTER" integer,
"CADNUM" VARCHAR(50),
"DIVTYPE" integer
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
Импорт данных осуществляется простым способом. Открываем файлы в Excel и сохраняем их как csv. Дополнительно рекомендуется изменить кодировку, так как в отличии от xml файлов, которые представлены в кодировке utf-8, dbf файлы — в кодировке win-866. Открываем файлы в редакторе (для данной цели подойдет notepad++) и преобразуем в utf-8.
Импорт таблицы с адресами:
COPY addrs FROM 'PathToTheFile\ADDROB01.csv' DELIMITER ';' CSV;
Импорт таблицы с домами:
COPY addrs FROM 'PathToTheFile\HOUSE30.csv' DELIMITER ';' CSV;
Из чего сделана таблица ADDROBXX?
Несмотря на обилие полей, понадобятся только некоторые из них.
- ACTSTATUS — поле содержит значения 0 и 1. При внимательном просмотре обнаружится, что в таблице множество улиц с одним и тем же названием. Дело в том, что налоговая база содержит историю объектов, т.е. изменения, которые происходили с каким-либо элементов адреса. К примеру, улиц Ленина может быть 10 штук. Из них 9 — будут неактуальными. Они могли быть переименованы или изменены другим образом. ACTSTATUS — 0 — неактуальные адреса, а 1 — будет единственным актуальным.
- AOGUID — ID записи. Для разных исторических записей будет один и тот же AOGUID. Различаться они будут по полю AOID.
- AOLEVEL — уровень объекта. Под номером 1 — субъект федерации, 4 — город, 7 — улица и т.д.
- CENTSTATUS — статус центра. Например, города в областном центре.
- FORMALNAME — название объекта.
- PARENTGUID — ID родительского элемента. При поиске города к которому принадлежит улица. PARENTGUID улицы будет совпадать с AOGUID улицы.
- POSTALCODE — почтовый индекс.
- REGIONCODE — код субъекта федерации.
Примечание: на fias-nalog.ru можно найти полное описание всех полей.
Важное в таблице HOUSEXX:
- AOGUID — ID улицы в ADDROBXX, в которой находится дом.
- HOUSEGUID — ID дома.
- Сложность заключается в наименовании дома. Оно состоит из 4 полей:
- BUILDNUM — номер корпуса.
- HOUSENUM — номер дома.
- STRUCNUM — номер строения.
- STRSTATUS — признак строения (от 0 до 4, где 0 — никакого, 1 — строение, 2 — сооружение, 3 — литера).
Поля могут содержать схожую или идентичную информацию и выходить из ситуации приходится экспериментальным путем. В классе QuerryController (ссылка на репозиторий — ниже) есть метод: chooseBuilding, в котором мы попытались решить эту задачу. Может быть у кого-то получится лучше. Запросы к БД находятся там же.
Далее, после того, как база была импортирована в Postgres — мы занялись созданием API и плагина для нашей системы.
Для API, дабы не изгаляться, использовали Laravel. Схема запросов получилась достаточно простой. Иерархия объектов выглядит следующим образом:
- Субъект федерации
- Регион субъекта федерации
- Населенный пункт
- Улица
- Дом
К слову, тестируя систему, мы столкнулись с тем, что не во всех населенных пунктах есть улицы, и не на всех улицах есть дома, что нас немало удивило, и озадачило. Это обстоятельство не давало покоя потому, что если предоставить пользователю возможность самому вводить информацию, то понапишут такого, что невольно возникает вопрос — “Эти люди вообще в школе-то учились?!”.
Поэтому было решено не давать пользователю ни малейшей возможности для самодеятельности, а для таких вот “отсутствующих” адресов предоставить возможность подать заявку на включение отсутствующего адреса в справочную систему.
Схема запросов выглядит просто:
- myfias/choose_dstrict — список субъектов федерации
- myfias/choose_region{district_aoguid} — список районов субъекта федерации
- myfias/choose_city{region_aoguid} — список населенных пунктов региона
- myfias/choose_street{city_aoguid} — список улиц населенного пункта
- myfias/choose_building{street_aoguid} — список домов улицы населенного пункта
Для последнего пункта формируется список домов с литерами, корпусами, строениями, и прочим.
После всех мытарств сделали плагин на vue.js, для работы со справочником, и его альтернативу на jQuery.
Регион и район можно убрать за ненадобностью, так как они подтягиваются вместе с городами.
При вводе высплывают autocomplete-подсказки, как и в КЛАДР. Правда разница заключается в том что КЛАДР-плагин предназначен для автодополнения, а здесь валидным считается только адрес, выбранный из подсказок.
В папке ASPUDcomponent — находится VueJs компонент для работы с адресной базой.
Исходники доступны в нашем репозитории.
Часть 4. Как это все обновлять?
С обновлениями ситуация следующая: для начала необходимо по протоколу SOAP получить версии обновлений. Посмотреть как это делается можно в классе UpdateController (метод: filesVersions()).
Примечание: версия, которая указана последней в полученном списке — не обязательно совпадает с той, которую можно скачать на главной странице. Но не стоит спешить скачивать только последнюю версию, так как она может оказаться “битой”. Случались и таким прецеденты. Далее скачивается архив с последней версией и распаковывается. Для работы необходимо использование расширения для php (php_rar.dll).
Ну а далее выбирается необходимый файл региона (или при необходимости все файлы) для обновления БД.
Комментарии (14)
dopusteam
16.07.2017 13:52+11CREATE TABLE addrs
CREATE TABLE hous
Что мотивирует Вас использовать такие названия таблиц?
SkyCat
16.07.2017 14:42Можно использовать велосипеды из статьи (один метод открытия dbf через Excel чего стоит), а можно поставить это приложение на Python и горя не знать. Оно за вас не только базу будет импортировать, но еще и правильные запросы для поискового движка Sphinx создаст.
netpilgrim
17.07.2017 02:15возможность подать заявку на включение отсутствующего адреса в справочную систему
Могли бы поподробнее рассказать как реализовали данную возможность?
Иерархия объектов выглядит следующим образом:
Субъект федерации
Регион субъекта федерации
Населенный пункт
Улица
ДомНаселенный пункт может находится в населенном пункте. Пример: поселок Малино в Зеленограде
hayk
17.07.2017 04:02Населенный пункт может находится в населенном пункте. Пример: поселок Малино в Зеленограде
А сам Зеленоград находится в Москве. В данном случае Москва — регион, Зеленоград — НП, а поселение Малино — улица.Borz
17.07.2017 17:41а улица Садовая тогда что? дом?
http://bit.ly/2uvfseDhayk
17.07.2017 18:38Выше ошибся, Зеленограда в цепочке нет и НП — это поселение Малино.
Но в качестве улицы могут быть поселения, санатории, дачные кооперативы и прочие объекты, у которых нет деления на улицы, а только номера домов.netpilgrim
17.07.2017 19:25+1Все же Зеленоград в цепочке должен быть http://www.ifias.ru/fias71.html?i=292dc42c-1a6a-4a27-9241-75753bf86ba2
hayk
18.07.2017 17:55Ну вот НП с кодом 7700000200400 в kladr-api записан как «Малино», а в dadata «г Зеленоград, п Малино».
il_kow
17.07.2017 09:39Алгоритм действий для добавления адреса примерно такой (примеры будут на VueJS, но думаю для других фреймов будет примерно то же самое):
Посмотреть можно здесь: https://github.com/KOVCHENKO/fias/tree/master/ASPUDcomponent
Пользователь вводит информацию о своем местоположении: город/поселок/село. Далее у населенного пункта может не оказаться улиц вообще. Или домов на выбранной улице. Появляется возможность открыть модальное окно с вводом адреса. Далее введенный адрес отправляется на модерацию (отправляется по email или сохраняется в дополнительную таблицу БД для проверки).
Метод здесь:
https://github.com/KOVCHENKO/fias/blob/master/app/Http/Controllers/ApplicationController.php
Ну и далее вносится руками в БД. Согласен, что нужно проработать более автоматизированный способ, но пока что руки до этого не доходят.
hayk
17.07.2017 04:03А почему бы не использовать готовое решение? Например kladr-api или dadata (я уверен что существуют и другие подобные решения)?
il_kow
17.07.2017 09:46Да. kladr наиболее известная штука и далеко не самая плохая, если требуются к примеру адреса регионального центра, но если дело касается поселков, то все становится сложнее. Не вспомню сейчас точный пример, но сталкивалсь со следующим: при обращении к API КЛАДР для выбора домов улицы, выдаются результаты таблицы HOUSEINT (такая тоже есть в БД ФИАС). Она выдает интервалы домов, которые необходимы для налоговой. В таблице HOUSE с домами есть дома для этой улицы, но получается, что они остаются вне выборки.
P.S. Кроме того, для проекта требовались другие дополнительные поля в БД.
TimsTims
Семь бед — один ответ
SDSWanderer
Костыль и велосипед!