How to Cluster Markers with FlutterMap

Rory Stephenson
ITNEXT
Published in
5 min readJul 6, 2023

--

In this article we are going go from a mess of overlapping Markers to a beautiful, fast clustered Marker implementation. You can find the finished code here.

Create a new project

First things first, let’s start out with a bare-bones app:

import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'FlutterMap Clustering',
home: FlutterMapPage(),
);
}
}

class FlutterMapPage extends StatelessWidget {
const FlutterMapPage({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('FlutterMap Clustering')),
body: const SizedBox.shrink(),
);
}
}

Add FlutterMap

Now we want to add in FlutterMap with some dummy data. I’m going to add the dummy data as normal Markers first so we can see what it looks like without clustering. You will need to add flutter_map and latlong2 to your pubspec.yaml. Your pubspec.yaml should look as follows:

dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.5
flutter_map: ^5.0.0 # Add this
latlong2: ^0.9.0 # Add this

Now we can modify our main.dart to show FlutterMap with some Markers. We’re going to randomly generate a few thousand Markers so that we can see what the map looks like with a lot of Markers close to each other. Modify your FlutterMapPage class as follows:

class FlutterMapPage extends StatelessWidget {
// Initialise randomly generated Markers
static final _random = Random(42);
static final _initialCenter = LatLng(42.0, 10.0);
static final _markers = List<Marker>.generate(
3000,
(_) => Marker(
builder: (context) => const Icon(Icons.location_on),
point: LatLng(
_random.nextDouble() * 3 - 1.5 + _initialCenter.latitude,
_random.nextDouble() * 3 - 1.5 + _initialCenter.longitude,
),
),
);

const FlutterMapPage({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('FlutterMap Clustering')),
body: FlutterMap( // Add FlutterMap
options: MapOptions(
center: _initialCenter,
zoom: 8,
),
children: [
TileLayer( // Show a satellite map background
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
),
MarkerLayer(markers: _markers), // Show the Markers
],
),
);
}
}

Now at this point if you run the app you should see something like the following:

Densely packed Markers

Congratulations on creating a mess! Apart from being hard to see, this will also perform quite poorly. When moving the map FlutterMap will search the Markers to determine which Markers are within the visible bounds and will render them all on every frame. With just a few thousand Markers you may start to notice some jank, with 10s of thousand your map will grind to a jittery halt.

Never fear, supercluster is here. The flutter_map_supercluster package will group your Markers in to clusters when they are close to each other. This prevents the overcrowded mess we created above and will bring your map back to its old snappy self.

Supercluster

The flutter_map_supercluster package uses the supercluster package to cluster your Markers and display. It is based on MapBox’s supercluster javascript package which uses a lightening fast algorithm to group Markers. flutter_map_supercluster also supports adding/removing of Markers efficiently but that’s for another article!

Let’s add the flutter_map_supercluster package and set it up. Add it to your pubspec as follows:

dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.5
flutter_map: ^4.0.0
flutter_map_supercluster: ^4.3.0 # Add this
latlong2: ^0.8.1

Now we can swap out the MarkerLayer for a supercluster layer. We are going to use SuperclusterLayer.immutable because it’s faster and we don’t need to add/remove Markers. If you do need to add or remove Markers you should use SuperclusterLayer.immutable, which is still very fast. For now, modify your FlutterMapPage build method to match the following:

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('FlutterMap Clustering')),
body: FlutterMap(
options: MapOptions(
center: _initialCenter,
zoom: 8,
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
),
SuperclusterLayer.immutable( // Replaces MarkerLayer
initialMarkers: _markers,
indexBuilder: IndexBuilders.rootIsolate,
builder: (context, position, markerCount, extraClusterData) =>
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: Colors.blue,
),
child: Center(
child: Text(
markerCount.toString(),
style: const TextStyle(color: Colors.white),
),
),
),
),
],
),
);
}

A couple of things to note here:

  • indexBuilder determines how flutter_map_supercluster builds the clusters initially. If you have a very large number of Markers you may notice some jank on the first load as the cluster building takes place in the main isolate. If that is the case use IndexBuilders.computeWithOriginalMarkers or IndexBuilders.computeWithCopiedMarkers . See the documentation of IndexBuilders for an explanation of the differences, for now building in the main isolate is fine.
  • builder is the builder for the Marker clusters.

So how does it look?!

Clustered Markers

Ahhhh so much better. We can now zoom in or tap a cluster to see the clusters and Markers it contains. You will find that if you move around the map, even with far greater numbers of clusters, no frames will be dropped and it will be as snappy as ever!

So that brings us to the end of this article. I should mention that we have just scratched the surface of flutter_map_supercluster. Here’s a quick list of some its other features:

  • Customise clustering radius and minimum points to form a cluster.
  • Show a loading overlay when creating the clusters in a separate isolate.
  • Customise cluster widgets to give a summary of the contained Markers.
  • Show popups for Markers
  • Fast adding/removing of Markers using SuperclusterMutableController.
  • … and more!

Here’s a teaser of customised cluster widgets that show a summary of their contained Markers:

Clusters that show a summary of their contained markers

Conclusion

So now you know how to cluster large collections of Markers to avoid a visual mess and slow loading! As the creator of flutter_map_supercluster I hope it is useful for you. If you have any issues or feature requests feel free to leave a comment or open an issue. You can find the finished code here.

If you are interesting in popups without clustering, or dynamically loaded markers you may be interested in our of my other packages:

--

--

Passionate about Flutter for over 3 years, prior to that building high performance backends.