En este artículo vamos a crear un visor web sencillo con MapLibre GL JS que muestre datos en tiempo real geolocalizados. Para ello he elegido la API pública de OpenSky Network, que proporciona datos ADS-B de aviones a nivel mundial, aunque podríamos buscar cualquier otra fuente datos, como datos atmosféricos, estado del tráfico, etc. Un dato importante a tener en cuenta, es que esta API tiene limitaciones en cuanto a frecuencia y volumen de peticiones. Si queremos, podemos crear una cuenta de usuario y ampliar dichos límites, pero nos servirá perfectamente para ilustrar el ejemplo.
Crear nuestro mapa y cargar los datos
El primer paso es inicializar un mapa básico con MapLibre GL JS y añadir una fuente GeoJSON que se actualizará con los datos obtenidos de OpenSky. Para simplificar la visualización, representaremos cada avión como un círculo azul en el mapa.
Para una prueba rápida, podemos utilizar estos estilos y el CDN de MapLibre en la cabecera del documento html:
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#map {
width: 100%;
height: 100%;
}
</style>
<script src='https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js'></script>
<link href='https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css' rel='stylesheet' />// Crear el mapa
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [0, 20],
zoom: 2
});
// Cargar datos de vuelos
async function loadFlights() {
// ¡La API pública tiene un límite de peticiones!
const res = await fetch('https://opensky-network.org/api/states/all');
const data = await res.json();
// Transformar a GeoJSON
const features = data.states
.filter(d => d[5] !== null && d[6] !== null) // has lat/lon
.map(d => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [d[5], d[6]] },
properties: {
callsign: d[1],
origin_country: d[2],
velocity: d[9],
heading: d[10]
}
}));
const geojson = { type: 'FeatureCollection', features };
// Actualizar o agregar fuente
if (map.getSource('flights')) {
map.getSource('flights').setData(geojson);
} else {
map.addSource('flights', { type: 'geojson', data: geojson });
// Layer de círculos (Representación de los aviones)
map.addLayer({
id: 'planes',
type: 'circle',
source: 'flights',
paint: {
// Escalar tamaño del círculo con el zoom
'circle-radius': [
'interpolate', ['linear'], ['zoom'],
4, 3,
8, 5,
12, 7
],
'circle-color': '#1e90ff',
'circle-stroke-color': '#ffffff',
'circle-stroke-width': 1
}
});
}
}
map.on('load', async () => {
// Cargar datos
await loadFlights();
// Actualizar cada 30s
setInterval(loadFlights, 30000);
});En este punto ya tenemos un mapa base y sobre él círculos que representan la posición actual de los vuelos detectados de forma mundial y actualizamos dicha capa cada 30 segundos. Este valor podría ser inferior, pero como se ha comentado al principio no queremos alcanzar los límites ni abusar de una API pública.

Vale, esto está muy bien, pero ¿tampoco nos dice mucho verdad?, lo interesante es que podemos seguir ampliando el visor con información o acciones adicionales.
Mostrar información adicional con etiquetas y ventanas emergentes
Podemos enriquecer el visor mostrando el identificador de vuelo (callsign) junto a cada punto y añadiendo un popup con información extra al hacer clic. Esto permite que el mapa no solo muestre posiciones, sino también detalles útiles para el usuario.
map.addLayer({
id: 'flights-labels',
type: 'symbol',
source: 'flights',
layout: {
'text-field': ['get', 'callsign'],
'text-size': 10,
'text-offset': [0, 1.2]
},
paint: {
'text-halo-color': '#ffffff',
'text-halo-width': 1
}
});Ahora podemos ver los puntos junto con su identificador.

Y por último, permitamos inspeccionar algunos atributos del vuelo al pulsar sobre cada punto:
map.on('click', 'planes', (e) => {
const p = e.features[0].properties;
new maplibregl.Popup()
.setLngLat(e.lngLat)
.setHTML(`
<strong>${p.callsign}</strong><br/>
País: ${p.origin_country}<br/>
Velocidad: ${Number(p.velocity_ms || 0).toFixed(0)} m/s
`)
.addTo(map);
});
map.on('mouseenter', 'planes', () => map.getCanvas().style.cursor = 'pointer');
map.on('mouseleave', 'planes', () => map.getCanvas().style.cursor = '');Con estas pocas líneas el visor ya muestra los vuelos en tiempo real (cada 30s), permite identificarlos fácilmente en el mapa y consultar su información básica.

En definitiva, esto no se queda aquí, podemos adicionalmente aprovecharnos del atributo «heading» y reemplazar el punto, por un icono de un avión y rotarlo para representar visualmente la dirección actual en la que se dirige el avión (Esta idea la dejo como ejercicio para el lector).
¿Quieres comentarnos algo? Adelante!