445 велокилометров по городу. Строим карту качества тротуаров Минска

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

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Shakely


Если вы используете велосипед для передвижения по городу, то, скорее всего, у вас есть какие-то вопросы к велоинфраструктуре и ее качеству.
Чтобы понять, что велодорожки вашего города не такие и идеальные достаточно простого кофе-теста.


Берем в одну руку стакан с кофе, во вторую руль и едем. Если после пары минут неспешной езды кофе не стекает по рукам-ногам-телу, то, скорее всего, у вас руки-амортизаторы (ну или вы использовали крышечку).

Во время такого непредвиденного теста пришла в голову мысль, что отвлекаться во время вождения опасно, а также карта наподобие Яндекс.Пробки, иллюстрирующая качество дорожного покрытия вместо заторов.


После долгих раздумий в голове сформировалось примерное видение того, что хотелось бы реализовать. Главным техническим интересом, позволившим ухватиться за эту идею, стала возможность наконец-то сделать какую-нибудь железку. К тому же в наличии имелась Arduino Uno, приобретенная 5 лет назад и оставшаяся без дела после дежурного мигания светодиодом.


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


Все компоненты, представленные выше, можно условно разделить на три группы, соответственно роли в общем решении:


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

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


Сбор данных. Arduino


Требования к данным


Для начала определим задачи — какие данные хотим получить.


Качество дороги может определяться разными параметрами, среди которых и уклон и прямолинейность и форма самого покрытия (по песку ехать так себе удовольствие). Но в нашем случае (вспоминаем кофе-тест) было важно насколько покрытие ровное, чем оно ровнее, тем ехать приятнее, тряски нет, скорость не теряется, пятая точка рада.


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


трясти — внешним усилием приводить что-то в колебательное движение с относительно высокой частотой

Если что-то приводить в движение, то в физическом смысле это означает изменение скорости. Величина, характеризующая изменение скорости — ускорение. Именно его и надо измерять. Так как трясет велосипедиста, обычно, в вертикальной плоскости, то и ускорение нужно получить вертикальное. Значения ускорения вполне можно заиспользовать для количественного определения качества покрытия.


Здесь нам понадобится акселерометр. Но, появляется еще вопрос — тряска на одном и том же участке, с использованием одного и того же велосипеда с наездником, может быть различной в зависимости от скорости движения. Если проезжать лежачий полицейский на разных скоростях, то ощущения разные (как и сила удара головой в потолок). Поскольку мы хотим получить некую универсальную карту, то, скорее всего, нам понадобятся данные о скорости, так как в нашем идеальном вымышленном мире оценка качества для одного и того же участка, пройденного с разными скоростями должна быть одинакова.


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


Для определения качества полотна:


  • ускорение (по вертикали)
  • скорость (по горизонтали)

Для привязки оценки качества к карте:


  • время
  • местоположение
  • точность определения местоположения


Схема устройства для сбора данных о качестве дороги.


Компоненты


Как уже было сказано, у нас есть Arduino Uno, с ней добавим пару модулей для сбора и передачи данных.


Весь список используемых компонентов:


  • микроконтроллер Arduino UNO
  • акселерометр ADXL 345
  • геркон NONAME
  • bluetooth модуль HC-06
  • кучка проводов и резисторов

Сборка


Я так себе эксперт в схемотехнике. Если быть точнее — делал все описанное в этом разделе впервые. Схемы и макеты плат были выполнены в easyEDA и перенесены на текстолит лазерно-утюжным методом. Интересное занятие, особенно для человека, потерявшего связь с "железной" реальностью.


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


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


Схема платы для размещения акселерометра.
Схема платы для размещения акселерометра.


Акселерометр. Ожидание и реальность.
Акселерометр. Ожидание и реальность.


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


Модуль передачи данных. Ожидание и реальность.
Модуль передачи данных. Ожидание и реальность.


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


Упаковка в корпус


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


Коробки в роли корпусов.
Коробки в роли корпусов. То, что нужно.


Один корпус для сенсора выносной установки акселерометра и второй побольше для размещения Arduino Uno с кастомной платой для Bluetooth модуля. Корпуса имеют фланцы, что позволит закрепить выносной сенсор по месту установки. Также здесь имеются плотно защелкивающиеся крышки и места под выводы проводов наружу.


Единственный минус выбранных коробок — отсутствие внутренних креплений. Для фиксации плат пришлось заколхозить пластину с установленными в нее стойками. Пластина вклеилась в дно коробки и добавила нашему корпусу недостающие крепления.


Микроконтроллер и плата передачи данных установлена в корпус. Провода — наше все.
Микроконтроллер и плата передачи данных установлена в корпус. Провода — наше все.


Плата акселерометра установлена в корпус.
Плата акселерометра установлена в корпус.


Установка на велосипед


Мы имеем два сенсора и сам контроллер в корпусе, что должно быть размещено на подопытном велосипеде.


Геркон. Изначально был приобретен отдельный компонент, без корпуса. Но потом, в размышлениях об установке его на велосипед, пришла мысль, что можно использовать геркон с обычного китайского велокомпьютера. Главное преимущество которого — он упакован в корпус и имеет специальную форму и крепления под перо велосипеда. Цепляем геркон на перо, фиксируем стяжками.


Геркон, проставка и стяжки.
Геркон, проставка и стяжки.


Установленный геркон и магнит на спице.
Установленный геркон и магнит на спице. Выглядит надежно.


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


При поиске нового места для крепления, в районе дропаута были обнаружены два отверстия под болты, идеально подходящие под задачу. Из стального уголка был изготовлен кронштейн, который болтами M5 фиксируется к раме и позволяет разместить сенсор на полученной площадке.


Корпус акселерометра, болты, гайки, кронштейн.
Корпус акселерометра, болты, гайки, планка кронштейна.


Корпус акселерометра установлен на кронштейн и ожидает подключения.
Корпус акселерометра установлен на кронштейн и ожидает подключения.


Блок микроконтроллера. Данный компонент уже не требует особой установки. Кладем в сумку на багажнике, подключив к пауэрбанку и протянув нужные провода к блокам сенсоров.


Блок микроконтроллера в корпусе.
Блок микроконтроллера в корпусе.


Прошивка


Наш собранный девайс должен выполнять три функции:


  • подсчет скорости с помощью геркона
  • сбор данных с акселерометра
  • передача данных по Bluetooth на мобильное устройство

В соответствии с поставленными требованиями пишем прошивку на C++ и загружаем посредством Arduino IDE.


Определение скорости


Напомню, мы установили на перо велосипеда сенсор от китайского велокомпьютера.


На спице заднего колеса установлен крошечный магнит, который при вращении колеса замыкает ключ в момент прохождения мимо пера. Используя этот факт, мы можем считать время между замыканием/размыканием ключа.


Курс физики за 5й класс советует добыть еще пройденный путь, для определения скорости. На любой покрышке, обычно, расположена маркировка с ее диаметром. Диаметр превращаем в длину окружности, что и будет являться пройденным путем за один оборот колеса.


Определение скорости по обороту колеса.
Определение скорости по обороту колеса.


При имплементации решения нужно учесть несколько подводных камней:


Дребезг. При прохождении магнита мимо геркона может возникнуть дребезг — несколько переключений ключа за один оборот. Это исправляется добавлением некого минимального лимита между оборотами. Лимит можно прикинуть определив максимально возможную для определения скорость minDt = L / Vmax


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


Определение ускорения


Акселерометр позволяет снимать показания ускорения по трем осям (будем использовать только одну).


Для работы с использованным модулем существует готовая библиотека. Используемый экземпляр имеет возможность программной установки конфигурации — здесь устанавливаем максимальный диапазон ±16g и частоту обновления данных в соответствии с технической возможностью читать и отправлять их со стороны Arduino.


Первоначальная идея, читать моментальные значения ускорения и передавать их по Bluetooth, провалилась из-за ограничения пропускной способности канала. А данные, к слову, нужно отправлять часто, поскольку пики ускорения при езде происходят каждые ~10-40ms.


Поэтому, после изучения формы графика ускорения, было принято решение предобрабатывать данные на Arduino и собирать только те, которые как-то могут повлиять на результирующую оценку качества покрытия.



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


Отправка данных


Данные собраны, данные надо отправить. На первый взгляд ничего сложного — используя встроенную библиотеку SoftwareSerial для работы с последовательными портами, отправляем данные и наблюдаем их в тестовом Bluetooth-терминале на мобильном устройстве. Однако на практике пришлось походить по граблям.


Первый момент возник со скоростью. Я, как непуганый веб-девелопер, в первом прототипе передавал по bluetooth данные, обернутые в JSON, содержащие длинные float-числа, кучу ненужных данных про запас. Тогда я и подумать не мог про то, что лимит скорости моего Bluetooth модуля настолько осязаем.


После профайлинга с осознанием, что отправка моих пакетов данных занимает слишком много времени, были предприняты некоторые шаги.


Скорость. Сперва была увеличена скорость самого HC-06 модуля, который по умолчанию работал на 9600 baud. Для изменения таких настроек как скорость устройства, имя, пароль используются AT-команды. Во время попыток конфигурации оказалось, что одни и те же платы могут иметь разные прошивки, поддерживающие разные форматы. Успешно поменять скорость моего Bluetooth модуля, который отказывался подчиняться любым мануалам, было настоящим праздником.
Сжатие данных. Следующим шагом была оптимизация пакета с данными. Был выброшен JSON, выброшены лишние данные, сокращены ключи, округлены числа с плавающей точкой.


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


Скриншот тестового Bluetooth-терминала c поступающими данными. Формат пакета данных.
Скриншот тестового Bluetooth-терминала c поступающими данными. Формат пакета данных.


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


Время


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


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



Алгоритм Кристиана для синхронизации времени.


В идеальном мире с единорожками данную операцию синхронизации понадобится сделать только в начале сеанса обмена данными между устройствами. Однако, на практике оказалось, что время, выдаваемое функцией millis, не совсем то, чем кажется. На самом деле значения этих часов не точны на длинном промежутке и в моем случае вечно убегали вперед на ~3%. За минуту отставание в секунды довольно критично для задачи привязки данных к местоположению. Для устранения дрейфа часов процесс синхронизации выполняется периодически, раз в несколько секунд.


Итоги


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


Сбор данных. Мобильное приложение.


Требования к приложению.


Мобильное приложение имеет четыре кейса использования.


  • сбор данных о местоположении устройства
  • принятие данных сенсоров по Bluetooth
  • отображение данных в live-режиме
  • сохранение данных на носитель для последующего анализа

Схема архитектуры мобильного приложения.
Схема архитектуры мобильного приложения.


Реализация


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


Скриншот мобильного приложения.
Скриншот мобильного приложения.


Для формата данных был выбран CSV. Файлы собираемых профилей с данными записываются в директорию приложения, откуда мы сможем их забрать руками. Поскольку эта процедура почти одноразовая, то здесь было решено не усложнять.


В общем мобильное приложение — одна с самых неинтересных частей этого проекта (что видно по объему написанного).


Обработка данных


В результате этапа сбора данных у нас имеется набор csv файлов с данными. Задача этапа обработки — превратить собранную информацию в некие числовые характеристики качества покрытия, сопоставленные с сегментом дороги.


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


Поиск решения для отрисовки


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


Пример трека, нарисованного посредством Leaflet.hotline.
Пример трека, нарисованного посредством Leaflet.hotline.


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


Однако, остается вопрос — что делать если один и тот же сегмент был пройден в разных треках или вообще разными пользователями. Здесь простая отрисовка трека не поможет, нужно иметь некую базу данных, где представлены участки дороги, которым приведены в соответствие наборы собранных данных (могут быть собраны в разное время разными пользователями). Используя такие наборы данных, можно будет агрегировать данные разных треков и получать финальную оценку качества отрезка дороги.


Агрегация нескольких треков.


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


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


Таким образом мы сможем зарендерить наборы тайлов (map tiles), представляющих собой дополнительный слой для карты.


Слияние тайлов базового слоя и слоя данных карты.
Слияние тайлов базового слоя и слоя данных карты.


Отобразить это все можно используя библиотеку Leflet поверх любой базовой карты в браузере.


Подготовка


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


Определяем сущности:


  • Segment. Представляет часть дорожного полотна. Все сегменты могут быть подсегментом более длинного сегмента (это нам пригодится для отрисовки карты в разных размерах)
  • Profile. Представляет отдельный записанный трек с данными. Пригодится для возможности отрисовки слоев на основе данных из разных профилей.
  • Layer. Представляет слой для отрисовки.

И связи:


  • Profile Segment Quality. Будем использовать для сохранения оценки качества определенного сегмента, затреканного в определенном профиле.
  • Layer Segment Quality. Сюда будут агрегированы данные качества сегмента из разных профилей. Таким образом разные оценки качества из разных профилей превратятся в финальное значение для отрисовки путем агрегации по некоему алгоритму. Эти данные в последующем будут использованы для финальной отрисовки карты.

Диаграмма схемы базы данных.
Диаграмма схемы базы данных.


Импорт сегментов


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


  1. Скачиваем OSM данные для Беларуси в формате *.pbf например отсюда
  2. Для ускорения обработки отрезаем от этих данных Минск, используя osmosis
  3. Поднимаем PostgreSQL c расширением PostGIS (позволяет работать с гео-данными). Я использовал AWS EC2 c готовым имеджем.
  4. Импортируем полученный .pbf файл для Минска в новую базу данных с использованием osm2pgsql

После выполнения этих шагов у нас есть база данных со всем дорогами, зданиями, и другими гео-объектами Минска.
Из всего этого нам пригодятся только дороги.


Была написана небольшая консольная команда, которая извлекает из OSM базы данных дороги, разбивает их в дерево по расстоянию, согласно настройкам.


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


Разбиение на сегменты отрезка дороги.
Разбиение на сегменты отрезка дороги.


Уже терминальные сегменты (в моем случае не длиннее 5 метров) будут на этапе обработки треков сопоставлены с качеством покрытия. Качество покрытия нетерминальных сегментов (в моем случае по 50 метров) будет получено путем агрегация качества подсегментов.


Запускаем команду, идем за чаем, все 90000+ дорог из OSM попали в таблицу segment и превратились в миллионы записей.


Алгоритм


Подготовка завершена: готова БД, сегменты ждут своей участи.
Опишем основные этапы алгоритма обработки сырых треков.


  1. Матчинг трека
  2. Матчинг сегментов
  3. Матчинг данных сенсоров на сегмент
  4. Определение качества сегмента

Матчинг трека


На входе мы имеем последовательность координат, которая далека от реальности из-за погрешностей GPS трекинга. Чтобы корректно определить сегменты на следующих этапах, мы должны привязать сырой трек к реальной дорожной сети.


Эта проблема не нова и называется Map matching


Матчинг трека на дорожную сеть.
Матчинг трека на дорожную сеть.


Один из вариантов решения этой задачи — использование сервисов из проекта OSRM (Open Source Routing Machine).
OSRM предоставляет набор сервисов и REST API к ним. Как это все развернуть описано например здесь.


Нам понадобится сервис Match service. Он принимает на вход последовательность координат и, что важно, точность для каждой координаты. Точность можно получить на этапе сбора данных с мобильного устройства. Результат определения позиции в Android API содержит точность в том числе.


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


Матчинг сегментов


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


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


Алгоритм запроса следующий:


  1. Преобразуем трек в GeoJSON линию, которую способен обработать PostGIS
  2. Используя функцию buffer расширяем нашу линию во все стороны и получаем полигон. Данная операция нужна, чтобы сгладить отклонения в пределах погрешности.
  3. Ищем все сегменты, которые находятся внутри полученного полигона.

Поиск сегментов по треку.


На выходе — список сегментов, пройденных в треке.


Матчинг данных сенсоров на сегмент


На входе:


  • список сегментов
  • трек, представляющий набор координат со временем их прохождения
  • набор данных, в котором каждому элементу соответствует время

Задача — каждому сегменту поставить в соотвествие набор данных с сенсоров.


Пройдемся по каждому сегменту, используя алгоритм:


  1. Буферизируем сегмент (уже не трек, как в предыдущем этапе) до полигона
  2. Разобъем трек на части, используя сегмент-полигон
  3. Возьмем все части трека, которые оказались внутри сегмента
  4. Определим интервал времени, в который был пройден сегмент
  5. По интервалу времени найдем все данные сенсоров

На выходе — список сегментов, каждому из которых сопоставлен набор данных с сенсоров.


Определение качества сегмента


На входе:


  • список сегментов и данные сенсоров, соответствующие каждому

Задача данного этапа — по набору данных дать числовую оценку качеству отрезка дороги.


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


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


Придумаем что-нибудь простое и более или менее правдоподобное. Диапазон значений акселерометра у нас -160 — +160.
Пусть значение качества дороги будет определяться по следующей формуле q = (Amax - Amin) / 320.


Для удобства будем использовать целочисленные значения от 0 до 5, где


  • 0 — минимальное качество (интервал содержит перепады ускорения до 320)
  • 5 — максимальное качество (интервал содержит перепады ускорения до 64)

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


Агрегация сегментов


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


Перед отрисовкой нужно провести агрегацию данных, у которой две разные цели:


  • получение общего значения качества одних и тех же сегментов, полученных разными затреканными профилями (разное время, разные пользователи)
  • получение значения качества сегментов, состоящих из подсегментов (для отрисовки карт с разной детализацией)

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


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


Подсчет качества терминального сегмента по значениям, полученным в разных профилях, выполним используя медиану. Применяем соответствующую функцию PostgreSQL PERCENTILE_DISC, используя значение перцентиля 0.5 получаем медианное значение качества дороги.


Когда значения качества терминальных сегментов готовы можем агрегировать их для получения значений качества родительских сегментов. Здесь нам тоже пригодится перцентиль, но уже с другим значением. Для его определения был задан вопрос — какая часть дороги должна быть в хорошем состоянии, чтобы принять решение о хорошем состоянии всей дороги. Методом математически выверенного тыка было определено значение перцентиля в 0.7. Не спрашивайте почему, это значение, когда карта получается и уже не красная и еще не зеленая.


Как итог этапа агрегации, мы имеем таблицу с данными о сегментах, данные о которых представлены в профилях. Можно рисовать!


Отрисовка


Генерация тайлов


Напомню, в рамках подготовки к обработке данных мы выбрали серверный рендеринг тайлов и Mapnik как opensource вариант для этого.


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


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


Данные есть, нужно указать как их отрисовывать. Для этого создаем стиль с пятью правилами (Rules) (по одному на каждый уровень качества). Таким образом Mapnik раскрасит сегменты разными цветами в соответствии со значениями атрибута quality, который будет извлечен в указанном SQL-запросе.


<Style name="default">
  <Rule>
    <Filter>[quality] = 0</Filter>
    <LineSymbolizer stroke="rgb(255, 0, 0)" stroke-width="1"/>
  </Rule>
  <Rule>
    <Filter>[quality] = 1</Filter>
    <LineSymbolizer stroke="rgb(255, 128, 0)" stroke-width="1"/>
  </Rule>
</Style>

Здесь еще замечу, что для разных уровней приближения будем использовать разные SQL-запросы (с разными длинами сегментов) и разные стили, для чего создадим небольшую обертку на python.


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


Просмотр карты


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


Возьмем уже анонсированную выше библиотеку Leaflet с удобным API. Для создания просмотрщика нам понадобятся два слоя:


  • слой базовой карты
  • слой карты качества дорог

Базовый слой можно использовать какой угодно, я выбрал бесплатный из wiki.openstreetmap.org
Наш слой, сгенерированный с помощью Mapnik, нужно куда-то захостить, возьмем githubpages.


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


Катать!


Все эти несколько месяцев с мыслями о завершении проекта я наивно полагал, что завершающий этап катания по городу с устройством на борту будет самым веселым и простым. Однако я недооценил его сложность :/ Все-таки проехать на выходных 100+км за городом на природе это намного веселее и жизнеутверждающе, чем накручивать круги по городу с автомобильными запахами и не лучшей инфраструктурой, да еще и по далеко не самой оптимальной траектории.


Первоначально была мысль проехать город целиком, внутри МКАД. По всем улицам конечно не поедем, только по основным, на которых есть хотя бы тротуары (в Беларуси можно ездить только по тротуарам, если они есть, их-то мы и инспектируем). Задача немного усложняется тем фактом, что в отличие от Яндекс и Гугл — автомобилей, снимающих панорамы города, нам нужно ездить по одной улице как минимум дважды, так как тротуара обычно два, состояние их бывает очень разным.


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


Схема запланированного и исследованного участков.
Схема запланированного и исследованного участков.


Для того, чтобы ничего не пропустить, я планировал маршруты заранее, составляя треки и закачивая их в maps.me. Кстати, вы знали, что у Android устройств можно сплитать экран и использовать два приложения одновременно? В моем случае это оказалось весьма удобно — на одной части был запущен навигатор, на другом — наше мобильное приложение с мониторингом состояния трекинга.


Транспорт готов к старту.
Транспорт готов к старту.


Обкатать все запланированное получилось за примерно неделю активного катания — все выходные по ~70км, почти каждый будний день с двумя выездами: утром, до работы, около 20км и вечером, после работы, около 30км. Всего чистых затреканных километров получилось около 445км. За это время увидел кучу интересных мест, в которых, кажется, бы и не побывал никогда, хотя объездил Минск на велике достаточно плотно.


Итоги


Нет времени объяснять — держите карту.


Карта качества покрытия тротуаров Минска.
Карта качества покрытия тротуаров Минска.


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


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


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

Источник: https://habr.com/ru/post/521274/


Интересные статьи

Интересные статьи

Проектировщики самолёта отвечают на вопрос о его безопасности: — Ничто не вечно, но современные авиалайнеры невероятно устойчивы, а самолёт — самый безопасный транспорт в мире. Инженеры-строи...
Ранее в одном из наших КП добавление задач обрабатывалось бизнес-процессами, сейчас задач стало столько, что бизнес-процессы стали неуместны, и понадобился инструмент для массовой заливки задач на КП.
Привет, Хабр! Когда-то мы использовали метрику «Вроде бы стало лучше» для оценки качества наших релизов. Но потом мы решили довериться чему-то более надёжному. В этой статье я расскажу о том, как...
OpenCV — библиотека, разработанная для проектов по компьютерному зрению. Ей уже около 20 лет. Я использовал ее еще в колледже и до сих пор применяю для своих проектов на C++ и Python, поскольку...
Один из самых острых вопросов при разработке на Битрикс - это миграции базы данных. Какие же способы облегчить эту задачу есть на данный момент?