Skip to content

Draw GeoJSON Points in Flutter

This tutorial shows how to render multiple points from GeoJSON data on your MapMetrics Flutter map — efficient for displaying large datasets of locations.

Prerequisites

Before you begin, ensure you have:

Basic GeoJSON Points

Render European capital cities as circle markers from a GeoJSON FeatureCollection:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class GeoJsonPointsScreen extends StatefulWidget {
  @override
  _GeoJsonPointsScreenState createState() => _GeoJsonPointsScreenState();
}

class _GeoJsonPointsScreenState extends State<GeoJsonPointsScreen> {
  MapMetricsController? mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('GeoJSON Points')),
      body: MapMetrics(
        styleUrl:
            'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
        onMapCreated: (MapMetricsController controller) {
          mapController = controller;
        },
        initialCameraPosition: CameraPosition(
          target: LatLng(50.0, 10.0),
          zoom: 3.0,
        ),
        onStyleLoaded: () {
          _addGeoJsonPoints();
        },
      ),
    );
  }

  void _addGeoJsonPoints() {
    final geoJson = {
      'type': 'FeatureCollection',
      'features': [
        {
          'type': 'Feature',
          'properties': {'name': 'Paris', 'population': 2161000},
          'geometry': {
            'type': 'Point',
            'coordinates': [2.349902, 48.852966],
          },
        },
        {
          'type': 'Feature',
          'properties': {'name': 'London', 'population': 8982000},
          'geometry': {
            'type': 'Point',
            'coordinates': [-0.1276, 51.5074],
          },
        },
        {
          'type': 'Feature',
          'properties': {'name': 'Berlin', 'population': 3645000},
          'geometry': {
            'type': 'Point',
            'coordinates': [13.405, 52.52],
          },
        },
        {
          'type': 'Feature',
          'properties': {'name': 'Rome', 'population': 2873000},
          'geometry': {
            'type': 'Point',
            'coordinates': [12.4964, 41.9028],
          },
        },
        {
          'type': 'Feature',
          'properties': {'name': 'Madrid', 'population': 3223000},
          'geometry': {
            'type': 'Point',
            'coordinates': [-3.7038, 40.4168],
          },
        },
        {
          'type': 'Feature',
          'properties': {'name': 'Vienna', 'population': 1897000},
          'geometry': {
            'type': 'Point',
            'coordinates': [16.3738, 48.2082],
          },
        },
        {
          'type': 'Feature',
          'properties': {'name': 'Amsterdam', 'population': 872680},
          'geometry': {
            'type': 'Point',
            'coordinates': [4.9041, 52.3676],
          },
        },
      ],
    };

    // Add the GeoJSON source
    mapController?.addGeoJsonSource('cities', geoJson);

    // Add a circle layer to render points
    mapController?.addCircleLayer(
      'cities-circles',
      'cities',
      circleRadius: 8.0,
      circleColor: '#3b82f6',
      circleStrokeColor: '#ffffff',
      circleStrokeWidth: 2.0,
    );
  }
}

Points with Labels

Add text labels next to each point:

dart
void _addPointsWithLabels() {
  final geoJson = {
    'type': 'FeatureCollection',
    'features': [
      {
        'type': 'Feature',
        'properties': {'name': 'Paris'},
        'geometry': {
          'type': 'Point',
          'coordinates': [2.349902, 48.852966],
        },
      },
      {
        'type': 'Feature',
        'properties': {'name': 'London'},
        'geometry': {
          'type': 'Point',
          'coordinates': [-0.1276, 51.5074],
        },
      },
      {
        'type': 'Feature',
        'properties': {'name': 'Berlin'},
        'geometry': {
          'type': 'Point',
          'coordinates': [13.405, 52.52],
        },
      },
    ],
  };

  mapController?.addGeoJsonSource('labeled-cities', geoJson);

  // Circle layer
  mapController?.addCircleLayer(
    'labeled-cities-circles',
    'labeled-cities',
    circleRadius: 6.0,
    circleColor: '#ef4444',
    circleStrokeColor: '#ffffff',
    circleStrokeWidth: 2.0,
  );

  // Text label layer
  mapController?.addSymbolLayer(
    'labeled-cities-labels',
    'labeled-cities',
    textField: '{name}',
    textSize: 12.0,
    textColor: '#1f2937',
    textOffset: [0.0, 1.5],
    textAnchor: 'top',
  );
}

Styled Points with Different Sizes

Use Flutter markers alongside GeoJSON data for richer styling:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class StyledPointsScreen extends StatefulWidget {
  @override
  _StyledPointsScreenState createState() => _StyledPointsScreenState();
}

class _StyledPointsScreenState extends State<StyledPointsScreen> {
  MapMetricsController? mapController;

  final List<Map<String, dynamic>> cities = [
    {'name': 'London', 'lat': 51.5074, 'lng': -0.1276, 'pop': 8982000},
    {'name': 'Berlin', 'lat': 52.52, 'lng': 13.405, 'pop': 3645000},
    {'name': 'Madrid', 'lat': 40.4168, 'lng': -3.7038, 'pop': 3223000},
    {'name': 'Rome', 'lat': 41.9028, 'lng': 12.4964, 'pop': 2873000},
    {'name': 'Paris', 'lat': 48.853, 'lng': 2.3499, 'pop': 2161000},
    {'name': 'Vienna', 'lat': 48.2082, 'lng': 16.3738, 'pop': 1897000},
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Styled Points')),
      body: Stack(
        children: [
          MapMetrics(
            styleUrl:
                'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
            onMapCreated: (MapMetricsController controller) {
              mapController = controller;
            },
            initialCameraPosition: CameraPosition(
              target: LatLng(48.0, 8.0),
              zoom: 4.0,
            ),
            markers: _buildMarkers(),
          ),
          // Legend
          Positioned(
            bottom: 16,
            left: 16,
            child: Container(
              padding: EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(8),
                boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4)],
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text('Population', style: TextStyle(fontWeight: FontWeight.bold)),
                  SizedBox(height: 4),
                  Row(children: [
                    Container(width: 10, height: 10, decoration: BoxDecoration(color: Colors.red, shape: BoxShape.circle)),
                    SizedBox(width: 6),
                    Text('> 5M'),
                  ]),
                  Row(children: [
                    Container(width: 8, height: 8, decoration: BoxDecoration(color: Colors.orange, shape: BoxShape.circle)),
                    SizedBox(width: 6),
                    Text('2M - 5M'),
                  ]),
                  Row(children: [
                    Container(width: 6, height: 6, decoration: BoxDecoration(color: Colors.blue, shape: BoxShape.circle)),
                    SizedBox(width: 6),
                    Text('< 2M'),
                  ]),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Set<Marker> _buildMarkers() {
    return cities.map((city) {
      return Marker(
        markerId: MarkerId(city['name']),
        position: LatLng(city['lat'], city['lng']),
        infoWindow: InfoWindow(
          title: city['name'],
          snippet: 'Pop: ${(city['pop'] / 1000000).toStringAsFixed(1)}M',
        ),
      );
    }).toSet();
  }
}

GeoJSON Circle Layer Properties

PropertyTypeDescription
circleRadiusdoubleRadius of the circle in pixels
circleColorStringFill color of the circle
circleOpacitydoubleFill opacity from 0.0 to 1.0
circleStrokeColorStringBorder color of the circle
circleStrokeWidthdoubleBorder width in pixels

Next Steps


Tip: GeoJSON circle layers are more efficient than individual markers when displaying hundreds or thousands of points. Use them for large datasets and switch to Flutter markers only when you need custom widgets.