Банк данных ПРОДУКЦИЯ РОССИИ

Ведение адресов с помощью классификатора КЛАДР

Озниев Н.К. ктн

Дата публикации: 06.01.2018 г.

Общие сведения

Классификатор КЛАДР введен в состав Банка данных ПРОДУКЦИЯ РОССИИ (БДПР) с целью структурированного ведения адресной информации предприятий изготовителей продукции и держателей документов стандартизации.

Полное описание структуры КЛАДР занимает довольно много места, с ним можно ознакомиться на сайте ГНИВЦ, где ведется поддержка и обновление данного классификатора https://www.gnivc.ru/inf_provision/classifiers_reference/kladr/

Более компактное, но также полное, описание приведено здесь.

Нам потребуется точное описание основного элемента, который используется при реализации процедур обработки адресной информации, которым является так называемый Код адресного объекта. Сами адресные объекты, примерами которых являются наименование региона, района, города, улицы, дома и квартиры, разбиты на 7 уровней. В БДПР используются только первые 6 уровней. Эти уровни являются иерархическими, т.е. каждый следующий уровень подчинен предыдущему.

Код адресного объекта однозначно определяет его в пределах КЛАДР, поэтому он является центральным элементов в процедурах обработки адресных данных.

Структура кода адресного объекта для 1-5 уровней, т.е. до улиц городов, поселков городского типа, сельских населенных пунктов, а также садоводческих некоммерческих тавариществ, расположенных в границах населенных пунктов или привязанных к улично-дорожной сети, состоит из двух частей:

- собственно код, полностью описывающий иерархию подчиненности конкретного адресного объекта, в дальнейшем его будем называть просто код,

- признак актуальности, принимающий одно из значений 00 (актуальный объект), 01 – 50 (объект был переименован), 51 (объект был перепочинен или влился в состав другого объекта), 52 – 98 (резервные значения), 99 – адресный объект не существует, состоящий из двух разрядов.

Код адресного объекта имеет вид:

СС-РРР-ГГГ-ППП-АА

Тире сюда вставлен автором просто для удобства чтения, и в самом коде КЛАДР его нет.

Элементы кода:

СС – код субъекта Российской Федерации (региона),

РРР – код района,

ГГГ – код города,

ППП – код населенного пункта,

АА - признак актуальности адресного объекта, описанный выше.

Таким образом, длина собственно кода адресного объекта 11 разрядов.

Адресные объекты, содержащие коды приведенного типа (уровни 1-5), в КЛАДР расположены в таблице kladr.

Структура этой таблицы следующая:

№№

Имя поля

Пример

Комментарии

1

name

Троицк

Наименование

2

socr

г

Сокращенное наименование типа объекта

3

code

7700000500000

Код

4

index

 

Почтовый индекс

5

gninmb

7751

Код ИФНС

6

uno

 

Код территориального участка ИФНС

7

ocatd

45297581000

Код ОКАТО

8

status

0

Статус объекта (признак центра)

Обращает на себя внимание, что данный адресный объект, взятый непосредственно из таблицы kladr, актуальной на дату написания данной статьи, не содержит почтового индекса.

В БДПР не используется значения полей gninmb, uno, ocatd, status, несмотря на то, что поле status, вообще говоря, заслуживает внимания. В этом поле может быть одно из значений

0 - объект не является центром административно-территориального образования;

1 – объект является центром района;

2 – объект является центром (столицей) региона;

3 – объект является одновременно и центром района и центром региона;

4 – центральный район, т.е. район, в котором находится центр региона (только для объектов 2-го уровня).

Значение поля status предназначено для правильного формирования почтового адреса с использованием базы данных КЛАДР: если значением этого поля является 1, то в адресе указываются регион и населенный пункт (район не указывается); если – 2 или 3, то в адресе указывается только центр региона (регион и район не указываются).

Пока указанные правила в процедурах обработки адресной информации БДПР не действуют.

Структура адресного объекта 6 уровня, включающего улицы, имеет вид:

СС-РРР-ГГГ-ППП-УУУУ-АА

Здесь смысл тот же, что и в ранее описанном коде для уровней 1-5, но появился новый элемент

УУУУ – код улицы.

Длина кода адресного объекта в данном случае 15 разрядов.

Адресные объекты, содержащие коды уровня 6, в КЛАДР расположены в таблицах street и doma.

Структура таблицы street следующая:

№№

Имя поля

Пример

Комментарии

1

name

Панфилова

Наименование

2

socr

ул

Сокращенное наименование типа объекта

3

code

50000030000004900

Код

4

index

141407

Почтовый индекс

5

gninmb

5047

Код ИФНС

6

uno

 

Код территориального участка ИФНС

7

ocatd

46483000000

Статус объекта (признак центра)

Как видим, поля этой таблицы те же, что и в таблие kladr, но отсутствует поля status.

Структура таблицы doma имеет вид:

№№

Имя поля

Пример

Комментарии

1

name

19алитерА,4литерА,6алитерА

Наименование

2

korp

 

Номер корпуса

3

socr

ДОМ

Сокращенное наименование типа объекта

4

code

50000030000001700

Код

5

index

141407

Почтовый индекс

6

gninmb

5047

Код ИФНС

7

uno

 

Код территориального участка ИФНС

8

ocatd

46483000000

Статус объекта (признак центра)

Видно, что структура та же, что и в предыдущих таблицах, но появилось поле korp.

Таблица doma в БДПР используется в исключительных случаях, когда не удается найти почтовый индекс в таблицах kladr и street. Поэтому не будем здесь разбираться в структуре записи в поле name, которая довольно запутанная. В БДПР номера домов используются, но они не сверяются с таблицей doma, и их проставляет пользователь по своему усмотрению.

Условия применения КЛАДР в БДПР

Предполагается, что любое предприятие имеет почтовый индекс, и именно он является отправной точкой при формировании адреса. При вводе и редактировании адреса любого предприятия он пользователю заранее известен, т.к. ЦСМы имеют дело с предприятиями-парнерами.

Главная задача системы обработки адресной информации заключается в том, чтобы отыскать в КЛАДР (в таблицах kladr, street, doma) запись, содержащую требуемый почтовый индекс, а затем, на его основе, определить все составляющие адреса, получив в свое распоряжение код адресного объекта для данного почтового индекса. Так как таких адресных объектов, как правило, имеется несколько (иногда много, а иногда он только один), то пользователю предлагается список для выбора. Если таких объектов только один – он автоматически вносится в поле формы ввода адреса и система сама опускается на уровень ниже.

При этом соблюдается иерархия, заложенная в коде адресного объекта КЛАДР: сначала пользователь получает в свое распоряжение список объектов уровней 1 – 5, а затем, когда пользователь выберет населенный пункт, он получает в свое распоряжение список улиц, т.е. список объектов уровня 6.

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

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

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

Перейдем к описанию алгоритма формирования адреса на основе приведенных условий.

Алгоритм формирования адреса в БДПР

Блок-схема алгоритма поиска адреса по заданному почтовому индексу имеет вид:

Соответствующий программный код на языке PHP имеет вид:

 // Получить адрес по почтовому индексу $ind

public function getAddreesFromPostindex($ind) {

// Предварительная обработка - получение $index

$query = "SELECT `index` FROM `street` ".

"WHERE `index` = " . $ ind . " LIMIT 1";

$res = $this->db->select($query);

if (!$res) {

        // Если ничего не найдено - делаем запрос в doma

        $query = "SELECT `index` FROM `doma` WHERE `index` = " .

$ ind . " LIMIT 1";

        $res = $this->db->select($query);

        if (!$res) {

               return false;

        }

        $index = $res[0]["index"];

       

        // Тогда и код региона ищем тут же

        $query = "SELECT SUBSTRING(`code`, 1, 2) AS `region_code` ".

"FROM `doma` WHERE `index` = '" . $index . "' LIMIT 1";

        $res = $this->db->select($query);

        if (!$res)

               return false;

        $region_code = $res[0]["region_code"];

} else {

       $index = $res[0]["index"];

 

        // Код региона ищем в street

        $query = "SELECT SUBSTRING(`code`, 1, 2) AS `region_code` ".

"FROM `street` WHERE `index` = '" . $index . "' LIMIT 1";

        $res = $this->db->select($query);

        if (!$res)

               return false;

        $region_code = $res[0]["region_code"];

}

// Накапливаем коды для запроса адресных объектов из kladr

// Адресные коды надо накопить, просмотрев обе таблицы: street и doma,

// из которых они запрашиваются

// Сначала ищем в street

$code_array = $this->getSubCodeArrayForTable("street", $index, $region_code);

if ($code_array) {

        // Теперь дополнительно ищем в таблице doma

        $code_array_d = $this->getSubCodeArrayForTable("doma", $index, $region_code);

        if ($code_array_d) {

               foreach ($code_array_d as $item)

                     if (!in_array($item, $code_array))

                            $code_array[] = $item;

        }

} else {

        $code_array = $this->getSubCodeArrayForTable("doma", $index, $region_code);

        // Это все, что можно получить в этом случае

}

 

// Свернем все это в строку списка кодов

if (count($code_array) > 0) {

        $code_list = "(";

        foreach ($code_array as $r) {

               $code_list .= $r["s_code"] . ",";

        }

        // Последнюю запятую убрать и закрыть скобку

        $code_list = substr($code_list, 0, -1) . ")";

       

        // ..и запросим список адресных объектов из kladr

        $query = "SELECT * FROM `kladr` WHERE SUBSTRING(`code`, 1, 11) IN ".

$code_list . " AND `code` LIKE '%00'";

        $res = $this->db->select($query);

        if (!$res)

               return false;

} else {

        $out = Array(

               "n" => "1",

               "np" => "",

               "city" => "",

               "raion" => "",

               "region" => "",

               "region_code" => $region_code

        );

        return $out;

}

// Если код почтового индекса возвращает одно значение

if (count($res) == 1) {

        // НП

        $region_raion_city_np_code = mb_substr($res[0]["code"], 0, 11);

        if (mb_substr($region_raion_city_np_code, 9, 3) == '000')

               $np = ""; //"НП не задан";

        else {

               $np_res = $this->get_np_by_code($region_raion_city_np_code);

               $np = $np_res[0]["socr"] . " " . $np_res[0]["name"];

        }

       

        // Регион

        $region_res = $this->get_region_by_code($region_code, '00');

        $region = $region_res[0]["name"] . " " . $region_res[0]["socr"];

       

        // Город

        $region_raion_city_code = mb_substr($region_raion_city_np_code, 0, 8);

        if (mb_substr($region_raion_city_code, 5, 3) == '000')

               $city = ""; //"Город не задан";

        else {

               $city_res = $this->get_city_by_code($region_raion_city_code);

               $city = $city_res[0]["socr"] . " " . $city_res[0]["name"];

        }

       

        // Район

        $region_raion_code = mb_substr($region_raion_city_np_code, 0, 5);

        if (mb_substr($region_raion_code, 2, 3) == '000')

               $raion = ""; //"Район не задан";

        else {

               $raion_res = $this->get_raion_by_code($region_raion_code);

               $raion = $raion_res[0]["name"] . " " . $raion_res[0]["socr"];

        }

       

        $addressObj = $index . ", " . $res[0]["code"] . ", " . $res[0]["socr"] .

" " . $res[0]["name"];

        // На выход отправляем массив

        $out = Array(

               "n" => "1",

               "np" => $np,

               "city" => $city,

               "raion" => $raion,

               "region" => $region,

               "region_code" => $region_code,

               "region_raion_code" => $region_raion_code,

               "region_raion_city_code" => $region_raion_city_code,

               "region_raion_city_np_code" => $region_raion_city_np_code

        );

}

// Если код почтового индекса вовзращает несколько значений

// В этом случае возвращаются просто коды + список объектов из kladr

$kladr_codes = Array();

for ($i=0; $i<count($res); $i++) {

        $kladr_codes[] = Array("code" => $res[$i]["code"],

"name" => $res[$i]["name"], "socr" => $res[$i]["socr"]);

}

if (count($res) > 1) {

        $out = Array(

               "n" => count($res),

               "region_code" => $region_code,

               "codes" => $kladr_codes

        );

}

// Отправляем результат на выход

return $out;

}

 

// Получить из заданной таблицы массив кодов для поиска адресных объектов в kladr

private function getSubCodeArrayForTable($table, $index, $region_code) {

if ((!$table) || (!$index) || (!$region_code))

        return false;

$sub_query = "SELECT DISTINCT SUBSTRING(`code`, 1, 11) AS `s_code` ".

"FROM `" . $table . "` ".

"WHERE `index` = '" . $index ."' AND `code` LIKE '" .

$region_code ."_____________00' ";

$sub_res = $this->db->select($sub_query);

if ($sub_res) {

        return $sub_res;

}

return false;

}

В данной редакции программного кода не приведена защита от SQL-иньекции, которая обязательно должна быть включена для реального применения данной функции. Кроме того, в приведенном коде запрос в базу данных реализуется через строку вида

$this->db->select($query);

Если читатель захочет протестировать данный код, то надо позаботиться о том, чтобы вместо этой конструкции, взятой из реально работающей системы, была сформирована функция, которая позволяет выполнить SQL-запрос $query, и получить результат ее выполнения. Это значит, что нужно добавить код подключения к серверу MySQL, получения объекта запроса и выполнения запроса. Все эти подробности для нашей статьи являются несущественными, поэтому опущены. В остальном данный код является полностью работоспособным и позволяет получить код адресного объекта или их список для заданного почтового индекса.

Если код адресного объекта один, то результат однозначно задан в переменной $out, а именно:

$out["np"] – наименование населенного пункта,

$out["city"] – наименование города,

$out["raion"] – наименование района,

$out["region"] – наименование региона.

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

В БДПР этот интерфейс выглядит следующим образом:

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

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

Пользователь выбрал в последнем списке ул. Маяковского. Это 6-й уровень в КЛАДР. Процедуры обработки для этого уровня здесь не рассмотрены, но подход остается тем же, только задача эта совсем простая – нужно по коду адресного объекта, отобранному пользователем, выбрать все записи из таблицы street с этим кодом.

Дальнейший разбор в БДПР не производится, т.е. таблица doma не используется (выше она использовалась для поиска кодов адресного объекта для почтового индекса). Остальные объекты (номер дома, корпус, строение и дополнительная информация) пользователю предоставляется возможность вписать в форму адреса в произвольной форме.