Встраивание карты: SDK и протокол

> Кратко. Карта сети встраивается на чужой сайт через iframe страницы /embed с ключом встраивания; сайт управляет ею и получает события через SDK tgid-embed.js или напрямую сообщениями postMessage. Здесь — протокол, проверки и адреса API. Что видит посетитель — Встраиваемая карта, как выдать ключ — Встраиваемые карты.

Читаю как

Как устроено

  1. Администратор выдаёт ключ tge_…: сайты (схема, хост, порт), слои, фрагменты, отключения, начальный вид, срок. В базе (tgid_embed_key) хранится только SHA-256 ключа.
  2. Сайт вставляет iframe <сайт ТГИД>/embed?key=… — сам или через SDK.
  3. Страница /embed узнаёт сайт-родитель (location.ancestorOrigins, в Firefox — document.referrer) и рисует карту, только если он есть в ключе. Не во фрейме и во фрейме самого ТГИД — тоже рисует (предпросмотр).
  4. Настройки и тайлы страница берёт из API без входа, по ключу:
Адрес Что Ответ без ключа или с чужим
GET /api/v1/embed/config?key= слои с зумами, вид, сайты, охват сети, город (Cache-Control: private, max-age=60) 403
GET /api/v1/embed/tiles/{слой}/{z}/{x}/{y}?key= MVT слоя; 204 — пусто или слой ещё не виден на этом зуме 403

Ответы на копии Алматы: без ключа — 403 «Нужен ключ встраивания (параметр key)», с выдуманным — 403 «Ключ встраивания не принят: отозван, истёк или из другого города».

Ключ публичный — он виден в коде страницы сайта. Поэтому в тайлах нет имён потребителей, а сервер тайлов сайт-родитель не проверяет: ему достаточно действующего ключа. Отбор сайтов делает страница /embed.

Заголовки веба (server/middleware/frameAncestors.ts): /embed и /outages — Content-Security-Policy: frame-ancestors *; остальные страницы — frame-ancestors 'self' и X-Frame-Options: SAMEORIGIN.

SDK

<div id="tgid-map" style="height:480px"></div>
<script src="https://tgid.example/itwin-map/tgid-embed.js"></script>
<script>
  const map = TGIDEmbed.create(document.getElementById('tgid-map'), {
    url: 'https://tgid.example/itwin-map/embed', key: 'tge_…',
    layers: ['pipes', 'heat_sources'], center: [76.92, 43.245], zoom: 15, ui: true,
  });
  map.on('select', (e) => console.log(e.layer, e.id, e.properties, e.lngLat));
  map.ready.then(() => map.highlight('pipes', [993, 994]));
</script>

Адреса условные. TGIDEmbed.create(элемент, параметры) создаёт iframe с referrerpolicy= "strict-origin-when-cross-origin" (с no-referrer Firefox не сообщит сайт, и карта не откроется). Параметры: url, key (обязательны), layers, center, zoom, ui (false — без плашки и карточки), height, title.

Метод Что
on(тип, обработчик) подписка; возвращает функцию отписки; off(тип, обработчик)
setView({ center, zoom }) перелёт
fitBounds([запад, юг, восток, север], отступ) показать область (отступ до 200 px)
setLayers([...]) видимые слои — из слоёв ключа
highlight(слой, [id…]) подсветить до 500 объектов; пустой список — снять
getView() Promise<{ view: { center, zoom }, bounds }>; нет ответа за 5 с — отказ
destroy() убрать iframe и подписки
ready Promise, выполняется по событию ready

Команды до ready SDK копит и отправляет после загрузки карты.

Протокол сообщений

Если SDK не подходит (свой фреймворк, другой язык), можно обмениваться с iframe напрямую. Каждое сообщение — объект { source: 'tgid-embed', v: 1, type, … }.

Команды сайта → карта (iframe.contentWindow.postMessage(сообщение, '<origin сайта ТГИД>')):

type Поля
setView center?: [долгота, широта], zoom?
fitBounds bounds: [w, s, e, n], padding?
setLayers layers: [...]
highlight layer, ids: number[]
getView id — строка до 64 символов; ответ придёт событием view с тем же id

События карта → сайт:

type Поля
ready layers, view
select layer, id, properties, lngLat
moveend view, bounds
view id, view, bounds — ответ на getView
error message — ключ перестал действовать (один раз)

Проверки с обеих сторон: карта принимает команды только от сайта-родителя из ключа и отвечает только ему (не *); команды разбирает parseCommand (utils/embedProtocol.ts), остальное отбрасывает. SDK принимает сообщения только из своего iframe (event.source) и с адреса карты (event.origin). В своём коде делайте те же проверки.

Слои: pipes, nodes, valves, consumers, heat_sources. id объекта — его номер в базе, тот же, что в OGC API и OBJECTID FeatureServer. У потребителей номера реальных и обобщённых совпадают, поэтому highlight('consumers', [id]) и select могут относиться к двум объектам; вид — в properties.kind.

Для разработчика

  • API: routers/embed.py, database/embed.py (LAYERS, зумы, прореживание, кеш проверки ключа 60 с и тайлов обзорных зумов), миграция sql/migrations/20261030_embed_keys.sql. Описание — docs/embed-maps.md.
  • Веб: pages/embed.vue, utils/embedProtocol.ts (типы сообщений, parseCommand, стили слоёв), public/tgid-embed.js, components/EmbedKeysDialog.vue.
  • Тесты: vitest — tests/utils/embedProtocol.spec.ts, tests/utils/embedSdk.spec.ts; API — tests/test_embed.py; e2e — отказ без ключа и с чужим ключом в e2e/functions/developer-api.spec.ts.
Хотите увидеть эту функцию на своих данных?Покажем ТГИД на демонстрации и ответим на вопросы инженеров.
Запросить демо