GNSS seed data
Ready-made orbit data for GPS, Galileo, BeiDou, GLONASS and QZSS. Load it into a GNSS receiver and it finds its first fix from cold in seconds rather than minutes. Free to use, with no account and no key.
Built 2 minutes ago from the BKG live stream. The next build is due in about 8 minutes.
- 32 GPS
- 30 Galileo
- 36 BeiDou
- 24 GLONASS
- 6 QZSS
Using one of our altimeters? There is nothing to set up. Our altimeters download this data automatically over the air; you do not need to configure anything on this page. It is published here purely so anyone can use the same data in their own projects and devices.
Choose a file
Every file is one frame per satellite, in three encodings. Pick the systems your receiver tracks and the encoding your device can unpack. Both http:// and https:// work on every link, for devices without TLS.
| File | Satellites | Raw | zlib | LZMA |
|---|---|---|---|---|
| Bundles, whole or in 1000-byte chunks | ||||
| gnss_ | 128 | .bin10.0 KB | .z7.3 KB | .lzma6.5 KB |
| gnss_ | 122 | .bin9.5 KB | .z7.0 KB | .lzma6.2 KB |
| gnssglobalGPS, Galileo, BeiDou | 98 | .bin8.2 KB | .z6.0 KB | .lzma5.3 KB |
| gnss_ | 56 | .bin3.7 KB | .z2.8 KB | .lzma2.6 KB |
| gnss_ | 62 | .bin4.8 KB | .z3.7 KB | .lzma3.4 KB |
| One system each, to make your own mix | ||||
| gnss_ | 32 | .bin2.4 KB | .z1.9 KB | .lzma1.8 KB |
| gnss_ | 30 | .bin2.5 KB | .z1.8 KB | .lzma1.7 KB |
| gnss_ | 36 | .bin3.4 KB | .z2.4 KB | .lzma2.0 KB |
| gnss_ | 24 | .bin1.3 KB | .z1009 B | .lzma949 B |
| gnss_ | 6 | .bin456 B | .z401 B | .lzma378 B |
Getting it into a receiver
- Pick a file from the table, or read index.json to find one in code.
- Download it whole, or fetch its chunks .0, .1, .2 in order and join them. The last chunk is under 1000 bytes; a 404 also means you have them all.
- Unpack it. zlib with any inflate, LZMA with an LZMA-alone decoder. Raw needs nothing.
- Send the receiver the time (MGA-INI-TIME-UTC) and a rough position, then every frame just as it is. Each frame starts B5 62 and carries one satellite.
- Watch the MGA-ACK replies if you have turned them on. An ACK says the frame was stored, not that the orbit inside was checked.
Cache busting. These folders are served with caching turned off, so a download should always be the current build. To be certain nothing along the way, such as a proxy or a mobile network’s cache, hands you an old copy, add a random number or the Unix time to the URL, as in gnss_all.lzma?1791849600, and the same on each chunk. The server ignores it.
Working code for a u-blox SAM-M10Q, for an ESP32 or a PC, is on the Example code tab.
Formats
Encodings
| .bin | The frames, uncompressed. |
| .z | zlib (RFC 1950): 2-byte header, DEFLATE, Adler-32 check. |
| .lzma | LZMA-alone, as xz --format=lzma writes: 5 bytes of properties (64 KB dictionary), the 8-byte length, then the data. The smallest. |
Frames
| GPS | MGA-GPS-EPH, 76 bytes |
| Galileo | MGA-GAL-EPH, 84 bytes |
| BeiDou | MGA-BDS-EPH, 96 bytes |
| GLONASS | MGA-GLO-EPH, 56 bytes |
| QZSS | MGA-QZSS-EPH, 76 bytes |
GLONASS orbit data is good for about 15 minutes either side of its time, so it helps most from a fresh download. It comes from the live streams only: a build made from a fallback file has none.
For programs
Each build writes an index of every file, so code never has to guess a name or a chunk count. The GNSS Seed Data API page sets it all out, with an example.
- altimetercloud.com/ubx/index.jsonEvery bundle and single-system file: what is in it, the satellites of each system, sizes, chunk counts and the URL of every file and chunk.
- altimetercloud.com/ubx/currenturls.jsonThe gnssglobal bundle on its own, in the form devices already read. currenturls.txt has the same as KEY=VALUE lines.
Data credit. Broadcast ephemeris from the IGS Real-Time Service, by the International GNSS Service (IGS) and its contributing organizations: the merged broadcast ephemeris streams of BKG (Federal Agency for Cartography and Geodesy, Germany), CAS (Chinese Academy of Sciences) and JPL (NASA Jet Propulsion Laboratory), served on BKG’s Ntrip caster, with BKG’s broadcast navigation files as the fallback. If you use these files, please acknowledge the IGS and its contributing organizations too. Land outlines in the picture from Natural Earth.
Terms of use. The underlying ephemeris is public broadcast navigation data. We rebuild and host it as a convenience and place our packaging in the public domain, so you may use these files freely in your own projects, commercial or otherwise. It is provided as-is with no guarantee of availability or accuracy; do not rely on it as a sole safety-critical input. Please fetch on a sensible schedule: the data only changes every 10 minutes, so there is no reason to poll faster than that.
Example: loading it into a u-blox SAM-M10Q
Two complete programs that load the latest data into a SAM-M10Q over its serial port: one for an ESP32 under Arduino, one for a PC or Raspberry Pi in Python. Each quietens the receiver’s NMEA, gives it the time, then sends every frame and waits for the receiver to acknowledge it before sending the next. They follow the same steps as the code in our own Jupiter flight computer, which uses a SAM-M10Q.
Other receivers will differ. These are written for the SAM-M10Q at its factory settings, 9600 baud. Other u-blox receivers take the same messages, but their default baud rate, the systems they track and how quickly they take messages vary, so check your receiver’s manual. Receivers from other makers use their own assistance formats and can’t use these files.
// Load AltimeterCloud GNSS seed data into a u-blox SAM-M10Q from an ESP32 (Arduino).
// The receiver is on Serial2 at 9600 baud, the SAM-M10Q's factory default. WiFi is needed
// for the download, and NTP for the time the receiver must be given before the orbits.
#include <WiFi.h>
#include <HTTPClient.h>
#include <time.h>
const char* SSID = "your-wifi";
const char* PASS = "your-password";
const char* URL = "http://altimetercloud.com/ubx/gnss_all.bin"; // all five systems, about 10 KB
HardwareSerial& gnss = Serial2; // receiver TX to the ESP32's RX2, and back
// Send one UBX message: sync, class, id, length, payload, then the Fletcher checksum
void ubxSend(uint8_t cls, uint8_t id, const uint8_t* pl, uint16_t len) {
uint8_t hdr[6] = {0xB5, 0x62, cls, id, (uint8_t)(len & 0xFF), (uint8_t)(len >> 8)};
uint8_t a = 0, b = 0;
for (int i = 2; i < 6; i++) { a += hdr[i]; b += a; }
for (int i = 0; i < len; i++) { a += pl[i]; b += a; }
gnss.write(hdr, 6); gnss.write(pl, len); gnss.write(a); gnss.write(b);
gnss.flush(); // wait until it has all gone out
}
// Wait for UBX-MGA-ACK-DATA0 (0x13 0x60). Returns 1 accepted, 0 refused, -1 nothing heard
int waitAck(uint32_t ms) {
uint8_t m[16]; int n = 0; uint32_t t0 = millis();
while (millis() - t0 < ms) {
if (!gnss.available()) { delay(1); continue; }
uint8_t c = gnss.read();
if (n == 0 && c != 0xB5) continue; // skips the NMEA in between
if (n == 1 && c != 0x62) { n = (c == 0xB5); continue; }
m[n++] = c;
if (n == 4 && !(m[2] == 0x13 && m[3] == 0x60)) { n = 0; continue; }
if (n == 16) return m[6]; // m[8] holds the reason for a refusal
}
return -1;
}
void setup() {
Serial.begin(115200);
gnss.begin(9600);
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) delay(200);
configTime(0, 0, "pool.ntp.org");
struct tm u;
while (!getLocalTime(&u)) delay(200); // UTC, to the second
// 1. Download the bundle into RAM
HTTPClient http;
http.begin(URL);
if (http.GET() != 200) { Serial.println("Download failed"); return; }
int len = http.getSize();
if (len <= 0) { Serial.println("No length from the server"); return; }
uint8_t* buf = (uint8_t*)malloc(len);
if (!buf) { Serial.println("Not enough memory"); return; }
WiFiClient* s = http.getStreamPtr();
int got = 0; uint32_t t0 = millis();
while (got < len && millis() - t0 < 10000) {
if (s->available()) got += s->readBytes(buf + got, len - got); else delay(1);
}
http.end();
// 2. Quieten the NMEA while we talk (CFG-UART1OUTPROT-NMEA off), and ask for an
// acknowledgement of each assistance message (CFG-NAVSPG-ACKAIDING on), both in RAM only
const uint8_t nmeaOff[] = {0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x74, 0x10, 0x00};
const uint8_t ackOn[] = {0x00, 0x01, 0x00, 0x00, 0x25, 0x00, 0x11, 0x10, 0x01};
ubxSend(0x06, 0x8A, nmeaOff, sizeof nmeaOff); delay(100);
ubxSend(0x06, 0x8A, ackOn, sizeof ackOn); delay(100);
while (gnss.available()) gnss.read();
// 3. The time first (UBX-MGA-INI-TIME_UTC): without it the orbits are refused
uint8_t t[24] = {0x10, 0x00, 0x00, 0x80}; // no time pulse reference, leap seconds unknown
uint16_t y = u.tm_year + 1900;
t[4] = y & 0xFF; t[5] = y >> 8; t[6] = u.tm_mon + 1; t[7] = u.tm_mday;
t[8] = u.tm_hour; t[9] = u.tm_min; t[10] = u.tm_sec;
t[16] = 2; // good to about 2 seconds
ubxSend(0x13, 0x40, t, sizeof t);
waitAck(500);
// 4. Every frame just as it is, one at a time, each answered before the next
int ok = 0, refused = 0, silent = 0;
for (int i = 0; i + 8 <= got; ) {
if (buf[i] != 0xB5 || buf[i + 1] != 0x62) break;
uint16_t n = buf[i + 4] | (buf[i + 5] << 8);
if (i + 8 + n > got) break;
gnss.write(buf + i, n + 8);
gnss.flush();
int r = waitAck(300);
if (r == 1) ok++; else if (r == 0) refused++; else silent++;
i += n + 8;
}
free(buf);
const uint8_t nmeaOn[] = {0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x74, 0x10, 0x01};
ubxSend(0x06, 0x8A, nmeaOn, sizeof nmeaOn); // NMEA back on
Serial.printf("%d accepted, %d refused, %d not answered\n", ok, refused, silent);
}
void loop() {}# Load AltimeterCloud GNSS seed data into a u-blox SAM-M10Q from a PC or Raspberry Pi.
# pip install pyserial requests
import datetime, lzma, struct, time
import requests, serial
PORT, BAUD = "/dev/ttyUSB0", 9600 # the SAM-M10Q's factory default
index = requests.get("https://altimetercloud.com/ubx/index.json", timeout=10).json()
bundle = next(x for x in index["bundles"] if x["name"] == "gnss_all")
data = lzma.decompress(requests.get(bundle["lzma"]["url"], timeout=10).content, format=lzma.FORMAT_ALONE)
def ubx(cls, mid, payload):
"""One UBX message: sync, class, id, length, payload, Fletcher checksum."""
body = bytes([cls, mid]) + struct.pack("<H", len(payload)) + payload
a = b = 0
for c in body:
a = (a + c) & 0xFF
b = (b + a) & 0xFF
return b"\xb5\x62" + body + bytes([a, b])
def wait_ack(port, secs=0.3):
"""UBX-MGA-ACK-DATA0: 1 accepted, 0 refused, None nothing heard."""
end, buf = time.time() + secs, b""
while time.time() < end:
buf += port.read(port.in_waiting or 1)
k = buf.find(b"\xb5\x62\x13\x60")
if k >= 0 and len(buf) >= k + 16:
return buf[k + 6] # buf[k + 8] holds the reason for a refusal
return None
with serial.Serial(PORT, BAUD, timeout=0.05) as gps:
# quieten the NMEA while we talk (CFG-UART1OUTPROT-NMEA off), and acknowledge
# each assistance message (CFG-NAVSPG-ACKAIDING on), both in RAM only
def valset(key, value):
gps.write(ubx(0x06, 0x8A, bytes([0, 1, 0, 0]) + struct.pack("<IB", key, value)))
gps.flush()
time.sleep(0.1)
valset(0x10740002, 0)
valset(0x10110025, 1)
gps.reset_input_buffer()
# the time first (UBX-MGA-INI-TIME_UTC): without it the orbits are refused
now = datetime.datetime.now(datetime.timezone.utc)
t = struct.pack("<BBBbHBBBBBBIHHI", 0x10, 0, 0, -128, now.year, now.month, now.day,
now.hour, now.minute, now.second, 0, now.microsecond * 1000, 1, 0, 0)
gps.write(ubx(0x13, 0x40, t))
gps.flush()
wait_ack(gps, 0.5)
# every frame just as it is, one at a time, each answered before the next
counts = {1: 0, 0: 0, None: 0}
i = 0
while i + 8 <= len(data):
n = struct.unpack_from("<H", data, i + 4)[0]
gps.write(data[i:i + 8 + n])
gps.flush()
counts[wait_ack(gps)] += 1
i += 8 + n
valset(0x10740002, 1) # NMEA back on
print(counts[1], "accepted,", counts[0], "refused,", counts[None], "not answered")Which file
Out of the box the SAM-M10Q tracks GPS, Galileo, GLONASS and BeiDou, with QZSS and SBAS, so gnss_all matches it. If you have turned systems off, pick the bundle that matches what is left; frames for a system it isn’t tracking are wasted.
The time comes first
Until it knows the time to within a few seconds, the receiver refuses every orbit. NTP, a phone or a real-time clock is plenty. A rough position as well (UBX-MGA-INI-POS_LLH) saves a little more, but only send one whose stated accuracy truly covers where you are: a position that is wrong for the accuracy it claims stops the receiver finding satellites, which is worse than sending none.
Reading the replies
Each UBX-MGA-ACK-DATA0 carries 1 for accepted or 0 for refused, and a reason: 1 no time yet, 2 version not supported, 3 wrong length, 4 couldn’t store it, 5 not ready, 6 unknown type. Accepted means stored, not that the orbit was checked.
Download and speed
The raw file is about 10 KB, small enough for an ESP32’s RAM. For less time on the radio, fetch the .lzma (about two thirds the size) and unpack it with an LZMA-alone decoder; with a small HTTP buffer, fetch it in its 1000-byte chunks. At 9600 baud a full load takes about 15 seconds; a faster port rate (CFG-UART1-BAUDRATE) cuts that.
u-blox and SAM-M10Q are trademarks of u-blox AG. AltimeterCloud is not connected with u-blox; the examples are offered as they are, for you to adapt.



















