Pull to refresh

Как я писал карту дождей

Reading time 14 min
Views 16K
По следам недавней публикации про работу с картами метеорадаров я решил написать о собственном подобном опыте.

Где-то в конце мая, очередной раз прикидывая, соберется ли гроза из вон тех туч за окном, я подумал: если даже на самолетах ставят метеорадары, по которым они обходят грозы, то неужели нет аналогичных стационарных радаров? И если они есть, то не доступны ли изображения с них в интернете?

Не самый быстрый поиск в интернете показал, что такие радары есть, называются ДМРЛ (доплеровские метеорологические радиолокаторы), и снимки с них действительно выставляются в интернете. Есть российский сайт meteorad.ru, белорусский meteoinfo.by (правда, недоступно из России, не из России см. раскрывающееся меню слева), и еще есть сайт orm.mipt.ru. При этом самые приятные картинки — на meteorad.

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

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

Типичная картинка с метеорада выглядела в то время примерно так:
Большие картинки под спойлером, чтобы не разрывать текст
Нижегородский радар


Давайте для начала просто возьмем такого типа картинку с метеорада, обрежем ее и, не удаляя фон, попробуем наложить на картографическую подложку OSM. Благо я, скорее всего, точно знаю, где именно находится нижегородский радар. Взял в качестве фреймвока OpenLayers, наложил, подобрал размер картинки так, чтобы города, отмеченные на радаре, более-менее совпадали… но все равно города, дороги и реки, отмеченные на карте радара, плохо совпадают с подложкой OSM.

Ну что ж, может быть, фоновая карта у радара не очень хорошая? Вот есть еще костромской радар
картинка
Костромской радар

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

Что ж, кое-как попробовал выровнять костромской и нижегородский радары друг относительно друга. Вот состояние проекта на тот момент времени:
скриншот



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

Собственно, причина проблем очевидна: вряд ли на meteorad используют ту же проекцию Web Mercator, что и в OSM и OpenLayers, но какая именно проекция на meteorad — не очень понятно. Конечно, первым делом приходит в голову равнопромежуточная азимутальная проекция — именно в этой проекции «видит» местность радар (радар знает направление и расстояние, с которого пришел сигнал — если мы в полярных координатах нарисуем точку, соответствующую этому сигналу, именно под этим полярным углом и на пропорциональном расстоянии, то это и будет равнопромежуточная азимутальная проекция.)

Точной уверенности в этом у меня не было (особенно в равнопромежуточности), но товарищ посоветовал простой тест: сравнить участки вдоль прямой линии НН—Кострома, вырезанные из нижегородского и костромского радаров. Линии НН—Кострома оказались почти равной длины, и все объекты, лежащие на этой линии (пересечения с реками и границами) тоже находились в одном и том же месте на обоих радарах; это все явно говорило в пользу равнопромежуточности. Чуть позже на сайте meteorad я нашел отдельный документ, описывающий функционирование радаров, и там тоже было сказано, что проекция конечных изображений — равнопромежуточная азимутальная.

Что ж, будем считать, что это мы выяснили. В это же время тот же товарищ посоветовал мне использовать hugin для сшивки картинок с разных радаров. Попробовал. Да, сшивается хорошо, но как накладывать на подложку OSM — непонятно. Поэтому решил, что проше все-таки разобраться с проекциями и честно конвертировать.

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

Правда, теперь уже стал существенно более важным вопрос местоположения радара в Костроме, тем более что это надо знать не только для окончательного размещения картинки на карте, но и для конвертации (хотя в последнем случае погрешности из-за неверного центра, наверное, меньше). Быстрый гугл по «ДМРЛ Кострома» выдает, что радар на самом деле называется Сокеркино, а это небольшая деревня в южном конце Костромского аэропорта. (И да, конечно, сам аэропорт тоже называется Сокеркино, и радар, скорее, назван в честь аэропорта, а не деревни, но это я сообразил только много позже. На OSM название деревни есть, а названия аэропорта нет, поэтому нашел только один объект с этим названием — деревню.) Подставил координаты деревни в качестве координат радара, провел конвертацию, немного подправил координаты и заодно масштаб — о, все стало намного лучше!

Версия проекта на тот момент и
скриншот



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

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


… и тут же видна причина: дороги совпадают относительно хорошо, не совпадает только сама Вологда. Но при этом на OSM Вологда находится южнее трассы М-8, а на карте с радара — севернее! Явная ошибка карт с радаров. Поэтому, видимо, придется все оставшиеся ошибки списать на погрешности карт с радаров, и придти к выводу, что точнее наложить карты с радаров на OSM не получится из-за неточности первых. Возможно, что на карте с радаров отмечены не города, а, например, метеостанции, которые могут находиться чуть в стороне. (Собственно, может, потому часть городов отмечена треугольниками, а часть — кружочками?)

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

Новая версия, и
скриншот


Совпадение очень хорошее, дополнительно подтверждает, что мы все делаем правильно.

Вот только конвертор на питоне работает долго. 30 секунд конвертация одной картинки — это слишком. Я поначалу был сильно удивлен, я думал, что основное узкое место — это непосредственно пересчет координат, несколько косинусов-синусов-чего-еще-там на точку, и вряд ли скорость работы этого зависит от языка программирования. Но ладно, переписал все на C++ (с OpenCV для работы с картинками), теперь конвертация одной картинки занимает пару секунд.

… Казалось бы, осталось немного: убрать фон у картинок — и можно пользоваться. Так казалось до того момента, когда я вдруг обнаружил, что мое выравнивание Москвы внезапно переехало.

Как так? Вот вы видели на картинках, выставляемых на meteorad (см. примеры в начале поста), полосы прокрутки. И вот в один прекрасный день я обнаружил, что изображение с московского радара сдвинулось по вертикали. Как будто картинки на meteorad — это реальные скриншоты с какого-то компьютера, и кто-то подошел к этому компьютеру и подвинул полосу прокрутки.

Что ж, будем детектировать центр картинки. Естественно, не хотелось привязываться к каким-нибудь особенностям фоновой карты; хотелось придумать метод, который будет легко работать для все радаров сразу. Я реализовал следущий подход: на картинке есть серые полосы сетки. По вертикали центр находится на одной из этих горизонтальных полос, осталось определить, на какой. Чтобы это сделать, у нас есть темно-серый фон за пределами круга видимости радара. Чем ближе к настоящему центру, тем меньше этого темно-серого на каждой конкретной горизонтали. Поэтому из всех полос сетки выберем ту, вокруг горизонтали которой (плюс-минус 50 пикселей по высоте) меньше всего темно-серого внешнего фона. Ура, работает. Поскольку на картинке есть и горизонтальная полоса прокрутки, то реализуем тот же подход для детектирования вертикали, на которой находится центр.

Идем дальше. Теперь можно уже писать скрипт, регулярно вытаскивающий новые картинки с метеорада. Будем каждую минуту опрашивать сайт, скачивать картинку и, если она изменилась, то конвертируем ее. Как показал опыт, новые картинки появляются примерно в :07, :17, :27, ..., :57 минут каждого часа — ну, плюс-минус минута. (На самих картинках в районе правого верхнего угла указано время, я даже подумывал было о том, что можно его парсить чтобы определить к какому моменту точно относится та или иная картинка, но руки у меня до этого так и не дошли. Так вот, это время всегда кратно 10 минутам и на 6-7 минут меньше чем время выкладывания картинок.)

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

Все хорошо, но зеленые облака теряются на зеленом фоне MapQuest'а. Идея: сделать черно-белый OSM-слой. Собственный tile-server поднимать не хочется (хотя все равно проект уже завязан на proj и opencv для работы с картинками, поэтому размещать его получится только на vps). Ищу какой-нибудь стандартный черно-белый слой, ничего не нахожу. Видимо, раньше был черно-белый Mapnik, но сейчас он не работает. Нахожу ряд вариантов с OpenMapSurfer или CartoDB, но они меня не очень устраивают.

Одновременно обнаруживаю, что у OpenLayers можно задать слою значение saturation. Задаю в ноль — ничего не меняется. Путем экпериментирования устанавливаю, что saturation работает только если OpenLayers перевести в режим webgl, а это не работает во многих браузерах, а во многих других тормозит. Жалко. В общем, поигрался с разными вариантами и пока вернул обратно цветной MapQuest.

Где-то тут покупаю простенький vps и выкладываю проект в сеть. Пока доступен только по прямому ip, домен сразу покупать не хотел. Заодно пишу письмо администраторам сайта meteorad.ru (по единственному email, найденному на сайте) с вопросом, можно ли использовать их картинки так. Ответа я так и не получил, но теперь меня все время гложет совесть: не явилось ли это письмо причиной всех последующих событий, а именно закрытия доступа к данным в реальном времени. Я думал, стоит им писать или нет, понимая, что, если написать, то могут запретить, а если не писать, то могут и не заметить, но порядочность все-таки пересилила и я написал.

Делаю более продвинутый вариант убирания фона — а именно, заменяю на прозрачные теперь все пиксели, не лежащие в палитре условных обозначений дождей. Это довольно очевидный вариант, но есть одна проблема: черный цвет. Черный используется для двух вещей сразу: для подписей городов и для обозначения ветров. Ветры показываются и как маленькие черные «галочки» в облаках, и как большие черные линии, торчащие из центров радаров. Информацию о ветре очень хочется сохранить, без нее не понятно, куда ожидать движения облаков. (Я планирую сделать анимацию истории, но все равно направление ветра хочется оставить.)

Здесь уже простой подход так просто не придумаешь. Я решил сделать так: беру несколько изображений с одного радара, но с разных дней, и оставляю те пиксели, которые одинаковые на всех изображениях (благо названия городов наносятся поверх осадков) — получаю то, что я назвал «шаблоном». Это и будут в первую очередь названия городов, т.к. ветры успеют смениться. Соответственно, теперь, когда я обрабатываю очередную картинку и натыкаюсь на черный пиксель, я могу посмотреть в шаблон и, если там тоже черный пиксель, то надо этот черный пиксель с картинки удалить, иначе это ветер — надо оставить.

Пришлось еще немного повозиться, чтобы победить эффекты anti-aliasing в подписях городов (далеко не все пиксели там на самом деле черные). У меня не осталось окончательных картинок, но чуть ниже будет примерное представление о том, что было на этом этапе.

Тем временем меня все еще не устраивают никакие варианты OSM-подложки, которые я нахожу. Решаю сделать «проксирующий» tile-server: по запросу конкретного квадратика он будет скачивать этот же квадратик с какого-нибудь общедоступного сервера, переводить в черно-белый и отдавать пользователю. Из множества общедоступных серверов выбрал чепецк.net за то, что карта достаточно подробна даже на мелких масштабах. Написал простенький проксирующий скрипт, плюс оказалось, что просто переводить в черно-белый плохо, надо еще снижать контрастность и осветлять. В итоге остановился на простой формуле: цвет нового пикселя есть
(255*2+(r+g+b)/3)/3
где
(r, g, b)
— цвет старого пикселя.

Текущий результат (здесь еще не до конца почищены артефакты от anti-aliasing подписей городов):
скриншот



Дальше уже проще. Сделаем автообновление: скрипт скачивания пишет информацию о новых картинках в json-файл, javascript на стороне клиента каждые 10 секунд проверяет, не появились ли новые картинки в этом файле. Пока для простоты не стал завязываться ни на какую базу данных. В этом же json теперь есть информация обо всех картинках, когда-либо скачанных, поэтому нет проблем сделать проигрывание истории. Немного повозился с JS, сделал. Правда, загрузка картинок подтормаживает, поэтому пришлось сделать так, чтобы клиент их сначала кешировал.

(Да, на этот момент в json у меня хранилась информация обо всех когда-либо скаченных картинках, и все ти картинки хранились на диске. Я боялся, что место быстро кончится, а json разрастется до неимоверных размеров, но за пару месяцев работы все было еще разумно. Дальше, конечно, надо было бы переходить на БД и удалять старые записи и картинки.)

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

Развлечения продолжались. В очередной день я обнаружил, что картинки на моем сайте не обновляются, хотя на метеораде все работает. Выяснилось, что на метеораде решили изменить формат картинок: с 32-битового PNG на 8-битовый палитровый. И почему-то стандартный imread() из OpenCV отказывался такие картинки открывать. Немного погуглил как загружать палитровые картинки в OpenCV, ничего толкового не нашел и решил: раз у меня все и так на vps, то можно просто найти в линуксе какую-нибудь утилиту командной строки, которая переводит из 8-битового палитрового png в 32-битовый, и запускать ее перед запуском собственно конвертации. Так и сделал. Заодно обнаружилась другая проблема: теперь, во-первых, на картинках на метеораде стали плавать цвета и пришлось цвета сравнивать не на точное совпадение, а на небольшое отличие компонент; во-вторых, в местах, где дороги перекрываются с названиями городов, из-за anti-aliasing (и, видимо, из-за принудительного формирования палитры) цвет иногда стал совпадать с одним из цветов для града, поэтому метод «выкидывать все цвета, не совпадающие с цветами для осадков» стал давать ошибки. Добавил особую проверку: если пиксель картинки имеет такой «плохой» цвет града, а в «шаблоне» там похожий цвет, то это считаем пикселем дороги и тоже выкидываем.

Примерно в те же дни я обнаружил, что на московском радаре не только полосу прокрутки кто-то двигает, но иногда и меняет zoom. (На других радарах почему-то такого не наблюдалось.) Московский радар сначала пару дней был сильно призумлен, а потом несколько дней отзумлен. Призумленный вариант портил всё вообще, не работало даже детектирование центра, т.к. темно-серого внешнего фона на картинке не осталось, а вот на отзумленном варианте центр детектировался более-менее, но все равно ясно, что привязка работала неверно. Отзумленный вариант я довольно быстро исправил: я уже и так детектировал полосы сетки для определения центра, осталось посмотреть на расстояние между этими полосами и сравнить его с эталонным. С призумленным вариантом оставалась проблема детектирования центра, и я так ничего с ней и не сделал; но, в-принципе, не так это и страшно, т.к. все равно на призумленном варианте существенная часть информации теряется, не попадая в область видимости, так что толку от обработки призумленной картинки немного.

Код конвертора уже довольно сильно разросся, пришлось его разбить на несколько файлов и три класса: для детектирования центра и обрезки, для удаления фона и для перевода проекции. Уже даже подумывал о создании testsuite для конвертора.

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

Куча кода сразу стала не нужна. Детектировать центр и zoom уже не надо: судя по всему, они стали выкладывать картинки напрямую сгенерированные программой, а не скриншоты. Удаление фона стало простейшим — там уже не было ни сетки, ни названий городов, надо было только удалить светло-серый внутренний фон и удалить всё за пределами окружности фиксированного радиуса (там был темно-серый внешний фон и дополнительные подписи). Конвертация проекции как была, так и осталась; на самом деле даже немного усложнилась, т.к. я захотел сделать код универсальным для двух вариантов: чтобы можно было конвертировать как картинку дождей (для реальной работы), так и карту-подложку (чтобы проверять корректность привязки), а эти две картинки были разного размера — пришлось добавить простой учет размера исходной картинки.

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

Но тут мне написал один знакомый, что в Москве постоянные дожди, а на радаре их нет. И на исходных картинках на метеораде тоже нет. Посмотрели подробнее и обнаружили, что часть радаров явно не работает: на них написано скорость ветра 0 м/с. Одновременно с этим вдруг обнаружили, что на самом сайте метеорад появилась надпись, что карты предоставляются с задержкой на 24 часа.

Мда. Неработающие радары восстановились в течение дня — видимо, их так переводили на 24-часовую задержку. Но данные действительно очень похоже что идут с задержкой 24 часа, а это очень не интересно; тут же мне скинули ссылку на обсуждение этой задержки во вКонтакте. Я написал обращение в Росгидромет, и одновременно стал думать, откуда бы получать картинки, если не с метеорада. В обсуждении во вКонтакте была ссылка на казанский радар на сайте казанского гидромета, но это мне не так интересно — меня в первую очередь интересовали нижегородский и костромской радары. На сайте нижегородского гидромета никакой подобной картинки не обнаружилось.

Тут я вспомнил про второй сайт, который давно находил — meteoinfo.by. Там, конечно, пространственное разрешение картинок было явно хуже, но хоть что-то. А еще у них на сайте в «Положении о порядке использования информации» было явно разрешено использование информации на других сайтах при условии ссылки на них. Так что я просто переделал код на работу с белорусским сайтом. Конечно, их картинки заблокированы для России, но vps у меня не в России, поэтому с него все открывается. Правда, не так просто оказалось сделать привязку, но в итоге я решил, что фоновая карта у них еще хуже, чем на метеораде; например, взаимное положение Нижнего Новгорода относительно Волги на картах нижегородского и костромского радаров было разным.

Все поработало несколько дней, после чего конкретно моему скрипту скачивания белорусы стали отдавать редирект на meteoinfo.by/radar/no_radar.gif. На картинке была надпись «Мы посчитали, что должны ограничить вам доступ к нашим радарным картам» — и email администратора сайта. Ну ладно, написал им, указав, что я соблюдаю требования «Положения о порядке использования информации». Получил ответ:

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


Ну, вот, видимо, и всё на данный момент. Из Росгидромета мне пришел ответ полностью аналогичный тем, что другие люди постили в комментариях к предыдущей записи, вкратце: «мы не обязаны публиковать эти данные, поэтому только отдельным потребителям и за плату». (Хотя стоит отметить неожиданную оперативность Росгидромета: ответ пришел недели через две после обращения, я ожидал, что будет больше месяца.) Написал на data.gov.ru, но пока там никакого результата нет.

Поэтому предлагаю всем, кому интересны эти картинки, написать и в Росгидромет, и на data.gov.ru — это все можно сделать (если вдруг кто не знает) через интернет, может, получится какой-нибудь результат.

И да, предпоследняя версия сайта, использующая данные с метеорада, но с задежкой на 24 часа: radarmap.info. Картинки продолжают выкачиваться, т.е. сейчас там реальные данные 24-часовой давности. В правом верхнем углу кнопка для проигрывания истории.
скриншот




P.S. В обсуждении предыдущей публикации, да и раньше, когда я рассказывал людям про этот проект, я сталкивался с мнением «зачем все это надо, ведь и так есть многочисленные сервисы, предоставляющие карту облачности и дождей?» Мой ответ на этот вопрос следующий. Эти сервисы основываются на прогнозе и потому имеют ряд
фундаментальных недостатков
Во-первых, эти сервисы не различают достаточно точно моросящий дождь от ливня, от грозы, от града со шквалом. Конечно, количество выпавших осадков позволяет понять интенсивность дождя, но далеко не так точно.

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

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

Наконец, из-за низкого пространственного разрешения и высокое временнОе не подмога; по-моему, проку от подобных карт не сильно выше, чем от стандартного «почасового» (на самом деле по-3-часового) прогноза на гисметео. Ну да, с утра дождь, к 12 часам развеет. Но не более подробно.
Tags:
Hubs:
+24
Comments 17
Comments Comments 17

Articles