PerfectVector
Por Irene Kim

Marcadores SVG de Leaflet: alinea la punta con iconAnchor

Coloca marcadores SVG personalizados en Leaflet asignando la punta visible a iconAnchor. Ten en cuenta el margen del viewBox, la relación de aspecto y los cambios de tamaño.

10 min de lectura
En esta página

Para un marcador SVG personalizado de Leaflet, establece iconSize con las dimensiones de la imagen mostrada e iconAnchor con la posición en píxeles de la punta visible dentro de esa imagen. Mide desde la esquina superior izquierda de la imagen. Los márgenes transparentes del SVG cuentan en esas dimensiones, así que usar la mitad del ancho y toda la altura puede no coincidir con un pin asimétrico o con margen.

Esta guía usa un pin original cuya punta está a la derecha del centro de su cuerpo. Comparamos dos marcos de origen en dos tamaños de icono en un mapa de navegador sin teselas. Si ya tienes un diseño SVG limpio, úsalo directamente; colocar el anclaje no requiere vectorización.

Separa el diseño, el recuadro de imagen y el punto del mapa

Aquí se encuentran tres sistemas de coordenadas. El trazado SVG describe el diseño en unidades de origen. iconSize establece su recuadro de imagen mostrado en píxeles. Leaflet sitúa el punto de ese recuadro especificado por iconAnchor en la ubicación geográfica del marcador. La referencia de Icon define estas opciones y el origen del anclaje en la esquina superior izquierda.

Un pin convencional puede llegar al centro del borde inferior, pero tu diseño puede terminar en otro lugar. El valor predeterminado documentado de Leaflet es un anclaje centrado cuando se especifica un tamaño. Los valores de centro inferior de nuestro experimento son una suposición explícita que probamos, no ese valor predeterminado.

El tutorial oficial de iconos personalizados muestra cómo pasar una instancia de L.icon a un marcador. El trabajo adicional con un diseño importado es localizar el punto dentro de su imagen que debe tocar la coordenada del mapa.

La punta visible y el anclaje
Comparación conceptual de un pin de ubicación asimétrico dentro de un lienzo con margen, con la punta separada de una cruz de referencia y después coincidiendo con ella
Ilustración: ten en cuenta el marco completo de la imagen al colocar la punta visible. El experimento medido en el navegador que se muestra abajo usa archivos SVG creados por separado.

Asigna la punta de origen a píxeles del icono

La punta de nuestro pin está en la coordenada de origen (40, 80). Su marco ajustado es viewBox="0 0 60 80". La versión con margen solo cambia ese marco por viewBox="-20 -10 100 120"; ambos archivos conservan el mismo trazado y la abertura transparente.

La referencia de viewBox describe este rectángulo de origen. El espacio vacío dentro de él viaja con el diseño. Nuestra guía del cuadro delimitador SVG explica por qué un lienzo y una forma pintada pueden tener límites diferentes.

Ambas versiones usan explícitamente preserveAspectRatio="xMidYMid meet". Esto encaja el marco de manera uniforme dentro del viewport y centra el espacio sobrante. Consulta la referencia de preserveAspectRatio de MDN para esos ajustes.

Para este modo de ajuste, sean W y H las dimensiones del icono, (minX, minY, vbW, vbH) el viewBox y (tipX, tipY) la punta de origen:

s = min(W / vbW, H / vbH)
offsetX = (W - vbW * s) / 2
offsetY = (H - vbH * s) / 2
anchorX = (tipX - minX) * s + offsetX
anchorY = (tipY - minY) * s + offsetY

Para el icono con margen de 60×80, s es 0.6. El marco de origen de 100×120 ocupa 60×72 píxeles y deja cuatro píxeles arriba y abajo. La punta se convierte en [36, 58], incluido ese desplazamiento vertical. Tomar simplemente una fracción de cada dimensión por separado omitiría el paso de ajuste uniforme.

Marco de origenTamaño del iconoAnclaje de punta calculado
Ajustado, 0 0 60 8060×80[40, 80]
Ajustado, 0 0 60 8090×120[60, 120]
Con margen, -20 -10 100 12060×80[36, 58]
Con margen, -20 -10 100 12090×120[54, 87]

Estos cálculos se aplican a nuestra imagen sin transformar y al ajuste meet centrado explícito. Otra alineación, slice o none necesitan su asignación de coordenadas correspondiente. Nuestra guía de relación de aspecto cubre esas opciones. Las transformaciones CSS adicionales en la imagen del marcador también necesitan una inspección separada.

Comprueba la colocación en un mapa sin teselas

Usamos Leaflet 1.9.4, la versión estable identificada en la página oficial de descargas. El experimento carga localmente su JavaScript y CSS de versión fija y no añade ninguna capa de teselas. Cada mapa proyecta la coordenada [0, 0] en una cruz azul; muestra una imagen SVG junto a ese punto de referencia.

La inspección del origen encontró un trazado compuesto idéntico y ninguna imagen ráster incrustada en cada SVG. Las dimensiones de las imágenes en el navegador fueron 60×80 y 90×120 píxeles, como se solicitó. Usando el rectángulo observado de cada imagen, comparamos la posición de la punta asignada con el punto geográfico proyectado:

OrigenTamaño del iconoError con el anclaje supuesto en el centro inferiorError con el anclaje de punta calculado
Ajustado60×8010 px a la derecha0 px
Ajustado90×12015 px a la derecha0 px
Con margen60×806 px a la derecha, 22 px arriba0 px
Con margen90×1209 px a la derecha, 33 px arriba0 px

La cruz también coincidió visualmente con las puntas corregidas. Son observaciones de este archivo de prueba creado por nosotros en un navegador Chrome, no una garantía para todos los SVG, navegadores o integraciones de mapas. El pin con margen sigue siendo más pequeño dentro del mismo recuadro de imagen después de corregir el anclaje. La posición y el tamaño del diseño son decisiones separadas.

Copia una plantilla de marcador funcional

Guarda este HTML completo como marker.html y sírvelo desde un servidor web local. Carga los archivos Leaflet de versión fija desde el CDN enlazado por la página de descargas de Leaflet. El SVG se codifica en una URL de imagen, así que el ejemplo no necesita un archivo de icono ni un servicio de teselas. Hace falta acceso a Internet para esos dos archivos de la biblioteca.

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Leaflet SVG tip anchor</title>
<link rel="stylesheet"
      href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<style>
  #map { width: 360px; height: 300px; background: #edf3ef; }
  .cross { position: absolute; z-index: 1000; pointer-events: none;
    width: 24px; height: 24px; transform: translate(-50%, -50%); }
  .cross::before, .cross::after { content: ""; position: absolute;
    background: #006cbe; }
  .cross::before { top: 12px; width: 24px; height: 1px; }
  .cross::after { left: 12px; width: 1px; height: 24px; }
</style>
<div id="map"></div>
<pre id="status">Loading marker…</pre>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="60" height="80" viewBox="-20 -10 100 120" preserveAspectRatio="xMidYMid meet"><path fill="#bf593c" fill-rule="evenodd" d="M30 0 C46 0 60 12 60 28 C60 46 46 54 40 80 C30 62 0 46 0 28 C0 12 14 0 30 0 Z M40 26 A10 10 0 1 0 20 26 A10 10 0 1 0 40 26 Z"/></svg>`;
const size = [60, 80];
const viewBox = [-20, -10, 100, 120];
const sourceTip = [40, 80];
const scale = Math.min(size[0] / viewBox[2], size[1] / viewBox[3]);
const anchor = [
  (sourceTip[0] - viewBox[0]) * scale
    + (size[0] - viewBox[2] * scale) / 2,
  (sourceTip[1] - viewBox[1]) * scale
    + (size[1] - viewBox[3] * scale) / 2
];
const map = L.map("map", {
  zoomControl: false, attributionControl: false,
  dragging: false, scrollWheelZoom: false, doubleClickZoom: false,
  boxZoom: false, keyboard: false, touchZoom: false
}).setView([0, 0], 2);
const position = [0, 0];
const icon = L.icon({
  iconUrl: "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg),
  iconSize: size,
  iconAnchor: anchor
});
const marker = L.marker(position, { icon }).addTo(map);
const container = document.querySelector("#map");
const cross = document.createElement("span");
cross.className = "cross";
container.append(cross);
const point = map.latLngToContainerPoint(position);
cross.style.left = point.x + "px";
cross.style.top = point.y + "px";
(async () => {
  const image = marker.getElement();
  await image.decode();
  const ir = image.getBoundingClientRect();
  const mr = container.getBoundingClientRect();
  const displacement = [
    ir.left + anchor[0] - mr.left - point.x,
    ir.top + anchor[1] - mr.top - point.y
  ];
  document.querySelector("#status").textContent = JSON.stringify({
    version: L.version, size, anchor,
    observedImageSize: [ir.width, ir.height], displacement
  }, null, 2);
})();
</script>
</html>

El estado mostrado debería informar anchor: [36, 58], tamaño de imagen [60, 80] y desplazamiento cero. Cambia size a [90, 120]; el mismo cálculo da [54, 87]. Para usar el marco ajustado, cambia tanto el viewBox del SVG como el arreglo viewBox de JavaScript a 0 0 60 80. Mantén la coherencia entre ambos.

El trazado incluye un contorno interior circular con fill-rule="evenodd", que crea una abertura real. La referencia de fill-rule de MDN explica la regla. El ejemplo no añade borde ni transformación al contenedor del mapa; hacerlo requeriría cuidado al comparar rectángulos del viewport con coordenadas del contenedor.

Inspecciona el destino antes de cambiar el diseño

Si el marcador sigue sin coincidir con su coordenada, revisa esto en orden:

  1. Confirma que la imagen SVG haya cargado y que el elemento del marcador tenga el ancho y alto en píxeles previstos.
  2. Localiza la punta de origen real, incluidas las transformaciones aplicadas dentro del SVG.
  3. Revisa el viewBox completo, sus coordenadas mínimas y su modo de ajuste.
  4. Recalcula el anclaje cada vez que cambies el marco o el tamaño del icono.
  5. Elimina las sobrescrituras CSS sin relación mientras pruebas y vuelve a revisar el estilo final y los tamaños de visualización previstos.

Mantén fija la coordenada geográfica durante esta comprobación. Moverla para compensar el margen de imagen ocultaría el error de colocación del diseño.

Este es un flujo de marcadores de navegador. Un marcador SVG de QGIS usa sus propios controles de destino y sintaxis de pintura parametrizada; conserva un maestro SVG normal para la entrega general en navegador.

Si tu icono solo existe como PNG o JPG y necesitas contornos editables, convierte el diseño con PerfectVector, inspecciona la abertura y la punta, y guarda un maestro SVG limpio. Después calcula su anclaje de Leaflet a partir del marco de origen final. La vectorización prepara el diseño; tu código del mapa proporciona las ubicaciones y la colocación. Si el único problema es un desplazamiento en un SVG existente, corrige su marco o su anclaje directamente. La guía de vectorización de imágenes explica cuándo resulta útil convertir un origen ráster.

Preguntas frecuentes

¿iconAnchor es una latitud y una longitud? No. Es una posición en píxeles dentro de la imagen del icono mostrada, medida desde su esquina superior izquierda. La posición geográfica del marcador se proporciona por separado.

¿Por qué mi SVG con margen sigue siendo pequeño después de corregir su anclaje? El marco de origen vacío sigue ocupando parte del recuadro de imagen. Corregir el anclaje coloca la punta; no elimina ese espacio ni amplía la forma pintada.

¿Debo usar siempre la mitad del ancho y toda la altura? Usa ese par solo cuando el punto visible de unión esté realmente ahí. Nuestro pin asimétrico y nuestro origen con margen necesitan valores distintos, aunque ambos sean diseños de marcador de ubicación.

¿Convertir un PNG a SVG corrige la ubicación del marcador? La conversión puede proporcionar un diseño editable. El marco de origen final, las dimensiones mostradas, el anclaje y las coordenadas del mapa siguen determinando la colocación del marcador.

Fuentes

  1. Leaflet — Referencia de Icon — Dimensiones en píxeles, origen del anclaje, centrado predeterminado y alineación geográfica.
  2. Leaflet — Marcadores con iconos personalizados — Creación de un icono y asignación a un marcador.
  3. Leaflet — Descarga — Versión estable y sus enlaces JavaScript y CSS de versión fija.
  4. MDN — viewBox — Rectángulo de coordenadas de origen usado en los dos marcos.
  5. MDN — preserveAspectRatio — Ajuste uniforme y espacio sobrante centrado con xMidYMid meet.
  6. MDN — fill-rule — Regla par-impar usada para la abertura transparente.

Más del blog

PerfectVector

Empieza con un SVG más limpio
y fácil de editar

Sin tarjeta de crédito