Skip to content

Working with GeoJSON

GeoJSON is the data format that powers everything in MapLibre. Whether you're adding a symbol layer, a cluster, or a heatmap, the data comes from a GeoJSON source. Understanding GeoJSON unlocks the full power of style layers.

What is GeoJSON?

GeoJSON (RFC 7946) is a JSON format for geographic features. A FeatureCollection contains any number of Features, each of which has a Geometry (the shape) and Properties (arbitrary metadata).

flowchart LR
    FC["FeatureCollection"] --> F["Feature"]
    F --> G["geometry"]
    F --> P["properties"]
    G --> GT["type<br/><small>Point · LineString · Polygon</small>"]
    G --> GC["coordinates<br/><small>[lng, lat] nested by type</small>"]
    P --> PV["{ name: 'Paris',<br/>population: 2161000, ... }"]

    classDef root fill:#1f6feb,stroke:#1a5fd0,color:#fff;
    class FC root

Longitude before latitude

GeoJSON uses [longitude, latitude] order, the opposite of LatLng(lat, lng) in Flutter. This is a common source of bugs.

The three geometry types

Point

A single geographic location.

{
  'type': 'Feature',
  'properties': {'name': 'Eiffel Tower'},
  'geometry': {
    'type': 'Point',
    'coordinates': [2.2945, 48.8584], // [lng, lat]
  },
}

LineString

An ordered sequence of points forming a line.

{
  'type': 'Feature',
  'properties': {'route': 'Paris - Berlin'},
  'geometry': {
    'type': 'LineString',
    'coordinates': [
      [2.3522, 48.8566],   // Paris [lng, lat]
      [13.4050, 52.5200],  // Berlin
    ],
  },
}

Polygon

A closed ring of coordinates (first and last point must be identical).

{
  'type': 'Feature',
  'properties': {'name': 'Zone A'},
  'geometry': {
    'type': 'Polygon',
    'coordinates': [
      [
        [2.0, 48.5],
        [2.5, 48.5],
        [2.5, 49.0],
        [2.0, 49.0],
        [2.0, 48.5], // close the ring
      ]
    ],
  },
}

Using properties in expressions

Properties are what make style layers powerful. Any feature property can drive the visual appearance of that feature via expressions.

// Property access in a layer
SymbolLayerProperties(
  textField: [Expressions.get, 'name'],         // show 'name' property as label
  textSize: [Expressions.get, 'font_size'],     // drive size from data
  iconColor: [Expressions.get, 'color'],        // drive color from data
)

Adding a GeoJSON source

Inline data

Pass a Dart Map<String, dynamic> representing the FeatureCollection:

await controller.addGeoJsonSource('my-source', {
  'type': 'FeatureCollection',
  'features': [
    {
      'type': 'Feature',
      'properties': {'name': 'Paris', 'population': 2161000},
      'geometry': {
        'type': 'Point',
        'coordinates': [2.3522, 48.8566],
      },
    },
  ],
});

Remote URL

Pass a URL string as the GeoJSON value. MapLibre fetches and parses it:

await controller.addGeoJsonSource('rivers', {
  'type': 'geojson',
  'data': 'https://example.com/rivers.geojson',
});

CORS on web

When running on web, the GeoJSON URL must have appropriate CORS headers. Requests to the same origin always work.

Updating data

Replace all features

await controller.setGeoJsonSource('my-source', {
  'type': 'FeatureCollection',
  'features': updatedFeatures,
});

Use this when the dataset changes significantly (e.g. filtering, time-series playback).

Update a single feature

await controller.setGeoJsonFeature('my-source', {
  'type': 'Feature',
  'id': 'feature-42',  // must match an existing feature id
  'properties': {'status': 'active'},
  'geometry': {
    'type': 'Point',
    'coordinates': [2.3522, 48.8566],
  },
});

For this to work, features must have an id field. On web you may also need promoteId (see below).

promoteId: web-only caveat

MapLibre GL JS requires features to have an integer ID for feature-state to work correctly. If your GeoJSON uses string IDs or property-based IDs, pass promoteId:

await controller.addGeoJsonSource(
  'my-source',
  geojsonData,
  promoteId: 'myIdProperty', // promotes this property to be the feature ID
);

This parameter is ignored on Android and iOS (native MapLibre handles feature IDs natively).

Live demo

South American capitals rendered from an inline GeoJSON FeatureCollection. Circle radius scales with population using an interpolate expression.

Next steps