Встраивание карты: SDK и протокол
> Кратко. Карта сети встраивается на чужой сайт через iframe страницы /embed с ключом встраивания; сайт управляет ею и получает события через SDK tgid-embed.js или напрямую сообщениями postMessage. Здесь — протокол, проверки и адреса API. Что видит посетитель — Встраиваемая карта, как выдать ключ — Встраиваемые карты.
Как устроено
- Администратор выдаёт ключ
tge_…: сайты (схема, хост, порт), слои, фрагменты, отключения, начальный вид, срок. В базе (tgid_embed_key) хранится только SHA-256 ключа. - Сайт вставляет iframe
<сайт ТГИД>/embed?key=…— сам или через SDK. - Страница
/embedузнаёт сайт-родитель (location.ancestorOrigins, в Firefox —document.referrer) и рисует карту, только если он есть в ключе. Не во фрейме и во фрейме самого ТГИД — тоже рисует (предпросмотр). - Настройки и тайлы страница берёт из 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.
