FlatGeobuf (FGB) — Streaming Binary GIS Format: Internals, R-tree, HTTP Range Requests and OSM Export
FlatGeobuf is a binary streaming GIS format that emerged from Bjorn Harrtell's proposal on the gdal-dev mailing list in December 2018. The format lives in the flatgeobuf/flatgeobuf GitHub repository, uses Google FlatBuffers as its underlying serialization, packs a Hilbert R-tree at the tail of the file, and reads through HTTP range requests — the client downloads only those bytes that intersect a requested bbox. It is not an OGC standard or ISO specification, but in six years FGB has become a de-facto cloud-native choice for large vector datasets: QGIS 3.16+ opens a 1 GB FGB file faster than the equivalent Shapefile, and a browser app using the flatgeobuf npm package reads only the visible rectangle from a CDN-hosted file.

History: a December 2018 proposal and six years of evolution
Bjorn Harrtell is a Stockholm-based developer who in the 2010s worked extensively on WFS clients and GDAL drivers. In December 2018 he sent a message to gdal-dev titled "proposal: FlatGeobuf — flat geospatial binary format" describing the idea: a binary format using Google FlatBuffers as backing (zero-copy reads, no parsing overhead vs Protocol Buffers), with a packed Hilbert R-tree at the file tail for spatial filtering. By spring 2019 a working driver appeared in GDAL, by summer GeoPandas had FGB support via pyogrio, and GDAL 3.1 (July 2020) made FlatGeobuf part of the standard build.
The format never sought OGC formalization or IANA media type registration — that's Harrtell's deliberate stance: standardization would slow iteration. Instead the community keeps clean reference implementations in C/C++/Java/JS/Python/Go/Rust, each natively handling FlatBuffers schema evolution. The current spec version is the 4th (implicit, via repository commits) with no dedicated CHANGELOG.md, but breaking changes get filed as issues and discussed openly.
By 2026 FGB lives across every professional GIS stack: GDAL/OGR 3.1+, QGIS 3.16+, GeoServer, MapServer, MapLibre GL JS via the flatgeobuf npm plugin, DeckGL, Felt, Microsoft Planetary Computer. The Overture Maps Foundation — a consortium of TomTom, Microsoft, Meta and AWS — publishes its vector layers (places, transportation, admins) simultaneously in GeoParquet and FlatGeobuf.
What's inside an FGB file
A FlatGeobuf file starts with an 8-byte magic: 0x66 0x67 0x62 0x03 0x66 0x67 0x62 0x00 — the letters "fgb" plus a version marker. Then comes a header (a FlatBuffers-serialized Header object) with metadata: column names, attribute types, CRS (as EPSG code), the collection's bbox, geometry_type, features_count. After the header sits an optional packed Hilbert R-tree, whose nodes are stored in Hilbert space-filling curve order. This gives two properties at once: bbox filtering runs in O(log n), and during sequential reads from disk or HTTP stream, neighbors on the Hilbert curve sit close together — CPU caches and CDN caches work efficiently.
The file body is a sequence of FlatBuffers Feature objects. Each feature carries geometry (points, polylines, polygons, MultiPolygon — including SQL/MM Part 3 circular interpolations) and a properties table (typed values matching the header schema). FlatBuffers enables zero-copy reading: to get a field value, the parser doesn't allocate memory, it reads from a fixed offset.
Typical compression runs 2–5x smaller than equivalent GeoJSON. For 1 million OSM buildings in Moscow: GeoJSON is around 340 MB, FlatGeobuf about 85 MB. Add HTTP gzip on top and you save another 30–40%.
HTTP range requests: FlatGeobuf's main USP
The single reason to pick FGB over GeoJSON or Shapefile is HTTP range request support. Scenario: an 800 MB FlatGeobuf file with Berlin buildings sits on CloudFlare R2. MapLibre GL JS makes the first request for the first 100 KB — reads the header, learns the schema and R-tree location. The second request targets the R-tree bytes — figures out which R-tree nodes intersect the visible map area. The third request grabs a specific byte range of features in that node. Loaded and rendered: ~2 MB instead of 800 MB.
To make this work you need proper nginx configuration (gzip off for FGB and add_header Accept-Ranges bytes) or hosting on an S3-compatible service that supports range requests by default: CloudFlare R2, AWS S3, Backblaze B2.

Strengths and weaknesses
Strengths. HTTP streaming with no server code. Packed Hilbert R-tree — bbox filtering inside the format itself. Binary packing via FlatBuffers with zero-copy reads. SQL/MM Part 3 circular interpolations supported. Compatibility across major GIS stacks since 2020. Single file — no companion .shx/.dbf/.prj like with Shapefile. Size cut by 2–5x versus GeoJSON.
Weaknesses. Not human-readable — can't open in a text editor, can't git diff. Not an OGC standard, which can block adoption in government procurements requiring ISO/OGC-certified formats. The R-tree is built once at write time — features can't be added incrementally without full file rewrite. Older software (ArcGIS before 2022, QGIS before 3.16, GDAL < 3.1) doesn't read it. No native raster support — vectors only.
FlatGeobuf vs alternatives
GeoPackage (OGC SQLite container) loses to FGB on streaming speed, wins on transactional editing and multi-layer support plus rasters and QML styles in one file. GeoParquet is the best choice for analytics via DuckDB/Athena, loses to FGB on spatial filtering (Parquet has no native R-tree, only bbox covering since v1.1). Shapefile loses on every dimension except legacy ArcGIS compatibility. GeoJSON wins on human-readability and native browser support, loses on size and speed.
Typical use cases
Cloud GIS developer. Takes a Germany-wide OSM extract (~25 GB as Shapefile or 6 GB as FGB), drops it on R2, hooks it into Mapbox/MapLibre via the flatgeobuf plugin. The browser pulls only the visible area; as the user pans, new byte ranges download. Zero server code.
QGIS power user. Opens a 5-million-feature FGB — QGIS reads only the header and R-tree, then fetches features in the current viewport as you pan. A Shapefile of the same size forces QGIS to read the entire .shx index then chunk-read .shp byte by byte — on a slow disk the difference is 30 seconds vs 3 minutes.
Web app with millions of POIs. Moscow POI map — 200 thousand points. As GeoJSON: 25 MB, browser parsing blocks the thread for 4 seconds. As FGB: 6 MB, parsing is essentially free (zero-copy via the JS FlatBuffers reader), spatial index already in-file, only visible features render.
OpenStreetMap extract publisher. Services like Geofabrik and BBBike publish daily regional dumps. Switching from Shapefile to FGB saves 60% disk space and speeds up downloads for end users.
Pipeline in OSM2CDR
Our flatgeobuf_exporter.py uses QGIS QgsVectorFileWriter with the FlatGeobuf driver from GDAL ≥ 3.1. Layers feed through the WKB fast-path: PostGIS returns geometry directly as WKB via ST_AsBinary, skipping GeoJSON serialization. This yields 10–50x speedup over the traditional OSM → Shapefile → ogr2ogr → FGB pipeline.
CRS comes from the user's pick among 165 systems (43 popular + 122 Gauss-Kruger zones keyed to Russian MSK numbers — an approximation, not the true MSK of a subject; the parameters of 222 real zones across 75 MSK numbers, each with a named source and confidence class, are computed by the coordinate converter). Default is WGS 84 (EPSG:4326) for web mapping compatibility. The R-tree is built automatically by the GDAL driver — no extra steps required.

FAQ
Does FlatGeobuf support transactions and editing? No. FGB is designed write-once-read-many — after writing the file, the R-tree becomes part of the binary structure, and any change requires a full rewrite. For editing, use GeoPackage.
Can I open FGB in ArcGIS Pro? ArcGIS Pro 3.1+ (released 2023) has a native FlatGeobuf driver. Before that you need ArcGIS Data Interoperability (FME-based) or a pre-conversion to GeoPackage via ogr2ogr.
What's a packed Hilbert R-tree, and why is it better than a regular R-tree? The Hilbert space-filling curve is a mathematical mapping from 2D to a 1D sequence that preserves locality: neighboring points on the curve are neighbors in the plane. An R-tree whose nodes are ordered by Hilbert index reads sequentially from disk or HTTP — each byte request pulls related features together rather than scattered blocks.
Does FGB support multiple layers? No. One FGB file means one geometry_type and one schema. For multiple layers, use a directory of .fgb files or a GeoPackage.
What compression does FlatGeobuf use? None internal — the format relies on HTTP gzip/brotli during delivery. This is intentional: zero-copy FlatBuffers reading breaks under compression. For archival compression, wrap .fgb in .gz.
Related
- FlatGeobuf and streaming GIS — overview guide
- GeoPackage — complete guide to the OGC SQLite format
- GeoParquet — columnar format for big-data GIS
- GeoJSON — the web standard, RFC 7946
- Best free GIS formats in 2026
Sources
- FlatGeobuf GitHub repository — github.com/flatgeobuf/flatgeobuf
- Björn Harrtell, original gdal-dev proposal (Dec 2018) — OSGeo mailing list archive
- GDAL FlatGeobuf driver — gdal.org/drivers/vector/flatgeobuf.html
- Google FlatBuffers documentation — flatbuffers.dev
- Overture Maps Foundation publishing format — overturemaps.org