App mobile e web
App di spotting, avvisi di voli insoliti, mappe: la chiave resta sul tuo server e l’app chiede a te. Così il consumo dipende da quanto spesso aggiorni, non da quanti utenti hai.
Lo schema
Il tuo server scarica i dati a intervalli fissi e li tiene in memoria; l’app chiede al server solo quello che le serve. Puoi scaricare una zona per ogni area che servi, oppure, con Pro o Business, il mondo intero in una richiesta.
Quanto consuma
Con la cache il conto non dipende dagli utenti. Due esempi, con un aggiornamento ogni 30 secondi:
| Schema | Al mese | Piano |
|---|---|---|
| Il mondo intero in una richiesta | 86.400 | Pro (solo Pro e Business hanno il raggio mondiale) |
| 10 zone da 500 km | 864.000 | Starter (fino a 1.000 km e 1 milione al mese) |
| Ogni utente chiede all’API da sé, 1.000 utenti attivi | 86.400.000 | oltre il Business: da evitare |
Il mondo intero pesa circa 7,2 MB di JSON, 1,1 MB con gzip: non ci sono limiti di banda, contano solo le richieste. Chiedi la compressione (Accept-Encoding: gzip, fetch lo fa da sé).
Il codice
// server.mjs: una cache per tutti gli utenti della tua app (Node 18+, nessuna dipendenza)
import http from 'node:http';
const KEY = process.env.FIA_API_KEY;
// Pro e Business: il mondo intero. Con Free usa la tua zona (fino a 500 km).
const SOURCE = 'https://api.flyitalyadsb.com/v2/lat/0/lon/0/dist/20000';
const ATTRIBUTION = 'Dati di volo: FlyItalyADSB (flyitalyadsb.com), CC BY-SA 4.0';
let snapshot = { now: 0, ac: [] };
async function refresh() {
try {
const r = await fetch(SOURCE, { headers: { 'X-Api-Key': KEY, 'User-Agent': 'my-app-backend/1.0' } });
if (r.ok) snapshot = await r.json(); // fetch decomprime il gzip da solo
else console.log('refresh', r.status, r.headers.get('retry-after') ?? '');
} catch (e) {
console.log('refresh failed', e.message); // resta l’ultima copia
}
}
refresh();
setInterval(refresh, 30_000);
const km = (la1, lo1, la2, lo2) => {
const p = Math.PI / 180;
const h = Math.sin(((la2 - la1) * p) / 2) ** 2
+ Math.cos(la1 * p) * Math.cos(la2 * p) * Math.sin(((lo2 - lo1) * p) / 2) ** 2;
return 12742 * Math.asin(Math.sqrt(h));
};
// GET /near?lat=40.64&lon=-73.78&km=25
http.createServer((req, res) => {
const q = new URL(req.url, 'http://localhost').searchParams;
const lat = Number(q.get('lat')), lon = Number(q.get('lon'));
const radius = Math.min(Number(q.get('km')) || 25, 250);
const ac = snapshot.ac.filter((a) => a.lat != null && km(lat, lon, a.lat, a.lon) <= radius);
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=5' });
res.end(JSON.stringify({ now: snapshot.now, attribution: ATTRIBUTION, ac }));
}).listen(8080); // Nell’app: chiedi al tuo server, mai all’API con la chiave
const res = await fetch('https://tuo-server.example/near?lat=40.64&lon=-73.78&km=25');
const { ac, attribution } = await res.json();
// mostra «attribution» dove l’utente vede gli aerei Al posto di LA_TUA_CHIAVE la tua chiave: è gratuita, si chiede qui. Specifica completa in openapi.json.
Funzioni in più per un’app
- Storico: la traccia di un aereo in un giorno, anche oggi, con
/v2/history/{hex}/{data}. Storico. - Sorvoli: gli aerei passati sopra un punto tra due istanti, con
/v2/overflights. - Traffico notevole: emergenze, militari e altri aerei notevoli in una zona con
/v2/events, e con i piani a pagamento gli stessi eventi in push sul tuo URL. Avvisi e webhook. - Profilo di un aereo in una richiesta: identità, se lo vediamo ora, primo e ultimo giorno nello storico, con
/v2/profile/{hex}.
Attenzione a
- Mai la chiave nell’app o nella pagina web. Il CORS è aperto, ma una chiave nel codice del client la vede chiunque.
- Aggiornare più spesso di ogni 2 secondi non serve: il dato non cambia più velocemente.
- L’attribuzione nell’app, dove l’utente vede gli aerei: la riga da usare. Il server dell’esempio la restituisce insieme ai dati.
- Su un 429 tieni l’ultima copia e riprova dopo
Retry-After: gli utenti non se ne accorgono. - Non ci sono tratte né foto nei dati: se l’app mostra partenza e arrivo, serve un’altra fonte.