惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
量子位
腾讯CDC
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LangChain Blog
aimingoo的专栏
aimingoo的专栏
The Hacker News
The Hacker News
T
The Exploit Database - CXSecurity.com
B
Blog
S
SegmentFault 最新的问题
P
Privacy & Cybersecurity Law Blog
T
Threatpost
博客园 - 聂微东
T
Tailwind CSS Blog
The Last Watchdog
The Last Watchdog
C
Check Point Blog
N
Netflix TechBlog - Medium
D
DataBreaches.Net
爱范儿
爱范儿
IT之家
IT之家
S
Secure Thoughts
M
MIT News - Artificial intelligence
NISL@THU
NISL@THU
C
Cisco Blogs
TaoSecurity Blog
TaoSecurity Blog
有赞技术团队
有赞技术团队
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
Schneier on Security
Schneier on Security
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
雷峰网
雷峰网
Project Zero
Project Zero
博客园 - Franky
H
Heimdal Security Blog
A
About on SuperTechFans
Security Latest
Security Latest
Webroot Blog
Webroot Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hugging Face - Blog
Hugging Face - Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Функции управления цифровыми активами автомобильных дорог. Часть 2 – маппинг
Кутепов Артём · 2026-06-02 · via Все публикации подряд на Хабре

Средний

14 мин

8K

Здравствуйте, уважаемые читатели Хабра!

В первой части мы рассказали, как решили задачу сегментации полигона дороги в PostGIS. А теперь рассмотрим сопоставление сегментов двух разных версий дороги для сохранения учёта историчности привязанным к ним событий.

Содержание

Введение

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

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

Что мы хотим получить и зачем нам это нужно? На рисунке ниже представлены две сегментированные версии дороги. Синим цветом обозначена актуальная часть дороги, красным – старая. Актуальная дорога находится на картинке под предыдущей. Новая дорога получена путём продления старой. Обе версии полигона были сегментированы и их общие части полностью совпадают. Это тот идеальный результат, который в итоге мы хотим получить.

Идеальный результат маппинг в случае удлинения дороги. Синим обозначена новая часть, красным – старая.

Идеальный результат маппинг в случае удлинения дороги. Синим обозначена новая часть, красным – старая.

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

Чтобы было совсем понятно, приведём следующий пример. Изначально была 1-я версия дороги, её сегментировали и к её 5-му участку привязали инцидент «отсутствие разметки». Затем спустя некоторое время всю дорогу решили удлинить и расширить. Это означает, что у нас появляется новая 2-ая версия цельного полигона дороги. Затем если мы её просто разрежем на сегменты, то с высокой долей вероятности наш 5-й участок будет иметь совсем другой идентификатор, и мы потеряем привязанный к нему инцидент. Но если есть механизм маппинга сегментов, то 5-й участок и привязанное к нему событие останутся на своих местах. Однако стоит отметить, что если произошли существенные изменения геометрии участка, при которых его новая и старая версия несопоставимы, то нам следует участку в новой версии дороги присвоить ранее неиспользовавшийся уникальный идентификатор.

Таким образом, функция маппинга сегментов должна сопоставлять участки двух разных дорог, а при их существенном различии присваивать новому участку ранее неиспользовавшийся уникальный идентификатор. В итоге при вставке новой версии полигона в таблицу (срабатывает по триггеру) должна появляться новая запись (INSERT) в целевой таблице активов со следующими заполненными полями:

  • layer_id — идентификатор слоя,

  • object_id — идентификатор объекта дороги в слое,

  • zone_id — идентификатор сегмента,

  • geom — геометрия сегмента.

Алгоритм сопоставления сегментов дороги

Для демонстрации качества работы каждого этапа алгоритма сначала применим его для не отличающихся по геометрии полигонов двух версий дороги. То есть представим, что к нам пришло крупное обновление дорожной сети, в которой данная дорога осталась прежней.

Старая размеченная на сегменты дорога.

Старая размеченная на сегменты дорога.

1) Заносим старое разбиение дороги. Функция fill_old_road_partition заносит старое разбиение дороги в таблицу old_road_partition.

2) Заносим новую цельную дорогу. Функция fill_new_solid_road добавляет геометрию новой цельной дороги в таблицу new_solid_road.

Новый цельная полигон дороги в таблице new_solid_road.

Новый цельная полигон дороги в таблице new_solid_road.

3) Разбиваем новый полигон дороги на шарды. Функция fill_new_road_shards разбивает новый полигон дороги на шарды и заполняет таблицу new_road_shards (применяем ST_Subdivide(ST_Segmentize(geom, segment_len))) аналогично как это делалось на 14-ом этапе в функции fill_road_parts в задаче сегментации.

Разбиение нового полигона на осколки (шарды).

Разбиение нового полигона на осколки (шарды).

4) Классифицируем шарды. Функция new_road_shards_classification проводит классификацию каждого шарда на множестве сегментов старой дороги аналогично как это производилось на 17-ом этапе в функции road_part_classification при разрезке дороги с небольшим отличием в том, что здесь в качестве зон классификации выступают сегменты старого полигона. По итогу обновляется колонка zone_id в таблице new_road_shards.

Классифицированные шарды новой дороги.

Классифицированные шарды новой дороги.

5) Объединяем шарды в сегменты. Функция group_zones_for_new_road_partition группирует шарды по одинаковым значениям zone_id, сливает их геометрии и тем самым формирует сегменты. Результат заносится в таблицу new_road_partition.

Результат маппинга.

Результат маппинга.

Функция group_zones_for_new_road_partition
-- группируем зоны и заполнеям таблицу new_road_partition
CREATE OR REPLACE FUNCTION road_processing.group_zones_for_new_road_partition(
	p_part_id INTEGER,
    p_layer_id INTEGER,
    p_object_id TEXT
)
RETURNS VOID AS $$
begin
-- удаляем старый результат
	delete from road_processing.new_road_partition
	where part_id = p_part_id and layer_id = p_layer_id and object_id = p_object_id;
	
	insert into road_processing.new_road_partition(part_id, layer_id, object_id, zone_id, geom)
	SELECT
		p_part_id as part_id, p_layer_id as layer_id, p_object_id as object_id, zone_id,
		ST_Union(geom) as geom
	FROM road_processing.new_road_shards
	where part_id = p_part_id AND layer_id = p_layer_id AND object_id = p_object_id
	group by zone_id;
END;
$$ LANGUAGE plpgsql;
Старая (выделена синим) и новая (красным) разбивка дороги.

Старая (выделена синим) и новая (красным) разбивка дороги.

Старая и новая разбивка дороги.

Старая и новая разбивка дороги.

В целом сопоставление сегментов прошло успешно. Хотя и можно выявить небольшие различия в границах участках и неперпендикулярность разрезов.

При маппинге могут быть следующие варианты:

  • Геометрия новой дороги не изменились или изменились незначительно. Этот вариант мы только что разобрали.

  • Геометрия новой дороги уменьшилась в размерах.

  • Геометрия новой дороги увеличилась в размерах.

4) Обрабатываем большие сегменты в случае увеличения дороги.

Функция fill_diff_zones определяет различия между старыми и получившимися новыми зонами с одинаковыми идентификаторами (zone_id).

Учитываем во внимание относительную разницу между площадями и периметрами. Результат заносится в таблицу diff_zones.

Разница между старыми и новыми зонами.

Разница между старыми и новыми зонами.

Функция fill_diff_zones
-- функция для определения различий старых и новых зон при маппинге
CREATE OR REPLACE FUNCTION road_processing.fill_diff_zones(
	p_part_id INTEGER,
    p_layer_id INTEGER,
    p_object_id TEXT
)
RETURNS VOID AS $$
begin
	
	-- удаляем старые записи
	delete from road_processing.diff_zones
	where part_id = p_part_id and p_layer_id = layer_id  and p_object_id = object_id;
	
	insert into road_processing.diff_zones(part_id, layer_id, object_id, zone_id, diff_area, diff_len)
	select 
		p_part_id as part_id,
		p_layer_id as layer_id,
		p_object_id as object_id,
		old_road.zone_id as zone_id,
		(ST_Area(old_road.geom) - ST_Area(new_road.geom)) / NULLIF(ST_Area(old_road.geom), 0) * 100 as diff_area,
		(ST_Length(ST_Boundary(old_road.geom))-ST_Length(ST_Boundary(new_road.geom))) / NULLIF(ST_Length(ST_Boundary(new_road.geom)), 0) * 100 as diff_len
	from road_processing.old_road_partition as old_road
	join road_processing.new_road_partition as new_road on old_road.zone_id = new_road.zone_id 
	where old_road.part_id = p_part_id AND old_road.layer_id = p_layer_id AND old_road.object_id = p_object_id
	and new_road.part_id = p_part_id AND new_road.layer_id = p_layer_id AND new_road.object_id = p_object_id;
END;
$$ LANGUAGE plpgsql;

Затем функция fill_large_zones выбирает увеличенные зоны из таблицы diff_zones на основе относительных пороговых значений отличий площадей и периметров, добавляет их в таблицу large_zones. То есть на данном этапе отбираем слишком большие зоны.Так на рисунке ниже крайний сверху сегмент старой дороги (красный) выступает в качестве наиболее подходящей зоны для классификации достроенного участка (синий).

Большой участок в новом полигоне (синий) и старая разбивка (красный).

Большой участок в новом полигоне (синий) и старая разбивка (красный).

Выявленная большая зона.

Выявленная большая зона.

Функция fill_large_zones
CREATE OR REPLACE FUNCTION road_processing.fill_large_zones(
	p_part_id INTEGER,
    p_layer_id INTEGER,
    p_object_id TEXT,
    area_threshold double precision default 50,
    len_threshold double precision default 50,
    standart_segment_len double precision default 300,
    diff_len_coefficient double precision default 2
)
RETURNS VOID AS $$
begin
-- удаляем старые записи
	delete from road_processing.large_zones
	where part_id = p_part_id AND layer_id = p_layer_id AND object_id = p_object_id;
	
-- находим увеличенные зоны	
	with increased_zones as (
		select zone_id
		from road_processing.diff_zones
		where (diff_area < 0 and abs(diff_area) >= area_threshold ) 
			or (diff_len < 0 and abs(diff_len) >= len_threshold )
		 and part_id = p_part_id AND layer_id = p_layer_id AND object_id = p_object_id
	),
-- отбираем большие зоны для разбиения
	zones_for_partitions as (
	select iz.zone_id as zone_id, nrp.geom as geom
	from increased_zones as iz 
	join road_processing.new_road_partition as nrp on iz.zone_id = nrp.zone_id
	where  ST_Length(ST_Boundary(nrp.geom))>= diff_len_coefficient*standart_segment_len AND nrp.part_id = p_part_id
		 AND nrp.layer_id = p_layer_id AND nrp.object_id = p_object_id
	)
	insert into road_processing.large_zones(part_id, layer_id, object_id, zone_id, geom)	
select 
		p_part_id as part_id,
		p_layer_id as layer_id,
		p_object_id as object_id,
		zone_id as zone_id,
		geom
	from zones_for_partitions;
END;
$$ LANGUAGE plpgsql;

Функция make_large_zones_partition производит разбиение больших зон на сегменты из таблицы large_zones. Для этого воспользуемся функцией make_part_of_road_segmentation из задачи сегментации. Результат заносится в таблицу new_large_zones_partition.

После чего функция new_large_zones_classification заново производит классификацию для зон из new_large_zones_partition.

И уже функция manage_new_large_zones добавляет зоны из таблицы new_large_zones_partition в new_road_partition.

Результат маппинг в случае увеличения новой дороги. Синим обозначена старая часть, красным – новая.

Результат маппинг в случае увеличения новой дороги. Синим обозначена старая часть, красным – новая.

5) Обрабатываем малые сегменты. Функция merge_small_intersected_mapped_zones итеративно находит и сливает смежные малые зоны. Обновляет записи в финальной таблице new_road_partition.

Функция merge_small_intersected_mapped_zones
CREATE OR REPLACE FUNCTION road_processing.merge_small_intersected_mapped_zones(
	p_part_id INTEGER,
    p_layer_id INTEGER,
    p_object_id TEXT,
    max_area DOUBLE PRECISION DEFAULT 20000,		-- максимальная площадь любого полигона
    max_length DOUBLE PRECISION DEFAULT 350,		-- максимальная длинна любого полигона
    min_area DOUBLE PRECISION DEFAULT 5000,      -- полигоны меньше этой площади считаются "малыми"
    min_length DOUBLE PRECISION DEFAULT 150      -- полигоны меньше этого размера также считаются "малыми"
)
--RETURNS INTEGER AS $$
RETURNS VOID AS $$
DECLARE
	merged INTEGER;
    v_small_id INTEGER;
    v_target_id INTEGER;
    v_new_geom GEOMETRY(GEOMETRY, 3857);
    v_small_geom GEOMETRY(GEOMETRY, 3857);
    v_small_area DOUBLE PRECISION;
    v_small_length DOUBLE PRECISION;
BEGIN
    LOOP
        -- Находим один малый полигон (по площади или линейному размеру)
        SELECT 
            zone_id, 
            geom,
            ST_Area(geom),
            ST_MaxDistance(geom, geom)  -- приблизительный линейный размер
        INTO 
            v_small_id,
            v_small_geom,
            v_small_area,
            v_small_length
        FROM road_processing.new_road_partition
        WHERE (ST_Area(geom) <= min_area 
           OR ST_MaxDistance(geom, geom) <= min_length)
			and part_id = p_part_id AND layer_id = p_layer_id AND object_id = p_object_id
        LIMIT 1;
        
--         Если малых полигонов нет - выходим
        IF NOT FOUND THEN
            EXIT;
        END IF;
        
--         Ищем пересекающийся полигон с НАИМЕНЬШЕЙ бы полученной площадью и длинне
        SELECT 
            b.zone_id,
            ST_Union(v_small_geom, b.geom)
        INTO 
            v_target_id,
            v_new_geom
        FROM road_processing.new_road_partition b
        WHERE b.zone_id != v_small_id
          AND ST_Intersects(v_small_geom, b.geom) 
		  and part_id = p_part_id AND layer_id = p_layer_id AND object_id = p_object_id
-- выбираем наименьший по площади и длинне полигон
        ORDER BY 
			 (COALESCE(ST_MaxDistance(v_small_geom, v_small_geom), 0) + 
             COALESCE(ST_MaxDistance(b.geom, b.geom), 0)) * 0.5 + 
            (COALESCE(ST_Area(v_small_geom), 0) + COALESCE(ST_Area(b.geom), 0)) * 0.5 ASC
        LIMIT 1;
        
        -- Если ничего не найдено, выходим
        IF NOT FOUND THEN
            EXIT;
        END IF;
        
        -- Обновляем геометрию первого полигона
        UPDATE road_processing.new_road_partition 
        SET geom = v_new_geom 
        WHERE zone_id = v_target_id and part_id = p_part_id AND layer_id = p_layer_id AND object_id = p_object_id;
        
        GET DIAGNOSTICS merged = ROW_COUNT;
        
        -- Удаляем малый полигон
        DELETE FROM road_processing.new_road_partition WHERE zone_id = v_small_id and part_id = p_part_id AND layer_id = p_layer_id AND object_id = p_object_id;

    END LOOP;

END;
$$ LANGUAGE plpgsql;

Итоговое решение

Все вышеперечисленные этапы были включены в функцию make_part_of_road_mapping, она обрабатывает один полигон дороги. Как и в задаче сегментации для обработки мультиполигона она вызывается в цикле функции main_road_mapping для каждого находящегося в нём полигона (part_id).

Функция make_part_of_road_mapping
CREATE OR REPLACE FUNCTION road_processing.make_part_of_road_mapping(
	
p_part_id INTEGER,
    p_layer_id INTEGER,
    p_object_id TEXT,
        
    standart_segment_len DOUBLE PRECISION DEFAULT 300, -- желаемая длинна сегмента дороги
    standart_segment_wide DOUBLE PRECISION DEFAULT 50, -- предполагаемая ширина дороги
    max_segment_area DOUBLE PRECISION DEFAULT 20000,	-- максимальная площадь любого полигона
    max_segment_length DOUBLE PRECISION DEFAULT 350,	-- максимальная длинна любого полигона
    min_segment_area DOUBLE PRECISION DEFAULT 5000,     -- полигоны меньше этой площади считаются "малыми"
    min_segment_length DOUBLE PRECISION DEFAULT 150,     -- полигоны меньше этого размера также считаются "малыми"
    merge_area_tolerance_percent DOUBLE PRECISION DEFAULT 50, -- процентный порог зон для слияния по общей площади их пересечения
intersection_area_weight double precision default 0.2, -- вес площади пересечения для функции классфикации шарда road_part_classification
    boundary_length_weight double precision default 0.2, -- вес длины границы для функции классификации шарда road_part_classification
    centroid_distance_weight double precision default 0.2, -- вес расстояния между центроидами для функции классификации шарда road_part_classification
    min_distance_weight double precision default 0.2, -- вес минимального расстояния для функции классификации шарда road_part_classification
    lies_inside_weight double precision default 1.0, -- вес того лежит ли часть дороги целиком в зоне для функции классификации шарда road_part_classification
    topology_distance_tolerance DOUBLE PRECISION DEFAULT 2.0, --  параметр для функции generate_road_skeleton - дистанция в ST_SimplifyPreserveTopology
    shard_segment_len DOUBLE PRECISION DEFAULT 1.0, -- длинна "осколка" дороги. Функция fill_road_parts, параметр для ST_Segmentize
    buffer_distance DOUBLE PRECISION DEFAULT 1.0, -- величина буффера для функции ST_Buffer в process_again_and_get_better_zones
    buffer_distance_for_group_zones DOUBLE PRECISION DEFAULT 0.01, -- величина буфера в функции группировки классифицированных шардов
    unsuitable_road_zones_area_threshold double precision default 100, -- избавляемся от зон с меньшей площадью
    unsuitable_road_zones_len_threshold double precision default 100,  -- избавляемся от зон с меньшей длинной
    area_threshold double precision default 50, -- процентный порог разницы площадей зон для функции fill_large_zones
    len_threshold DOUBLE PRECISION DEFAULT 50, -- процентный порог разницы длин зон для функции fill_large_zones
    diff_len_coefficient DOUBLE PRECISION DEFAULT 2.0 -- коэффициент разницы длины зоны функции fill_large_zones
)
RETURNS VOID AS $$
begin
-- разбиваем дорогу на шарды
perform road_processing.fill_new_road_shards(p_part_id => p_part_id, p_object_id => p_object_id,
	 p_layer_id => p_layer_id, segment_len => shard_segment_len);

-- проводим классификацию каждого шарда
perform road_processing.new_road_shards_classification(
	p_part_id => p_part_id, p_object_id => p_object_id,  p_layer_id => p_layer_id,
	intersection_area_weight => intersection_area_weight, boundary_length_weight => boundary_length_weight,
	centroid_distance_weight => centroid_distance_weight, min_distance_weight => min_distance_weight,
	lies_inside_weight => lies_inside_weight);

-- группируем зоны
perform road_processing.group_zones_for_new_road_partition(p_part_id => p_part_id, p_object_id => p_object_id,  p_layer_id => p_layer_id
	);

-- определяем различия зон
perform road_processing.fill_diff_zones(p_part_id => p_part_id, p_layer_id => p_layer_id,
		 p_object_id => p_object_id
	);

-- определяем наибольшие зоны
perform road_processing.fill_large_zones(p_part_id => p_part_id, p_layer_id => p_layer_id,
		p_object_id => p_object_id,
		standart_segment_len => standart_segment_len,
		area_threshold => area_threshold, len_threshold => len_threshold,
		diff_len_coefficient=> diff_len_coefficient
	);

-- разбиваем большие зоны
perform road_processing.make_large_zones_partition(
			p_part_id => p_part_id, p_layer_id => p_layer_id, p_object_id => p_object_id,
			process_for_better_zones => process_for_better_zones, 
			process_for_split_overlapped_zones => process_for_split_overlapped_zones,
			process_for_fill_road_parts_considering_zones => process_for_fill_road_parts_considering_zones,
            standart_segment_len => standart_segment_len, standart_segment_wide => standart_segment_wide,
            max_segment_area => max_segment_area, min_segment_area => min_segment_area,
            max_segment_length => max_segment_length, min_segment_length => min_segment_length,
			merge_area_tolerance_percent => merge_area_tolerance_percent,
			intersection_area_weight => intersection_area_weight,
			boundary_length_weight => boundary_length_weight,
			centroid_distance_weight => centroid_distance_weight,
			min_distance_weight => min_distance_weight,
			lies_inside_weight => lies_inside_weight,
			shard_segment_len => shard_segment_len,
			buffer_distance => buffer_distance,			
			unsuitable_road_zones_area_threshold => unsuitable_road_zones_area_threshold,
			unsuitable_road_zones_len_threshold => unsuitable_road_zones_len_threshold
		
	);
-- производим маппинг для разбитых сегментов из больших зон (сопоставляем zone_id)
perform road_processing.new_large_zones_classification(p_layer_id => p_layer_id, p_object_id => p_object_id,
			intersection_area_weight => intersection_area_weight,
			boundary_length_weight => boundary_length_weight,
			centroid_distance_weight => centroid_distance_weight,
			min_distance_weight => min_distance_weight,
			lies_inside_weight => lies_inside_weight
	);

-- добавляем классифицированные сегменты из больших зон в итоговое разбиение
perform road_processing.manage_new_large_zones(p_layer_id => p_layer_id,
			p_object_id => p_object_id);

-- объединяем малые зоны
perform road_processing.merge_small_intersected_mapped_zones(p_part_id => p_part_id,
			p_layer_id => p_layer_id,p_object_id => p_object_id,
			max_area => max_segment_area, max_length => max_segment_length,
			min_area => min_segment_area, min_length => min_segment_length
	);
END;
$$ LANGUAGE plpgsql;
Параметры функции main_road_mapping

Имя параметра

Смысловое значение

Тип

Значение по умолчанию

1

p_layer_id

Идентификатор слоя

INTEGER

-

2

p_object_id

Идентификатор объекта

TEXT

-

3

standart_segment_len

Желаемая длинна сегмента дороги

DOUBLE PRECISION

300

4

standart_segment_wide

Предполагаемая ширина зоны дороги

DOUBLE PRECISION

50

5

max_segment_area

Максимальная площадь сегмента

DOUBLE PRECISION

20 000

6

max_segment_length

Максимальная длинна сегмента

DOUBLE PRECISION

350

7

min_segment_area

Сегменты меньше этой площади считаются «малыми»

DOUBLE PRECISION

5 000

8

min_segment_length

Сегменты меньше этого линейного размера считаются «малыми»

DOUBLE PRECISION

150

9

merge_area_tolerance_percent

Процентный порог для слияния зон по общей площади их пересечения

DOUBLE PRECISION

50

10

intersection_area_weight

Вес площади пересечения для функции классификации шарда road_part_classification

DOUBLE PRECISION

0.2

11

boundary_length_weight

Вес длины границы для функции классификации шарда road_part_classification

DOUBLE PRECISION

0.2

12

centroid_distance_weight

Вес расстояния между центроидами для функции классификации шарда road_part_classification

DOUBLE PRECISION

0.2

13

min_distance_weight

Вес минимального расстояния для функции классификации шарда road_part_classification

DOUBLE PRECISION

0.2

14

lies_inside_weight

Вес того лежит ли часть дороги целиком в зоне для функции классификации шарда road_part_classification

DOUBLE PRECISION

1.0

15

topology_distance_tolerance

Параметр для функции generate_road_skeleton – дистанция в ST_SimplifyPreserveTopology

DOUBLE PRECISION

2.0

16

shard_segment_len

Длинна «осколка» дороги. Функция fill_road_parts, параметр для ST_Segmentize

DOUBLE PRECISION

1.0

17

area_threshold

Процентный порог разницы площадей зон для функции fill_large_zones

DOUBLE PRECISION

50

18

len_threshold

Процентный порог разницы длин зон для функции fill_large_zones

DOUBLE PRECISION

50

19

diff_len_coefficient

Коэффициент разницы длины зон для функции fill_large_zones

DOUBLE PRECISION

2.0

Основная функция - main_road_mapping.

select road_processing.main_road_mapping(p_layer_id => 179, p_object_id => '10000257');

Примеры маппинга

№1

Результат маппинг в случае уменьшения новой дороги. Синим обозначена старая часть, красным – новая. 100% сопоставление.

Результат маппинг в случае уменьшения новой дороги. Синим обозначена старая часть, красным – новая. 100% сопоставление.

№2

Маппинг мультиполигона – каждый полигон имеет свой идентификатор part_id.

Старое разбиение.

Старое разбиение.

Новое разбиение.

Новое разбиение.

№3

№4

№5

100% сопоставление - одна зона.

100% сопоставление - одна зона.

№6

№7

№8

Одна зона, 100% сопоставление.

Одна зона, 100% сопоставление.

№9

№10

select road_processing.main_road_mapping(p_layer_id => 179, p_object_id => '10000502', shard_segment_len => 1);

№11

№12

select road_processing.main_road_mapping(p_layer_id => 179, p_object_id => '10001480', shard_segment_len => 1);

№13

Заключение

Разработанный алгоритм маппинга сохраняет историчность событий при изменениях связанных с ними территорий.

Время его выполнения зависит от длины полигона и в среднем составляет от 5 до 30 секунд: чем крупнее полигон, тем дольше обработка.

Основные недостатки алгоритма — небольшие расхождения в границах участков и отсутствие строгой перпендикулярности разрезов.

Алгоритмы сегментации и маппинга интегрированы в систему управления цифровыми активами дорог. В некоторых случаях всё ещё требуется участие специалиста, однако, по нашим оценкам, около 90% дорог могут обрабатываться полностью автоматически. Это обеспечивает актуальность данных в системе и значительно сокращает время сотрудников на рутинные операции.

Как бы вы решили эту задачу? Делитесь идеями в комментариях. Задавайте вопросы.

Спасибо за внимание!

Ссылки, источники, материалы

  1. Stephen Vincent Mather, Pedro Wightma, Bborie Park, Thomas Kraft. PostGIS Cookbook: Store, organize, manipulate, and analyze spatial data, Second Edition, 2018, стр. 576

  2. Документация Postgres Pro

  3. Документация по функциям PostGIS