Всем привет. Меня зовут Александр Иннокентьев, и уже больше года мы с моим коллегой Павлом Головлевым делаем веб-инструмент для гидрологов под названием «Уровень-Спутник».

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

Казалось бы, что сложного: открыл архив спутниковых снимков, нашёл нужную дату и работаешь. На практике всё не так. Один снимок закрыт облаками, другой снят не в тот день, третий вроде хороший, но уровень воды на гидропосту в этот момент совсем не тот, который тебе нужен. А ведь для гидролога это критично: один и тот же участок реки при разных уровнях воды может выглядеть настолько по-разному, что сравнивать такие снимки напрямую иногда просто бессмысленно.

В какой-то момент нам надоело каждый раз собирать это вручную. Так и появился «Уровень-Спутник» — сервис, который помогает подбирать снимки Landsat и Sentinel не просто по дате, а под конкретный уровень воды на гидропосту.

И тут обычно возникает закономерный вопрос: а зачем вообще кому-то подбирать космоснимки именно под уровень воды? Если коротко — затем, что иначе очень легко начать сравнивать несравнимое.

Общий вид платформы
Общий вид платформы

Как зарождался прототип

Когда идея «Уровня-Спутника» только появилась, первым делом я сел делать прототип в Google Earth Engine. Логика была простой: платформа бесплатная, там уже есть спутниковые данные, значит можно быстро собрать рабочую схему и проверить саму идею.

Первый прототип мы делали на одном бассейновом округе. Для тестов брали данные с АИС ГМВО. На этом этапе стало понятно, что сама идея рабочая, но технически всё выглядело довольно тяжело: интерфейс лагал, фильтрации работали медленно, а большое количество обращений к коллекциям просто съедало лимиты GEE. Сейчас, когда квоты Earth Engine стали ещё строже, я особенно ясно понимаю, что решение со временем уйти на другую платформу было правильным.

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

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

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

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

  • Один бассейновый округ обсчитывался примерно 8–10 часов, и это был ещё не самый крупный случай. А всего таких округов больше двадцати.

  • При такой схеме почти не было нормального масштабирования: каждый новый день, каждый новый пост и любое расширение набора данных означали пересчёт или дозаполнение огромной заготовки.

  • Система получалась слишком хрупкой: если где-то на одном из этапов закрадывалась ошибка, значительную часть работы приходилось переделывать заново.

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

Потом чуть не исчезли сами данные

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

История с получением этих данных вообще получилась почти приключенческой. Я как раз был в командировке — у гидрологов это называется «в полях», хотя значительную часть времени мы проводим не в поле, а на воде. За несколько дней до выезда я начал делать парсер для АИС ГМВО, чтобы в перспективе массово наполнять базу данных для всего проекта.

И вот в один из дней мы под вечер возвращаемся на лодке к точке выгрузки, время что-то около пяти вечера, и во всех чатах одновременно начинает сыпаться одно и то же: портал вот-вот закроют, в 18:00 по Москве точно всё, часть данных уже недоступна, срочно сохраняйте всё, что можете.

Это наши "поля"
Это наши "поля"

И я буквально сижу в лодке и с помощью ИИ допиливаю парсер, чтобы он мог массово выкачать всё нужное. Потом забегаю в гостиницу, в спешке пытаюсь всё это развернуть и запустить. С первого раза, конечно, ничего нормально не заводится, но в какой-то момент процесс всё-таки пошёл. И в 17:55 я вижу заветное download successful и наконец выдыхаю.

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

Поворотный момент: STAC и Microsoft Planetary Computer

Если коротко, следующим большим поворотом для проекта стали STAC и Microsoft Planetary Computer. На конференции в ИКИ РАН, посвящённой дистанционным методам, нам посоветовали посмотреть именно в эту сторону. И это оказалось очень удачным советом.

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

А Microsoft Planetary Computer — это платформа, которая даёт доступ к таким каталогам и к самим данным через API. То есть не нужно жить внутри одной замкнутой системы, как в раннем прототипе на Earth Engine. Можно строить свою собственную архитектуру: отдельно хранить гидрологические данные, отдельно быстро искать нужные сцены по метаданным, отдельно подгружать снимки уже по запросу.

Для «Уровня-Спутника» это было очень важное изменение. Мы ушли от модели, где всё держалось на тяжёлом фильтровании коллекций внутри одной платформы, к более гибкой схеме: быстрый запрос к своей базе, поиск подходящих сцен по метаданным и обращение к спутниковым данным уже тогда, когда действительно нужно.

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

Когда CSV и Excel заканчиваются

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

Тут стоит честно сказать: я не человек с классическим бэкграундом разработчика. Я скорее самоучка. То есть это уже давно не уровень hello world, но и до образа «сеньор-разработчика» мне очень далеко. С нормальными базами данных до этого проекта я почти не работал, и во всё это тоже приходилось влезать по ходу дела.

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

И именно на этом этапе проект получил, наверное, самый заметный прирост в скорости. Если раньше связь «уровень воды — спутниковый снимок» собиралась через тяжёлые обходные схемы, то после перехода к нормальной базе данных запросы начали проходить практически мгновенно — по ощущениям уже за миллисекунды или за считанные секунды.

Что представляет собой проект сейчас

Сейчас «Уровень-Спутник» — это уже не просто набор экспериментов, а живой рабочий сервис.

По своей логике он устроен довольно просто: пользователь выбирает гидропост, задаёт интересующий диапазон уровней воды, период, месяцы, облачность — и получает список подходящих сцен Sentinel-2 и Landsat. Дальше эти сцены можно посмотреть на карте, а нужные данные — выгрузить и проанализировать.

Если совсем коротко, схема теперь такая:

  • В базе лежат гидропосты и многолетние ряды наблюдений — около 28 миллионов строк по 2 657 постам за 2008–2023 годы.

  • Пользователь задаёт условия отбора.

  • Сервис быстро находит подходящие даты и уровни.

  • Потом уже по этим условиям обращается к каталогу спутниковых сцен через MPC.

  • Пользователь получает не абстрактный список снимков «где-то рядом», а набор сцен, реально привязанных к гидрологической обстановке.

Сводка по посту
Сводка по посту
Статистика по посту за выбранный период
Статистика по посту за выбранный период
Подбор космоснимков на выбранную территорию
Подбор космоснимков на выбранную территорию

Водосборные бассейны — новая функция, которую мы давно хотели сделать

Несколько дней назад (20.05.2026) мы добавили в сервис то, что откладывали довольно долго: для каждого гидропоста теперь показывается его водосборный бассейн.

Водосбор — это полигон территории, с которой вся вода в конечном счёте стекает в реку выше поста. Для гидролога это один из базовых контекстов: размер бассейна, его рельеф, покрытие, осадки — всё это объясняет, почему на посту такой, а не иной режим. Без этой картины интерпретировать гидрограф довольно сложно.

Раздел "Водосборы"
Раздел "Водосборы"

Как это работает

Граница водосбора выделяется по глобальной цифровой модели рельефа MERIT-Hydro (90 м) — специально подготовленной для гидрологических расчётов, с исправленными артефактами и прорезанными руслами. Мы используем для этого сервис mghydro.com, который разработал Мэттью Хебергер (github.com/mheberger/delineator). Алгоритм строит направления стока для каждой ячейки модели рельефа, а затем собирает все ячейки, сток с которых проходит через точку поста.

Помимо полигона, для каждого бассейна автоматически подтягивается около 20 характеристик из открытых глобальных источников:

  • покрытие территории из ESA WorldCover 2020 (леса, пашня, болота, застройка, вода)

  • население по WorldPop 2020

  • осадки по CHIRPS

  • испарение по MODIS

  • тренд подземных вод по данным спутников GRACE / GRACE-FO

  • средние высоты, длина речной сети, число крупных водохранилищ

Как мы это всё получили для 2 657 постов

mghydro.com отдаёт данные через два эндпоинта: один возвращает GeoJSON полигона, второй — HTML-страницу с характеристиками. Второй пришлось парсить через BeautifulSoup, потому что никакого JSON там нет.

В процессе батч-обработки мы набили несколько шишек, о которых честно расскажем.

Шишка первая: язык ответа. Сервер определял наш российский IP и отдавал русскоязычный HTML. Все паттерны типа "tree cover" не находили ничего, потому что в ответе было "древесный покров". Решение оказалось простым до неприличия — один заголовок:

headers = {"Accept-Language": "en-US,en;q=0.9"}

Шишка вторая: баг подстроки. В таблице покрытия есть строки "Tree cover" и "Wetland + tree cover". Условие if "tree cover" in key срабатывало для обеих, и вторая затирала первую. В логах всё выглядело нормально — числа были правдоподобные, просто неправильные. Заметили только когда вручную сравнили с цифрами прямо на сайте. Исправление тривиальное:

# было:
if pattern in key:
# стало:
if key == pattern:

Шишка третья: перетирание метаданных. Функция парсинга инициализировала словарь d = {f: None for f in FIELDS}, включая поля post_code, lat, lon. Потом row.update(parsed) перезаписывал эти поля в None. В итоге CSV с характеристиками был полным, но без привязки к посту. Исправили, изменив инициализацию на d = {}.

Из 2 657 постов в итоге успешно обработано 2 481. Остальные ~6.6% — в основном труднодоступные районы, где MERIT-Hydro не справляется с выделением водосбора.

Небольшой технический блок

Раз уж Хабр всё-таки техническая площадка, совсем без инженерной части не обойтись. Но здесь хочется не уходить в документацию, а коротко объяснить, в чём вообще была ключевая техническая идея.

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

Что лежит в основе проекта:

Слой

Что используется

Backend

Python, Flask, Gunicorn

База данных

PostgreSQL, PostGIS, ~28 млн наблюдений

Спутниковые данные

Microsoft Planetary Computer

Водосборы

mghydro.com (Matthew Heberger) · MERIT-Hydro 90 м

Клиентская часть

Leaflet.js, Chart.js

Инфраструктура

Docker, nginx, HTTPS

Про домен и ощущение, что всё это не зря

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

И, наверное, отдельно я до сих пор немного радуюсь тому, что у проекта есть свой домен — level-satellite.ru. Для людей из IT, которые постоянно что-то запускают и выкатывают свои продукты, это, возможно, обычная вещь. Но для меня это был почти отдельный эмоциональный рубеж: больше года работы, десятки переделок, сомнений, костылей, новых решений — и вот у этого всего наконец появилось своё настоящее имя и адрес.

Он ещё не идеален, но он живой

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

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

Если вам откликается такая тема, можно заглянуть на сайт проекта: level-satellite.ru. А если интересно следить за развитием — вот публичный репозиторий: github.com/ruorv/level-satellite-public.