Это вторая версия статьи (первую модератор удалил, причину мне не сообщили. Что ж, пишу заново: без ссылок на свои каналы, зато с тем, что за эту неделю изменилось в проекте). Буду рад, если на этот раз текст останется на сайте.

Причалы гавани складываются в S-57, слева настоящие байты карты US5MA12M.000
s57-parser: морская карта S-57 в браузере

Коротко о чём речь. Электронные навигационные карты (ENC) формата S-57 стоят на каждом коммерческом судне. Стандарт живёт с 1996 года, но живого JavaScript-парсера для него я не нашёл: либо серверный GDAL, либо коммерческие SDK за десятки тысяч долларов в год. Поэтому я написал свой стек: бинарный парсер ISO 8211, модель данных S-57 и S-101, рендер символики S-52 на Canvas2D. Всё работает в браузере, лицензия MIT.

Демо на реальной карте NOAA: https://devladpopov.github.io/s57-parser/

Исходники: https://github.com/devladpopov/s57-parser

Зачем это нужно

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

  • единственный JS-пакет для S-57 на npm давно заброшен и читает только метаданные;

  • веб-решения конвертируют карты на сервере через GDAL или используют коммерческие SDK;

  • открытого браузерного рендерера символики S-52 я не встречал.

Отдельно про наш контекст. Крупные поставщики ECDIS-движков это западные компании, лицензировать их сейчас сложно. А S-57 это международный стандарт IHO, в нем выпускаются и отечественные карты. Парсер читает любую S-57-ячейку независимо от того, кто её выпустил, работает на клиенте и не ходит в облако. Это может пригодиться морским вузам, внутренним проектам и всем, кому просто нужно посмотреть карту без тяжёлого софта.

Как устроен S-57

Карта лежит в бинарном формате ISO 8211 (файл .000). Внутри три вида записей:

  • feature records: объекты реального мира (буи, маяки, изобаты, берег);

  • spatial records: геометрия (узлы, рёбра, координаты);

  • топология chain-node: полигоны собираются из рёбер, рёбра начинаются и заканчиваются в узлах.

Координаты целые, с множителем COMF (обычно 10 000 000).

Грабли, на которые я наступил при разработке

Две почти одинаковые нотации бинарных полей

b15   суффиксная нотация: signedness = 1, byteWidth = 5
B(40) скобочная нотация: 40 бит = 5 байт, unsigned

Поле NAME в записи FSPT описано как B(40). Если разобрать его как суффиксное b40, получаешь 0 байт вместо 5, и все ссылки на геометрию рассыпаются. Пустой канвас и несколько часов с hex-дампами файлов NOAA.

Одинаковые RCID у разных типов записей

Edge (RCNM = 130), ConnectedNode (120) и IsolatedNode (110) могут иметь одинаковый RCID. Если брать RCID ключом в Map, записи затирают друг друга. Лечится составным ключом rcnm * 100000 + rcid.

Обновления

Карты обновляются еженедельно файлами .001, .002 и так далее: вставка, удаление и изменение записей, плюс сплайсы координат и ссылок на уровне подзаписей. Всё это парсер применяет поверх базовой ячейки.

Что изменилось за неделю: баги, которые нашли люди

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

Плагин для Leaflet не работал вообще. Класс слоя не наследовался от L.Layer, и Leaflet 1.9 падал с ошибкой “The provided object is not a Layer” при добавлении на карту. Тесты проверяли только парсер, поэтому три месяца этого никто не замечал. Исправлено, опубликована версия 0.1.2, появилась живая демка поверх OpenStreetMap: https://devladpopov.github.io/s57-parser/leaflet.html. Заодно сделал такую же для MapLibre.

Кнопка “Open” в каталоге карт не срабатывала никогда. В каталоге 7108 ячеек NOAA, и у каждой есть кнопка открыть в вьюере. Но сервер NOAA не отдаёт заголовок Access-Control-Allow-Origin, и браузер блокирует чтение архива с чужого домена. Я знал об этом ограничении и сделал “честный” запасной вариант “скачайте и перетащите файл”. В итоге кнопка выглядела рабочей, но не работала ни для одной карты. Это нашёл Владимир Калачихин, автор картплоттера GaladrielMap. Теперь архивы идут через маленький прокси на Cloudflare Workers, который пропускает только файлы ENC с сайта NOAA и добавляет нужный заголовок. Прогнал 36 карт из всех шести масштабных диапазонов, включая ячейки с 1-4 файлами обновлений: все открываются.

Вьюер не открывался без сервера. Он был подключён как ES-модуль, а браузеры не грузят модули с file://. Для переносимого приложения это плохо, и Владимир справедливо на это указал. Переделал на обычный скрипт, а сборка теперь кладёт ещё и самодостаточный файл s57-viewer.html: весь вьюер одним HTML, его можно скачать, открыть офлайн и перетащить на него карту: https://devladpopov.github.io/s57-parser/s57-viewer.html

Карта пока не похожа на карту. Это тоже справедливое замечание. Сейчас точечные объекты рисуются примитивами, без полноценной символики S-52. План такой: брать символы из портрейал-каталога S-101, который IHO выложила открыто (там 725 SVG-символов и палитры для дня, сумерек и ночи), собрать из них спрайт-атлас и рисовать им. Обсуждение идёт в Discussions проекта, туда же приглашаю всех, кто работал с символикой S-52.

Как попробовать

npm install @s57-parser/s57 @s57-parser/s52-render
import { parseS57, toGeoJSON } from '@s57-parser/s57';

const dataset = parseS57(buffer);
const geojson = toGeoJSON(dataset);

Для Leaflet и MapLibre GL JS есть плагины: @s57-parser/leaflet и @s57-parser/maplibre.

Оговорка про ИИ

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

Ссылки

Если работаете с ENC, особенно с символикой S-52 или с переходом на S-101, буду рад замечаниям в комментариях или на GitHub.

Комментарии (14)


  1. black_list_man
    27.09.2026 21:33

    Сильно ли отличается s101 от s57? Когда то делал на qt/opengl вьювер карт s57 со стилем s52(по крайней мере, той версией, что доступна в сети, 3.0 вроде бы). Символы там описывались векторной графикой на HPGL. Тоже рисовал всë в атлас и рендерил все символы одним батчем. Самая сложная часть s52 это conditional rules, т.е. отображение, зависящее от множества факторов, типа пересекается ли данная геометрия с областью такого-то типа с таким-то атрибутом и тд.


    1. StudyQA Автор
      27.09.2026 21:33

      Спасибо, приятно встретить человека, который это уже проходил))

      Контейнер тот же: S-101 тоже кодируется в ISO 8211, поэтому бинарный парсер у меня общий на оба формата. А вот модель данных вроде другая. S-101 построен на S-100: каталог объектов машиночитаемый (XML), а не зашит в спецификацию; атрибуты бывают составными и вложенными; есть отдельные типы информации; геометрия описывается кривыми и поверхностями вместо узлов и ребер chain-node.

      Но, кжется, для вашей боли главное отличие в отображении. В S-52 условные процедуры описаны в спецификации псевдокодом, и каждый производитель переписывал их у себя руками, как вы и делали. В S-101 их заменил Portrayal Catalogue: правила отображения написаны на луа и поставляются вместе с каталогом, символы в SVG вместо HPGL, палитры отдельно. короче есть conditional rules теперь не нужно реализовывать, их нужно исполнять. IHO выкладывает этот каталог открыто на GitHub.

      Ну а у меня условных процедур почти нет. Есть заливка DEPARE по DRVAL1/DRVAL2, пока с фиксированными порогами, а не от safety contour, и цвета и секторы огней. Проверок вроде “лежит ли OBSTRN над областью глубин меньше безопасной” нет совсем. Как раз обсуждаем в Discussions на GitHub переход на символы и правила из каталога S-101 с атласом, по той же схеме, что вы описали.

      Ну и раз уж вы спросили, то мне интересно, как вы решали пространственную часть: заранее считали пересечения при загрузке ячейки или проверяли на лету через какой-нибудь индекс? Для браузера этот вопрос для меня сейчас в приоритете


      1. black_list_man
        27.09.2026 21:33

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


        1. StudyQA Автор
          27.09.2026 21:33

          Спасибо, по вашему совету уже поправил. Символы и подписи площадных объектов теперь ставятся в центр видимой части: на каждой смене вьюпорта полигон обрезается по экрану, центр считается по обрезанной фигуре, а если он выпадает наружу (вогнутый полигон или дырка), то берется середина самого широкого внутреннего отрезка. Для больших полигонов считаю по упрощенной геометрии, копия под каждый уровень масштаба с погрешностью около пикселя, и все это дело кэшируется. Лукап и приоритеты тоже теперь считаются один раз при загрузке ячейки. Правда сами условные процедуры у меня пока упрощенные, полноценные CSP с пересечениями (DEPCNT, OBSTRN, WRECKS, SOUNDG) ещё впереди, и тут ваш опыт очень пригодился бы.


  1. petrovichbrothers_design
    27.09.2026 21:33

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

    В целом даже получается, но совсем не просто


    1. StudyQA Автор
      27.09.2026 21:33

      О, круто, что кто-то этим занимается вживую))

      Навионикс в S-57 это прям боль, там же своя модель данных, и все приходится перекладывать в объекты и chain-node топологию S-57. Самое неприятное, по моему опыту, это как раз топология и DDR в ISO 8211: файл вроде собирается, а читалка молча показывает пустоту.

      Если хотите быстро проверить, что получилось, киньте свой .000 в демо: https://devladpopov.github.io/s57-parser/ Там сразу видно, собираются ли полигоны и не разъехались ли ссылки на геометрию. А если парсер на вашем файле сломается, пришлите, пожалуйста, мне такие кейсы как раз очень нужны для тестов.

      Я тут не хотел связываться с навиониксом из-за лицензии: для себя на лодке ок, а выкладывать сконвертированные карты вроде уже нельзя.

      А чем конвертируете? Через GDAL или пишете свой экспорт? И что пока самое сложное?


      1. petrovichbrothers_design
        27.09.2026 21:33

        Привет! У меня сугубо утилитарная задача - сделать для себя навигацию по ветру для Рыбинского водохранилища для qtVlm. Это старый французский софт чтобы прокладывать навигацию по ветру, получать прогнозы погоды, AIS и тд. Похож на SAS-Planet по концепции. Разобраться в нем нет шансов сходу, интерфейс дикий, но в нем есть всеm он настраивается и работает хорошо. И отличная документация и форум, поэтому чатгпт прекрасно отвечает на все вопросы. Он работает в том числе с картами s57 (s63). Так как навионикс у меня уже есть, подумал, что бы из него не вытащить.

        Мне казалось что карты должны быть зашифрованы, и я зашел издалека ))

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

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

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

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

        Я попробовал в вашем просмотрщике посмотреть тайлы - в целом нормально видно, но местами много лишних линий, такое ощущение что он сам закрывает контуры незакрытые. У меня они норм выглядят. Могу в ТГ отправить тайлы