Skip to content

FlatGeobuf

A FlatGeobuf (.fgb) file holds features (points, lines, polygons) with their properties in a compact binary form, often with a spatial index. maplibre-yaml reads it into an ordinary GeoJSON source, so everything that works with a .geojson file works with it: popups, classify:, clustering, fitTo. Use it when GeoJSON gets slow; for large static data, PMTiles is faster still. It is one of the large-data formats.

Keep type: geojson and point url: at the file:

type: map
id: districts
config:
center: [10.2, 42.15]
zoom: 10
mapStyle: "https://tiles.openfreemap.org/styles/positron"
fitTo: { source: districts, padding: 24 }
sources:
districts:
type: geojson
url: /gallery-assets/cloud-native/districts.fgb
layers:
- id: districts
type: fill
source: districts
paint:
fill-color: "#e9c46a"
fill-opacity: 0.6
interactive:
click:
popup:
- h3: [{ property: name }]

The file is on this documentation site; to run the document on yours, download districts.fgb into gallery-assets/cloud-native/. You should see yellow districts on a coast; click one for its name. The gallery’s FlatGeobuf source shows it live.

By default a file is read whole when it is small and by view when it is large. load: changes that:

load:What it readsNeeds
none, or all (default)Small files whole, once. Large ones by view (see Large files) when the file has a spatial index and the server supports range requests; otherwise the map says why and how to fix itAny web server for a small file
viewportOnly the features in view, read again as the map moves; nothing below minzoom (default 10), and nothing for a view holding more than maxFeatures (default 150,000)A file with a spatial index, and a server with range requests
sources:
districts:
type: geojson
url: data/districts.fgb # small file: read whole
parcels:
type: geojson
url: https://data.example.org/parcels.fgb
load: { mode: viewport, minzoom: 14 } # big file: only what's in view

Reading by view changes a few things:

  • fitTo frames the extent recorded in the file’s header instead of the features, at the outermost zoom whose view is read (never below minzoom nor over maxFeatures), so the map opens with features drawn.
  • classify: with method: needs the whole file. On a source read by view, write explicit breaks: (Thematic maps).
  • Read whole up to 50 MB and 400,000 features (the header’s count, so a file of many small features counts too). Between 10 and 50 MB a console note says it may take a few seconds. As a rule of thumb, a whole file is comfortable up to about 100,000 simple features (about 20 MB).
  • Over either, a file with a spatial index is read by view instead, with a note for the author; one without an index, or on a server without range requests, is refused, and the notice says which and how to fix it.
  • By view, a view holding more than maxFeatures features (default 150,000) isn’t read; the file’s index tells how many before any feature is downloaded. Readers see “Too many features to show here — zoom in to see them”, also below minzoom, until they zoom in.
  1. Say load: viewport. A large indexed file is read by view anyway; saying so drops the note.

  2. Raise maxFeatures under load:: the most features one read takes, by view, and above 400,000 it raises the whole-file feature limit too (load: { maxFeatures: 600000 }). It costs memory, so the notes suggest it only up to 600,000 features loaded whole, or 250,000 in a view, and say how much; the 50 MB limit stays.

  3. Add a spatial index so the file can be read by view:

    Terminal window
    ogr2ogr -f FlatGeobuf -lco SPATIAL_INDEX=YES parcels-indexed.fgb parcels.fgb
  4. For a citywide view, convert it to PMTiles, which draws at every zoom. tylertoo reads GeoParquet, so convert first (uvx --from geoparquet-io==1.7.0 gpio convert geoparquet parcels.fgb parcels.parquet, no GDAL needed), then follow the PMTiles guide.

npx maplibre-yaml@latest prepare parcels.fgb reads the file’s header and prints the step that applies, with the YAML to paste (Choose a format for large data).

Terminal window
ogr2ogr -f FlatGeobuf out.fgb in.geojson

GDAL writes the spatial index by default. Add -t_srs EPSG:4326 if the data is in another projection. In QGIS, use Export → Save Features As → FlatGeobuf.

  • Coordinates must be longitude/latitude (EPSG:4326).
  • Write the file uncompressed. A Zstandard-compressed file is refused (zstd -d decompresses it).
  • Live data (refresh:, stream:) isn’t available for FlatGeobuf; validation says so. Serve GeoJSON for data that changes (Live data).
  • “…is in EPSG:3857; MapLibre needs longitude/latitude (EPSG:4326)”: the notice gives the fix, ogr2ogr -t_srs EPSG:4326 out.fgb x.fgb.
  • “…has 500,000 features, more than a page loads whole … and it can’t be read by view”: the notice names what blocks reading by view (no spatial index, or a server without range requests) and the command that fixes it; see Large files for every way.
  • “Too many features to show here — zoom in to see them”: the view holds more than maxFeatures, or the map is below minzoom; zoom in, raise maxFeatures or lower minzoom under load:, or convert the file to PMTiles to draw it at every zoom.
  • “…was read as JSON, but it is a FlatGeobuf file”: the URL doesn’t end in .fgb. Rename the file, or add format: flatgeobuf to the source.
  • Hosting problems (range requests, CORS): see Large data › When something goes wrong.

Export to style.json: mlym emit writes the file’s features into the style as GeoJSON (Large-data files in an exported style).