Skip to content

Add Contour Lines in Flutter

This tutorial shows how to add elevation contour lines to your MapMetrics Flutter map — essential for topographic maps, hiking apps, and geographic analysis.

Prerequisites

Before you begin, ensure you have:

Basic Contour Lines

Add contour lines from a vector tile source:

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

class ContourLinesScreen extends StatefulWidget {
  @override
  _ContourLinesScreenState createState() => _ContourLinesScreenState();
}

class _ContourLinesScreenState extends State<ContourLinesScreen> {
  MapMetricsController? mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Contour Lines')),
      body: MapMetrics(
        styleUrl:
            'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
        onMapCreated: (MapMetricsController controller) {
          mapController = controller;
        },
        initialCameraPosition: CameraPosition(
          target: LatLng(46.8182, 8.2275), // Swiss Alps
          zoom: 11.0,
        ),
        onStyleLoaded: () {
          _addContourLines();
        },
      ),
    );
  }

  void _addContourLines() {
    // Add contour source (vector tiles with elevation data)
    mapController?.addVectorSource(
      'contour-source',
      'https://gateway.mapmetrics.org/contours/{z}/{x}/{y}.pbf',
    );

    // Add thin contour lines (every 100m)
    mapController?.addLineLayer(
      'contour-lines',
      'contour-source',
      sourceLayer: 'contour',
      lineColor: '#8B4513',
      lineWidth: 0.5,
      lineOpacity: 0.5,
    );

    // Add thicker lines for major contours (every 500m)
    mapController?.addLineLayer(
      'contour-lines-major',
      'contour-source',
      sourceLayer: 'contour',
      lineColor: '#8B4513',
      lineWidth: 1.5,
      lineOpacity: 0.7,
      filter: ['==', ['%', ['get', 'ele'], 500], 0],
    );

    // Add elevation labels on major contours
    mapController?.addSymbolLayer(
      'contour-labels',
      'contour-source',
      sourceLayer: 'contour',
      textField: '{ele}m',
      textSize: 10.0,
      textColor: '#8B4513',
      symbolPlacement: 'line',
      filter: ['==', ['%', ['get', 'ele'], 500], 0],
    );
  }
}

Contour Lines with Hillshade

Combine contour lines with hillshade for a classic topographic map:

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

class TopoMapScreen extends StatefulWidget {
  @override
  _TopoMapScreenState createState() => _TopoMapScreenState();
}

class _TopoMapScreenState extends State<TopoMapScreen> {
  MapMetricsController? mapController;
  bool showContours = true;
  bool showHillshade = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Topographic Map')),
      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(45.9763, 7.6586), // Matterhorn
              zoom: 12.0,
            ),
            onStyleLoaded: () {
              _addLayers();
            },
          ),
          // Layer toggles
          Positioned(
            top: 16,
            right: 16,
            child: Card(
              child: Padding(
                padding: EdgeInsets.all(8),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Checkbox(
                          value: showHillshade,
                          onChanged: (val) {
                            setState(() => showHillshade = val!);
                            _toggleLayer('hillshade-layer', val!);
                          },
                        ),
                        Text('Hillshade', style: TextStyle(fontSize: 13)),
                      ],
                    ),
                    Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Checkbox(
                          value: showContours,
                          onChanged: (val) {
                            setState(() => showContours = val!);
                            _toggleLayer('contour-lines', val!);
                            _toggleLayer('contour-lines-major', val!);
                            _toggleLayer('contour-labels', val!);
                          },
                        ),
                        Text('Contours', style: TextStyle(fontSize: 13)),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  void _addLayers() {
    // Hillshade
    mapController?.addRasterDemSource(
      'dem-source',
      'https://gateway.mapmetrics.org/terrain/{z}/{x}/{y}.png',
      tileSize: 256,
    );

    mapController?.addHillshadeLayer(
      'hillshade-layer',
      'dem-source',
      hillshadeExaggeration: 0.4,
      hillshadeIlluminationDirection: 315.0,
    );

    // Contour lines
    mapController?.addVectorSource(
      'contour-source',
      'https://gateway.mapmetrics.org/contours/{z}/{x}/{y}.pbf',
    );

    mapController?.addLineLayer(
      'contour-lines',
      'contour-source',
      sourceLayer: 'contour',
      lineColor: '#8B4513',
      lineWidth: 0.5,
      lineOpacity: 0.4,
    );

    mapController?.addLineLayer(
      'contour-lines-major',
      'contour-source',
      sourceLayer: 'contour',
      lineColor: '#8B4513',
      lineWidth: 1.2,
      lineOpacity: 0.6,
      filter: ['==', ['%', ['get', 'ele'], 500], 0],
    );

    mapController?.addSymbolLayer(
      'contour-labels',
      'contour-source',
      sourceLayer: 'contour',
      textField: '{ele}m',
      textSize: 9.0,
      textColor: '#654321',
      symbolPlacement: 'line',
      filter: ['==', ['%', ['get', 'ele'], 500], 0],
    );
  }

  void _toggleLayer(String layerId, bool visible) {
    mapController?.setLayerVisibility(layerId, visible);
  }
}

Contour Line Properties

PropertyValueDescription
lineColor#8B4513Brown for topographic convention
lineWidth0.5 (minor) / 1.5 (major)Thicker for index contours
lineOpacity0.4 - 0.7Semi-transparent to not obscure the base map
filter['==', ['%', ['get', 'ele'], 500], 0]Show only every 500m

Next Steps


Tip: Use brown (#8B4513) for contour lines — it's the cartographic standard. Show minor contours (every 100m) as thin/light lines and major contours (every 500m) as thick/labeled for readability.