Add a Color Relief Layer in Flutter
This tutorial shows how to add a color relief (hypsometric tinting) layer to your MapMetrics Flutter map — coloring terrain by elevation bands from green lowlands to white mountain peaks.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Basic Color Relief
Color the map terrain based on elevation bands using a raster DEM source:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class ColorReliefScreen extends StatefulWidget {
@override
_ColorReliefScreenState createState() => _ColorReliefScreenState();
}
class _ColorReliefScreenState extends State<ColorReliefScreen> {
MapMetricsController? mapController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Color Relief Layer')),
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(46.8182, 8.2275), // Swiss Alps
zoom: 8.0,
),
onStyleLoaded: () {
_addColorRelief();
},
),
// Elevation legend
Positioned(
bottom: 16,
left: 16,
child: Card(
child: Padding(
padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text('Elevation',
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 12)),
SizedBox(height: 6),
_elevRow(Color(0xFF1a6e1a), '0 - 200m'),
_elevRow(Color(0xFF4ca64c), '200 - 500m'),
_elevRow(Color(0xFFb3d98c), '500 - 1000m'),
_elevRow(Color(0xFFe6d96e), '1000 - 2000m'),
_elevRow(Color(0xFFc9854c), '2000 - 3000m'),
_elevRow(Color(0xFF8c5a2e), '3000 - 4000m'),
_elevRow(Color(0xFFffffff), '4000m+'),
],
),
),
),
),
],
),
);
}
Widget _elevRow(Color color, String label) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 1),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 16,
height: 12,
decoration: BoxDecoration(
color: color,
border: Border.all(color: Colors.grey[400]!, width: 0.5),
),
),
SizedBox(width: 6),
Text(label, style: TextStyle(fontSize: 11)),
],
),
);
}
void _addColorRelief() {
// Add terrain DEM source
mapController?.addRasterDemSource(
'terrain-dem',
'https://gateway.mapmetrics.org/terrain/{z}/{x}/{y}.png',
tileSize: 256,
);
// Add color relief raster layer with elevation-based color ramp
mapController?.addRasterLayer(
'color-relief',
'terrain-dem',
rasterColor: [
'interpolate',
['linear'],
['raster-value'],
0.0, '#1a6e1a', // Deep green (sea level)
0.05, '#4ca64c', // Green (200m)
0.125, '#b3d98c', // Light green (500m)
0.25, '#e6d96e', // Yellow-green (1000m)
0.5, '#c9854c', // Brown (2000m)
0.75, '#8c5a2e', // Dark brown (3000m)
1.0, '#ffffff', // White (4000m+)
],
rasterOpacity: 0.6,
);
}
}Color Relief with Hillshade
Combine elevation coloring with hillshade for a professional topographic look:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class ReliefHillshadeScreen extends StatefulWidget {
@override
_ReliefHillshadeScreenState createState() =>
_ReliefHillshadeScreenState();
}
class _ReliefHillshadeScreenState extends State<ReliefHillshadeScreen> {
MapMetricsController? mapController;
bool showRelief = true;
bool showHillshade = true;
double reliefOpacity = 0.5;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Relief + Hillshade')),
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: 10.0,
),
onStyleLoaded: () {
_addLayers();
},
),
// Controls
Positioned(
top: 16,
right: 16,
child: Card(
child: Padding(
padding: EdgeInsets.all(10),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Layers',
style: TextStyle(fontWeight: FontWeight.bold)),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Checkbox(
value: showRelief,
onChanged: (val) {
setState(() => showRelief = val!);
mapController?.setLayerVisibility(
'color-relief', val!);
},
),
Text('Color Relief', style: TextStyle(fontSize: 13)),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Checkbox(
value: showHillshade,
onChanged: (val) {
setState(() => showHillshade = val!);
mapController?.setLayerVisibility(
'hillshade', val!);
},
),
Text('Hillshade', style: TextStyle(fontSize: 13)),
],
),
SizedBox(height: 4),
Text('Opacity', style: TextStyle(fontSize: 12)),
SizedBox(
width: 150,
child: Slider(
value: reliefOpacity,
min: 0.1,
max: 1.0,
onChanged: (val) {
setState(() => reliefOpacity = val);
mapController?.setPaintProperty(
'color-relief',
'raster-opacity',
val,
);
},
),
),
],
),
),
),
),
// Location presets
Positioned(
bottom: 16,
left: 8,
right: 8,
child: SizedBox(
height: 40,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
_locationChip('Matterhorn', 45.976, 7.659, 10.0),
SizedBox(width: 6),
_locationChip('Mont Blanc', 45.833, 6.865, 10.0),
SizedBox(width: 6),
_locationChip('Swiss Alps', 46.818, 8.228, 8.0),
SizedBox(width: 6),
_locationChip('Dolomites', 46.410, 11.844, 10.0),
SizedBox(width: 6),
_locationChip('Pyrenees', 42.695, 0.041, 8.0),
],
),
),
),
],
),
);
}
Widget _locationChip(String name, double lat, double lng, double zoom) {
return ActionChip(
avatar: Icon(Icons.terrain, size: 14),
label: Text(name, style: TextStyle(fontSize: 12)),
onPressed: () {
mapController?.animateCamera(
CameraUpdate.newLatLngZoom(LatLng(lat, lng), zoom),
);
},
);
}
void _addLayers() {
mapController?.addRasterDemSource(
'terrain-dem',
'https://gateway.mapmetrics.org/terrain/{z}/{x}/{y}.png',
tileSize: 256,
);
// Hillshade layer (below relief for shadow effect)
mapController?.addHillshadeLayer(
'hillshade',
'terrain-dem',
hillshadeExaggeration: 0.5,
hillshadeIlluminationDirection: 315.0,
);
// Color relief on top with transparency
mapController?.addRasterLayer(
'color-relief',
'terrain-dem',
rasterColor: [
'interpolate',
['linear'],
['raster-value'],
0.0, '#1a6e1a',
0.05, '#4ca64c',
0.125, '#b3d98c',
0.25, '#e6d96e',
0.5, '#c9854c',
0.75, '#8c5a2e',
1.0, '#ffffff',
],
rasterOpacity: reliefOpacity,
);
}
}Color Scheme Switcher
Switch between different elevation color palettes:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class ReliefSchemesScreen extends StatefulWidget {
@override
_ReliefSchemesScreenState createState() => _ReliefSchemesScreenState();
}
class _ReliefSchemesScreenState extends State<ReliefSchemesScreen> {
MapMetricsController? mapController;
String activeScheme = 'natural';
final Map<String, Map<String, dynamic>> schemes = {
'natural': {
'name': 'Natural',
'colors': [
0.0, '#1a6e1a', 0.05, '#4ca64c', 0.125, '#b3d98c',
0.25, '#e6d96e', 0.5, '#c9854c', 0.75, '#8c5a2e', 1.0, '#ffffff',
],
},
'ocean': {
'name': 'Ocean Blue',
'colors': [
0.0, '#0d47a1', 0.1, '#1565c0', 0.25, '#42a5f5',
0.5, '#90caf9', 0.75, '#bbdefb', 1.0, '#e3f2fd',
],
},
'thermal': {
'name': 'Thermal',
'colors': [
0.0, '#00008B', 0.15, '#0000FF', 0.3, '#00FFFF',
0.5, '#00FF00', 0.7, '#FFFF00', 0.85, '#FF8C00', 1.0, '#FF0000',
],
},
'grayscale': {
'name': 'Grayscale',
'colors': [
0.0, '#1a1a1a', 0.25, '#4d4d4d', 0.5, '#808080',
0.75, '#b3b3b3', 1.0, '#ffffff',
],
},
};
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Relief Color Schemes')),
body: Column(
children: [
Container(
padding: EdgeInsets.all(8),
child: Wrap(
spacing: 8,
children: schemes.entries.map((entry) {
return ChoiceChip(
label: Text(entry.value['name']),
selected: activeScheme == entry.key,
onSelected: (selected) {
if (selected) {
setState(() => activeScheme = entry.key);
_applyScheme(entry.value['colors']);
}
},
);
}).toList(),
),
),
Expanded(
child: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(46.818, 8.228),
zoom: 8.0,
),
onStyleLoaded: () {
_setupRelief();
},
),
),
],
),
);
}
void _setupRelief() {
mapController?.addRasterDemSource(
'terrain-dem',
'https://gateway.mapmetrics.org/terrain/{z}/{x}/{y}.png',
tileSize: 256,
);
final colors = schemes[activeScheme]!['colors'] as List;
mapController?.addRasterLayer(
'color-relief',
'terrain-dem',
rasterColor: [
'interpolate',
['linear'],
['raster-value'],
...colors,
],
rasterOpacity: 0.6,
);
}
void _applyScheme(List colors) {
// Remove and re-add with new color ramp
mapController?.removeLayer('color-relief');
mapController?.addRasterLayer(
'color-relief',
'terrain-dem',
rasterColor: [
'interpolate',
['linear'],
['raster-value'],
...colors,
],
rasterOpacity: 0.6,
);
}
}Color Relief Properties
| Property | Type | Description |
|---|---|---|
rasterColor | List | Color interpolation expression mapping elevation to colors |
rasterOpacity | double | Layer transparency (0.0 - 1.0) |
rasterValue | Expression | Normalized elevation value from DEM (0.0 - 1.0) |
Standard Elevation Color Bands
| Elevation | Color | Terrain Type |
|---|---|---|
| 0 - 200m | Dark green | Lowlands, valleys |
| 200 - 500m | Green | Hills, foothills |
| 500 - 1000m | Light green | Uplands |
| 1000 - 2000m | Yellow-green | Mountains |
| 2000 - 3000m | Brown | High mountains |
| 3000 - 4000m | Dark brown | Alpine zone |
| 4000m+ | White | Glaciers, snow |
Next Steps
- Add a Hillshade Layer — Shaded relief effect
- Add Contour Lines — Elevation contour lines
- 3D Terrain — Full 3D elevation view
Tip: Combine color relief + hillshade + contour lines for a complete topographic map. Set the relief layer opacity to 0.4-0.6 so the base map labels remain readable underneath.