Всем привет. Меня зовут Александр Иннокентьев, и уже больше года мы с моим коллегой Павлом Головлевым делаем веб-инструмент для гидрологов под названием «Уровень-Спутник».
Идея проекта родилась из вполне обычной рабочей проблемы. Когда занимаешься русловыми процессами, берегами, поймой, затоплением или просто пытаешься понять, что происходило на реке в конкретный момент, очень быстро упираешься в одну и ту же вещь: снимков много, гидрологических данных тоже много, но связать одно с другим удобно и быстро не так-то просто.
Казалось бы, что сложного: открыл архив спутниковых снимков, нашёл нужную дату и работаешь. На практике всё не так. Один снимок закрыт облаками, другой снят не в тот день, третий вроде хороший, но уровень воды на гидропосту в этот момент совсем не тот, который тебе нужен. А ведь для гидролога это критично: один и тот же участок реки при разных уровнях воды может выглядеть настолько по-разному, что сравнивать такие снимки напрямую иногда просто бессмысленно.
В какой-то момент нам надоело каждый раз собирать это вручную. Так и появился «Уровень-Спутник» — сервис, который помогает подбирать снимки 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.
