Introdução

Esta página leva você de um mapa publicado a uma página que o controla. Ela supõe que um mapa já foi publicado e que você tem o ID de publicação dele (tem o formato pub_... e é a última parte da URL de incorporação).

1. Permita o seu site

Um mapa só aceita comandos de sites que o autor indicou. Peça ao autor que abra o mapa no Icon Map, escolha Publish e:

  1. Adicione seu site em Allowed origins: o esquema e o host de onde sua página é servida, por exemplo https://portal.contoso.com. Um subdomínio curinga é permitido (https://*.contoso.com); um caminho não é.
  2. Deixe Let the hosting page control this map from script (Embed API) ativado, assim como os grupos de comandos de que você precisa.
  3. Publique. As origens permitidas e as chaves dos grupos fazem parte da publicação assinada, então qualquer alteração exige uma nova publicação.

Durante o desenvolvimento, adicione também sua origem local, por exemplo http://localhost:5173. Uma origem é esquema + host + porta, portanto http://localhost:5173 e http://localhost:3000 são sites diferentes.

Se a sua origem não estiver na lista, o mapa ainda é exibido na sua página, só não pode ser controlado. map.ready é rejeitado com o código denied_origin, para que você descubra na hora em vez de ficar se perguntando por que nada acontece.

2. Instale o SDK

Carregue o SDK com uma tag de script. Ele define um único objeto global, IconMapEmbed:

<script src="https://www.icon-map.com/js/iconmap-embed/2/iconmap-embed.umd.min.js"></script>
<script>
  const { embed } = IconMapEmbed;
</script>

O SDK tem cerca de 17 KB e nenhuma dependência. Funciona em qualquer lugar que aceite uma tag de script, incluindo páginas do SharePoint, do Confluence e de sistemas de CMS.

Um pacote npm (@iconmap/embed, com tipos TypeScript, para uso com um bundler) está a caminho. A API é a mesma; os trechos abaixo que usam import se aplicam quando ele for publicado. Até lá, use o objeto global IconMapEmbed.

3. Monte o mapa

Dê ao mapa um elemento com tamanho definido. O frame o preenche.

<div id="map" style="height: 600px"></div>
const map = embed(document.getElementById("map"), {
  publishId: "pub_...",
  onError: (e) => console.error(e.code, e.message),
});

const capabilities = await map.ready;
console.log(capabilities.commands);   // exactly what THIS map answers

ready é resolvido com o descritor de capacidades. Use-o para decidir o que sua página oferece:

const can = (name) => capabilities.commands.includes(name);
if (!can("setFilters")) hideMyFilterPanel();

Tome decisões com base nas capacidades, não em números de versão: a lista já considera a versão do visualizador, o tipo de incorporação e as chaves do autor.

4. Envie comandos

Cada comando é um método que retorna uma promise.

await map.flyTo({ center: [-3.18, 51.48], zoom: 12 });

const layers = await map.getLayers();
await map.setLayerVisibility({ layerId: layers[0].id, visible: false });

await map.setSlicerState({ slicerId: "slicer-region", state: { values: ["Wales"] } });

Você pode chamar comandos assim que tiver o handle. Tudo o que for enviado antes de o mapa terminar de iniciar fica retido e é respondido quando a parte do mapa de que precisa estiver disponível, então isto funciona:

const map = embed(el, { publishId });
await map.fitLayer({ layerId: "depots" });   // no need to wait for anything first

5. Escute eventos

Nada é enviado até que você peça.

const stop = map.on("featureClick", (feature) => {
  if (!feature) return;                       // a click on empty map
  showDetails(feature.layerName, feature.fields, feature.featureKey);
});

map.on("viewChanged", (view) => {
  if (view.reason === "user") saveLastView(view);   // ignore moves your own code caused
});

stop();   // unsubscribe

Todo evento de estado traz um reason. Veja em Eventos por que isso importa.

6. Trate erros

Um comando rejeitado é um EmbedError com um code estável:

const { EmbedError } = IconMapEmbed;

try {
  await map.setFilters({ region: ["EMEA"], colour: "red" });
} catch (e) {
  if (e instanceof EmbedError && e.code === "undeclared_filter") {
    console.warn("not a filter on this map:", e.detail.undeclared);
  } else {
    throw e;
  }
}

Os códigos estão listados na referência e explicados em Erros e controle de versão.

Comece no lugar certo

Tudo o que você pode alterar em tempo de execução também pode ser definido quando o mapa é montado, para que o leitor nunca veja a vista padrão do autor aparecer primeiro:

embed(el, {
  publishId: "pub_...",
  filters: { region: ["EMEA"] },
  view: { center: [2.35, 48.85], zoom: 9 },
  visibility: { layers: { depots: false } },
  basemapId: "slate",
  theme: { mode: "dark" },
  language: "fr",
  chrome: { legend: false, layerControl: false },
  background: "transparent",
  hyperlinkBehavior: "raiseEvent",
  events: ["featureClick", "linkClicked"],
});

bookmarkId aplica um dos indicadores do autor, e scene restaura uma vista que você capturou antes.

Mapas protegidos por token

Se o autor publicou o mapa exigindo tokens assinados, o seu servidor gera um token de curta duração com o segredo de incorporação da publicação, e a sua página o repassa. O segredo nunca deve chegar ao navegador.

const map = embed(el, {
  publishId: "pub_...",
  getToken: () => fetch("/api/iconmap-token").then((r) => r.text()),
});

map.on("tokenExpiring", async () => {
  await map.setToken({ token: await fetch("/api/iconmap-token").then((r) => r.text()) });
});

getToken é chamado na montagem e sempre que o mapa pede um novo token. tokenExpiring é disparado um minuto antes da expiração, para que você possa enviar um token novo com antecedência.

Se a geração do token for lenta, inicie o download primeiro e forneça o token quando o tiver:

const map = embed(el, { publishId: "pub_...", deferRender: true });   // frame starts loading now
const token = await mintToken();
map.render({ getToken: () => token });

Incorporação para sua organização

Para incorporar um mapa ao vivo para colegas conectados, troque o ponto de entrada. O handle é o mesmo.

const { embedForOrganization } = IconMapEmbed;

const map = embedForOrganization(el, {
  mapItem: { workspaceId: "...", itemId: "..." },
  getToken: (scopes) => myMsal.acquireTokenSilent({ scopes }).then((r) => r.accessToken),
});

getToken recebe os escopos de que o mapa precisa naquele momento (OneLake, Fabric e outros, conforme os dados do mapa) e retorna um token delegado para o usuário conectado. Se você omiti-lo, o mapa faz a própria entrada dentro do frame. Veja Incorporação para sua organização para a configuração completa.

Os sites autorizados a controlar uma incorporação organizacional são definidos no item do mapa, em Publish > Embedding for your organization. Por padrão, são os mesmos sites de Allowed origins da publicação ("Use the allowed origins above"), salvos com o mapa quando você publica, ou com Save origins se você nunca publicar. Desmarque a caixa para manter uma lista separada, por exemplo quando a cópia publicada pode ser incorporada por qualquer site, mas só a sua intranet pode controlar o mapa ao vivo por script.

Em uma incorporação organizacional, os filtros declarados e refreshData não existem (não aparecem em capabilities.commands), highlight responde unsupported_command (use setSelection), e exportImage / print são recusados com denied_capability quando o mapa ou qualquer uma de suas fontes de dados tem um rótulo de confidencialidade.

TypeScript

O pacote npm (em breve, veja a nota acima) inclui seus tipos, de modo que os comandos, seus argumentos e resultados e os payloads dos eventos são todos tipados:

import { embed, type EmbedHandle, type EmbedScene, type EmbedFeatureClick } from "@iconmap/embed";

const map: EmbedHandle = embed(el, { publishId });
const scene = (await map.captureScene()) as EmbedScene;
map.on("featureClick", (f: EmbedFeatureClick | null) => { /* ... */ });

React

Não há wrapper para instalar. O handle cabe em uma ref e em um effect:

function IconMap({ publishId, filters, onFeatureClick }) {
  const el = useRef(null);
  const map = useRef(null);

  useEffect(() => {
    map.current = embed(el.current, { publishId, filters });
    const stop = map.current.on("featureClick", onFeatureClick);
    return () => { stop(); map.current.destroy(); };
  }, [publishId]);

  useEffect(() => { map.current?.setFilters(filters).catch(console.error); }, [filters]);

  return <div ref={el} style={{ height: 600 }} />;
}

Exemplos ao vivo

Os exemplos são mapas ao vivo que você pode usar, cada um seguido da página completa que o produz: câmera, camadas, segmentações, filtros, seleção e realce, cenas e tours, mudança de estilo, comandos de pop-up e eventos.

Solução de problemas

Sintoma Causa
ready é rejeitado com denied_origin A origem da sua página não está nas origens permitidas do mapa, ou o mapa não foi publicado de novo depois que ela foi adicionada. Confira o esquema, o host e a porta exatos.
O frame fica em branco e o console informa uma violação de frame-ancestors A mesma lista de origens permitidas também controla quais sites podem exibir o mapa em um frame. Adicione sua origem e publique de novo.
ready é resolvido, mas commands está quase vazio O autor desativou a Embed API (ou a maioria dos grupos) para esta publicação.
Um comando é rejeitado com denied_capability O grupo desse comando está desativado para este mapa.
Um comando é rejeitado com unsupported_command O mapa, ou este tipo de incorporação, não tem esse comando. Verifique capabilities.commands antes de oferecer o recurso.
Os comandos atingem o tempo limite O frame não carregou. Veja onError e a guia de rede.