Морские карты S-57 в браузере: открытый парсер на TypeScript и что в нём сломали и починили первые пользователи

—

от автора

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

Причалы гавани складываются в 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 = 5B(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.

ссылка на оригинал статьи https://habr.com/ru/articles/1087234/