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:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
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
| Property | Value | Description |
|---|---|---|
lineColor | #8B4513 | Brown for topographic convention |
lineWidth | 0.5 (minor) / 1.5 (major) | Thicker for index contours |
lineOpacity | 0.4 - 0.7 | Semi-transparent to not obscure the base map |
filter | ['==', ['%', ['get', 'ele'], 500], 0] | Show only every 500m |
Next Steps
- Add a Hillshade Layer — Shaded relief effect
- 3D Terrain — Full 3D elevation
- Satellite Terrain — Satellite with terrain
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.