ФИАС: различия между версиями

Материал из GIS-Lab
Перейти к навигации Перейти к поиску
Строка 340: Строка 340:
# <span style="color:green;">CURRSTATUS</span> правильный (0 - актуальный, 1 - исторический, 51 - переподчиненный);
# <span style="color:green;">CURRSTATUS</span> правильный (0 - актуальный, 1 - исторический, 51 - переподчиненный);
# <span style="color:green;">LIVESTATUS</span> показывает единственную актуальную запись, но значения инвертированы (см. прим. 1).
# <span style="color:green;">LIVESTATUS</span> показывает единственную актуальную запись, но значения инвертированы (см. прим. 1).
//ToDo - изменения в подчиненных объектах


'''Примечания:'''
'''Примечания:'''

Версия от 11:27, 12 января 2013

Эта страница является черновиком статьи.


Введение

Вопросы/пожелания/замечания - http://gis-lab.info/forum/viewtopic.php?f=3&t=12568

Федеральная информационная адресная система (ФИАС) создана Распоряжением Правительства Российской Федерации от 10.06.2011 №1011-р. С целью...

Замена КЛАДР... Тем не менее классификатор КЛАДР продолжает публиковаться и обновляться - версия 4.0 от 24.12.2012.

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

Загрузка данных

Процесс импорта данных из файлов XML в реляционную базу данных дается на примере PostgreSQL. Все применяемые инструменты являются кросплатформенными. Для других БД (MySQL, Oracle и т.п.) процедура потребует незначительной доработки. См. также гл. 2.3, в которой приводятся ссылки на сторонние проекты, предоставляющие подготовленные данные в других форматах.

Создание таблиц

На сайте ФИАС представлены схемы XSD, описывающие структуру данных. Для преобразования схемы в формат SQL (CREATE TABLE...) применим XSL Transformation (XSLT). В зависимости от БД может потребоваться изменить типы данных колонок.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
	xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:output method="text" encoding="UTF-8" indent="no"/>

<xsl:template match="/">
<xsl:variable name="filename" select="lower-case(tokenize(tokenize(base-uri(.), '/')[last()],'\.')[1])" />

DROP TABLE IF EXISTS <xsl:value-of select="$filename"/>;
CREATE TABLE <xsl:value-of select="$filename"/> (
<xsl:for-each select="/xs:schema/xs:element[1]/xs:complexType[1]/xs:sequence[1]/xs:element[1]/xs:complexType[1]/xs:attribute" >
    <xsl:text>  </xsl:text><xsl:value-of select="@name"/><xsl:text> </xsl:text>
	<xsl:choose>
		<!-- тип данных -->
		<xsl:when test="xs:simpleType/xs:restriction/@base='xs:integer'">integer</xsl:when>
		<xsl:when test="xs:simpleType/xs:restriction/@base='xs:byte'">integer</xsl:when>
		<xsl:when test="xs:simpleType/xs:restriction/@base='xs:string'">text</xsl:when>
		<xsl:when test="@type='xs:date'">date</xsl:when>
	</xsl:choose>
	<xsl:if test="@use='required'"> NOT NULL</xsl:if>
	<xsl:if test="position()!=last()">,&#xa;</xsl:if>
</xsl:for-each>
);

<xsl:for-each select="/xs:schema/xs:element[1]/xs:complexType[1]/xs:sequence[1]/xs:element[1]/xs:complexType[1]/xs:attribute" >
	<a>COMMENT ON COLUMN <xsl:value-of select="$filename"/>.<xsl:value-of select="@name" /> IS </a>
	<xsl:choose>
    		<!-- Часть комментариев слишком длинная, обрезаем лишние строки -->
		<xsl:when test="contains(xs:annotation/xs:documentation,'&#xa;')">
			<a>'<xsl:value-of select="substring-before(xs:annotation/xs:documentation,'&#xa;')"/>'</a>
		</xsl:when>
		<xsl:otherwise>'<xsl:value-of select="xs:annotation/xs:documentation"/>'</xsl:otherwise>
	</xsl:choose>
	<xsl:text>;&#xa;</xsl:text>
</xsl:for-each>
	
</xsl:template>
</xsl:stylesheet>

Схема подходит для всех таблиц ФИАС. Для работы требуется процессор, поддерживающий XSLT 2.0. Например, Saxon XSLT Processor (MPL). Тогда обработка файлов принимает вид (версии .NET, Windows):

@echo off
SET PATH=%PATH%;d:\Program Files\Saxonica\SaxonHE9.4N\bin

for %%f in (*.xsd) do (
    Transform ^
        -xsl:xsd2db.xslt ^
        -s:%%f ^
        -o:sql\%%~nf.sql
)

Далее полученные файлы SQL желательно откорректировать, чтобы явно назначить ключи (primary keys).

//// Ссылка на готовые файлы

Импорт данных

Для работы с большими файлами XML предпочтительнее применять потоковые парсеры: используется фиксированное количество оперативной памяти (в приведенном скрипте - порядка 80 Мб) на протяжении всего процесса работы.

Ниже приводится скрипт ETL Scriptella (Apache License) на базе парсера SAX. Он подходит для любой реляционной БД, для которой есть java-драйвер.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE etl SYSTEM "http://scriptella.javaforge.com/dtd/etl.dtd">
<etl>
    <connection id="in" driver="janino"/>
    <connection id="out" driver="postgresql" url="jdbc:postgresql://localhost:5432/address" user="postgres" password="secret">
        autocommit.size=100
    </connection>

    <query connection-id="in">
        import javax.xml.parsers.SAXParser;
        import javax.xml.parsers.SAXParserFactory;
        import org.xml.sax.Attributes;
        import org.xml.sax.SAXException;
        import org.xml.sax.helpers.DefaultHandler;

        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser saxParser = factory.newSAXParser();

        DefaultHandler handler = new DefaultHandler() {
            boolean isObject = false;
            int recordCnt = 0;

            public void startElement(String uri, String localName,String qName, Attributes attr) throws SAXException {
                if (qName.equalsIgnoreCase("Object")) {
                    String names = "";
                    String values = "";
                    
                    int attrCnt = attr.getLength();
                    for (int i=0; i &lt; attrCnt; i++) {
                        names += attr.getQName(i) + ",";
                        values += "'" + attr.getValue(i).replaceAll("'", "''") + "',";
                    }

                    names = names.substring(0, names.length()-1);
                    values = values.substring(0, values.length()-1);
                    set("sql_names", names);
                    set("sql_values", values);
                    <script connection-id="out">
                        INSERT INTO ###TABLE### ($sql_names) VALUES ($sql_values);
                    </script>
                    next();

                    recordCnt++;
                    System.out.print("\rprocessed: " + recordCnt);
                }
            }
        };

        System.out.println("\n\n\nConverting...");
        saxParser.parse("../xml/###TABLE###.xml", handler);
    </query>
</etl>

Примечания:

  1. Паттерн ###TABLE### следует заменить на имя файла/таблицы (например, ADDROBJ);
  2. Да работы требуются дополнительные библиотеки: Janino, PostgreSQL JDBC Driver;
  3. Выполняется простейшее экранирование значений (одиночная кавычка ').

Другие источники

Данные ФИАС также распространяются в формате DBF. Для работы с ними рекомендуются утилиты типа PgDBF (импорт ФИАС).

  • BasicData.ru - WebAPI и файлы для импорта в БД MySQL. Также может быть интересна БД "Почтовые отделения".
  • Классификаторы России (rus-ref)
  • zXML Parser - "Парсинг баз ФИАС в MySQL (потоковый XML парсер)". Программа из данных XML создает файл с sql-запросами.

Структура данных ФИАС (описание таблиц)

Для понимания структуры данных ФИАС требуются следующие документы:

  1. Сведения о составе информации Федеральной информационной адресной системы (DOC);
  2. Описание Классификатора адресов российской федерации (КЛАДР) (в архиве DOCUM.ARJ)

Таблицы

Основные

  • ADDROBJ - Классификатор адресообразующих элементов (край > область > город > район > улица)
  • HOUSE - Сведения по номерам домов улиц городов и населенных пунктов, номера земельных участков и т.п
  • HOUSEINT - Интервалы домов
  • LANDMARK - Описание мест расположения имущественных объектов
  • NORMDOC - Сведения по нормативному документу, являющемуся основанием присвоения адресному элементу наименования

Справочники

  • SOCRBASE - Типы адресных объектов (условные сокращения и уровни подчинения)
  • CURENTST - Статус актуальности КЛАДР 4.0
  • ACTSTAT - Статус актуальности ФИАС
  • OPERSTAT - Статус действия
  • CENTERST - Статус центра
  • INTVSTAT - Статус интервала домов
  • HSTSTAT - Статус состояния объектов недвижимости
  • ESTSTAT - Признак владения
  • STRSTAT - Признак строения
  • NDOCTYPE - Тип нормативного документа (закон, приказ, справка) // не упоминается в официальной документации

ADDROBJ

//ToDo:

  • текстовые элементы адреса
  • статус актуальности
  • исторические названия
  • иерархия
  • ссылки, классификаторы

Иерархия административных единиц

В таблице ADDROBJ иерархия построена по типу плоского дерева. И родительские и дочерние элементы хранятся в одной таблице. Воссоздание иерархии выполняется с помощью полей:

  • AOGUID - Глобальный уникальный идентификатор адресного объекта. Не смотря на название, уникальным в пределах таблицы он не является. Могут существовать несколько исторический версий и одна единственная актуальная для данного объекта. Подробнее см. раздел "Статус актуальности".
  • PARENTGUID - Идентификатор объекта родительского объекта. Содержит ссылку на AOGUID родительского элемента.
  • AOLEVEL - Уровень адресного объекта. Всего существует 8 основных уровней и несколько дополнительных (см. табл. SOCRBASE):
    1 - край/область
    4 - город/поселок
    6 - микрорайон / нас. пункт в составе муниципального образования
    7 - улица/квартал
    8 - дом [Прим. - записей этого уровня в таблице нет]

Получение полного адреса (от младшего к старшему):

WITH RECURSIVE child_to_parents AS (
  SELECT addrobj.* FROM addrobj
      WHERE aoid = '51f21baa-c804-4737-9d5f-9da7a3bb1598'
  UNION ALL
  SELECT addrobj.* FROM addrobj, child_to_parents
      WHERE addrobj.aoguid = child_to_parents.parentguid
        AND addrobj.currstatus = 0
)
SELECT * FROM child_to_parents ORDER BY aolevel;

AOLEVEL отражает административно-правовое подчинение, поэтому одни и те же текстовые элементы адреса могут располагаться на разных уровнях. Например, в снт. "Волжанка" (AOLEVEL=90) улицы имеют уровень 91 (против более распространенного 7):

(1) обл. Самарская
(3) р-н Сызранский
(90) снт Волжанка
(91) ул. Ягодная
(91) ул. Дачная
(91) ул. Рябиновая

Аналогично, существуют населенные пункты без улиц. Например, в пос. Лужки иерархия заканчивается на уровне 6:

(1) обл. Орловская
(3) р-н Мценский
(6) п. Лужки

Таким образом, построение таблицы полных адресов следует начинать от верхних элементов (AOLEVEL=1) к нижним (AOLEVEL=91), как правило, запрос оформляется в виде множества подзапросов (subquery). Или рекурсивно подниматься от нижних, не имеющих дочерних элементов. Рассмотрим для примера второй вариант. Поскольку число полей в обоих случаях не определено, то полный адрес будет формироваться единой строкой:

-- функция получения полного адреса (аналогично предыдущему примеру)
-- DROP FUNCTION IF EXISTS make_full(text);
CREATE FUNCTION make_full(IN id text, OUT fulladdress text) AS $$
  -- "id" is an unique identificator (use AOID)
  WITH RECURSIVE to_parents AS (
    SELECT format('<addr type="%s">%s</addr>', shortname, formalname) AS fulladdress,
           parentguid, aolevel
      FROM addrobj
     WHERE aoid = id
    UNION ALL
    SELECT format('<addr type="%s">%s</addr> %s',
                  addrobj.shortname, addrobj.formalname, to_parents.fulladdress
           ) AS fulladdress,
           addrobj.parentguid, addrobj.aolevel
      FROM addrobj, to_parents
     WHERE addrobj.aoguid = to_parents.parentguid
       AND addrobj.currstatus = 0
  )
  SELECT fulladdress FROM to_parents WHERE aolevel = 1
$$ LANGUAGE SQL;

-- сбор всех parentguid
WITH all_parents AS (
  SELECT DISTINCT parentguid
    FROM addrobj
   WHERE (currstatus = 0) AND (parentguid IS NOT NULL)
)
-- выбор элементов самого нижнего уровня (не входящих в список all_parents)
-- и получение полного адреса
SELECT make_full(aoid)
  FROM addrobj
 WHERE (currstatus = 0) AND (aoguid NOT IN (SELECT parentguid FROM all_parents))
 LIMIT 100

Образцы результатов:

<addr type="обл">Волгоградская</addr> <addr type="г">Волгоград</addr> <addr type="ул">Лавровая</addr>
<addr type="обл">Орловская</addr> <addr type="р-н">Мценский</addr> <addr type="п">Лужки</addr>
<addr type="обл">Челябинская</addr> <addr type="г">Челябинск</addr> <addr type="ул">Балтийская</addr>
<addr type="край">Приморский</addr> <addr type="г">Артем</addr> <addr type="ул">Григорьева</addr>
<addr type="обл">Калужская</addr> <addr type="р-н">Ульяновский</addr> <addr type="д">Бродок</addr>
<addr type="обл">Свердловская</addr> <addr type="г">Екатеринбург</addr> <addr type="ул">Окраинная</addr>
<addr type="Респ">Тыва</addr> <addr type="р-н">Чаа-Хольский</addr> <addr type="м">Сесеге</addr>

Статус актуальности

В базе ФИАС никогда не удаляются элементы. Они могут быть только переведены в разряд "отключенных" (устаревшие, измененные и т.п.). Поведение аналогично работе КЛАДР. Рассмотрим поля, определяющие действительность объекта:

Для выбора актуальных записей рекомендуется ориентироваться на поля CURRSTATUS и LIVESTATUS.

Рассмотрим подробнее:

  • LIVESTATUS - Признак действующего адресного объекта. Принимает значения: 0 - действующий, 1 - не действующий (см. прим. 1).
  • CURRSTATUS - Статус актуальности КЛАДР 4. Принимает значения: 0 - актуальный, 1-50 - исторический, 51 - переподчиненный (см. табл. CURENTST и гл. 1.2. "Коды адресных объектов" документации КЛАДР).
  • OPERSTATUS - Статус действия. Принимает значения: 1 - Инициация, 10 - Добавление, 20 - Изменение и др (см. табл. OPERSTAT).
  • ACTSTATUS - Статус актуальности адресного объекта ФИАС. Принимает значения: 1 - актуальный, 0 – не актуальный (см. табл. ACTSTAT). Отвечает непосредственно за актуальность "имени". Если объект был переименован (чаще это исправление опечаток), то старая запись получает CURRSTATUS=1 и ACTSTATUS=0. Если же административная единица была ликвидирована или переподчинена, то имя останется по-прежнему актуальным: CURRSTATUS=99/51 и ACTSTATUS=1. В тоже время, при внесении изменений, не касающихся непосредственно адресной части, признак актуальности все равно сбрасывается (ACTSTATUS=0).

Также, в категорию актуальности можно отнести поля STARTDATE, ENDDATE, UPDATEDATE. Но, на данный момент (декабрь 2012 г.) значения этих полей редко бывают заполнены правильно.

Покажем на примере выбор актуальных и исторический записей. "Пермский край" был образован 01.12.2005 объединением "Пермской области" и "Коми-Пермяцкого АО". В базе ФИАС это отразилось следующим образом (см. табл. 1):

  1. Создана новая запись "Пермский край" (1-й столбец);
  2. Запись "Пермская область" (2-й столбец) сохранена, причем AOGUID обеих записей идентичен (для связи с дочерними объектами);
  3. Запись "Коми-Пермяцкий АО" (3-й столбец) также сохранена.

Таблица 1

Примечание Актуальная запись Пермская обл. Коми-Пермяцкий АО
AOID c685f6ca-d7e1-4938-8b21-7c20035652d2 744e2599-d2bb-4364-9922-c13febb16e81 68824dc3-da0f-42d3-bd0b-1ceb91ff27bf
AOGUID 4f8b1a21-e4bb-422f-9087-d3cbf4bebc14 4f8b1a21-e4bb-422f-9087-d3cbf4bebc14 e3d95b95-cc2d-440d-95c6-65577fae076e
AOLEVEL 1 1 1
PREVID 744e2599-d2bb-4364-9922-c13febb16e81 68824dc3-da0f-42d3-bd0b-1ceb91ff27bf
NEXTID c685f6ca-d7e1-4938-8b21-7c20035652d2 744e2599-d2bb-4364-9922-c13febb16e81
ACTSTATUS 1 0 1
OPERSTATUS 1 1 1
CURRSTATUS 0 1 51
LIVESTATUS 1 0 0
UPDATEDATE 2011-09-13 2011-09-13 2011-09-13
STARTDATE 1900-01-01 1900-01-01 1900-01-01
ENDDATE 2079-06-06 2079-06-06 2079-06-06
FORMALNAME Пермский Пермская Коми-Пермяцкий
OFFNAME Пермский Пермская Коми-Пермяцкий
SHORTNAME край обл АО

Значения полей:

  1. ACTSTATUS для Коми-Пермяцкого АО сохранен в значении "Актуальный";
  2. OPERSTATUS не изменился - ошибочно(?);
  3. CURRSTATUS правильный (0 - актуальный, 1 - исторический, 51 - переподчиненный);
  4. LIVESTATUS показывает единственную актуальную запись, но значения инвертированы (см. прим. 1).

//ToDo - изменения в подчиненных объектах

Примечания:

  1. Все актуальные записи (CURRSTATUS = 0) имеют значение (LIVESTATUS = 1) и наоборот. Очевидно, что поведение LIVESTATUS не соответствует описанному в документации. Поле можно использовать для выбора актуальных записей, но с осторожностью до выяснения ситуации в будущем.

Исторические названия

Поля AOID, PREVID, NEXTID в совокупности составляют цепочку от современного к устаревшим наименованиям объекта. Хотя, если имеется два прямых предка (как в примере про "Пермский край"), то наследование невозможно отразить полностью однозначно.

В общем виде получение исторических вариантов наименований объекта имеет вид:

WITH RECURSIVE old_names AS (
    SELECT *
        FROM addrobj
        -- вариации отдельной записи:
        WHERE aoid = '002ff6b9-c2db-46e5-99da-8cf30d239b27'
        -- полный список:
        -- WHERE currstatus = 0 AND previd IS NOT NULL
    UNION
    SELECT addrobj.*
        FROM old_names, addrobj
        WHERE (addrobj.nextid = old_names.aoid)
)
SELECT * FROM old_names

Примеры:

shortname formalname actstatus operstatus currstatus livestatus updatedate startdate enddate
д Малое Пермиево 1 21 0 1 2012-09-08 2012-02-01 2079-06-06
д Малое Пермиево 0 1 2 0 2011-09-14 1900-01-01 2012-02-01
с Малое Пермиево 0 1 1 0 2011-09-14 1900-01-01 2079-06-06
...
ул Подстанция 220 1 20 0 1 2012-03-12 1911-11-11 2079-06-06
ул Подстанция (Комиссарово) 0 1 1 0 2011-09-15 1900-01-01 1911-11-11
...
пер Рябиновый 1 20 0 1 2011-09-27 1900-01-01 2079-06-06
ул Рябиновая 0 1 1 0 2011-09-15 1900-01-01 2079-06-06
...
снт Восход(п.Селезнево) 1 1 0 1 2011-09-14 1900-01-01 2079-06-06
дп СТ Восход(п.Селезнево) 0 1 1 0 2011-09-14 1900-01-01 2079-06-06
...
ул Самбуева В.М. 1 1 0 1 2011-09-14 1900-01-01 2079-06-06
ул Ф.Энгельса 0 1 1 0 2011-09-14 1900-01-01 2079-06-06
...
проезд 3-й Лабинский 1 1 0 1 2011-09-14 1900-01-01 2079-06-06
ул Лабинская 2-я 0 1 1 0 2011-09-14 1900-01-01 2079-06-06

// ToDo:

Примечания:

  1. Наблюдение (в документации не разъяснено): при внесении изменений, предыдущая историческая запись получает значение (CURRSTATUS = 1). Если вносится повторное изменение, то аннулируемая запись принимает значение (CURRSTATUS = 2). Таким образом, цепочка от современного названия к самому старому принимает вид: актуальное (0), предыдущее (3), более раннее (2), самое старое (1).