Add a Hillshade Layer in Flutter
This tutorial shows how to add a hillshade layer to your MapMetrics Flutter map — creating a shaded relief effect that makes terrain features like mountains, valleys, and ridges visible on a 2D map.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Basic Hillshade
Add a hillshade layer using a raster DEM (Digital Elevation Model) source:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class HillshadeScreen extends StatefulWidget {
@override
_HillshadeScreenState createState() => _HillshadeScreenState();
}
class _HillshadeScreenState extends State<HillshadeScreen> {
MapMetricsController? mapController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Hillshade Layer')),
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: 9.0,
),
onStyleLoaded: () {
_addHillshade();
},
),
);
}
void _addHillshade() {
// Add terrain DEM source
mapController?.addRasterDemSource(
'hillshade-source',
'https://gateway.mapmetrics.org/terrain/{z}/{x}/{y}.png',
tileSize: 256,
);
// Add hillshade layer
mapController?.addHillshadeLayer(
'hillshade-layer',
'hillshade-source',
hillshadeExaggeration: 0.5,
hillshadeShadowColor: '#000000',
hillshadeHighlightColor: '#ffffff',
hillshadeAccentColor: '#000000',
hillshadeIlluminationDirection: 315.0,
);
}
}Hillshade with Adjustable Light Direction
Let users control the sun direction to see terrain from different angles:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class AdjustableHillshadeScreen extends StatefulWidget {
@override
_AdjustableHillshadeScreenState createState() =>
_AdjustableHillshadeScreenState();
}
class _AdjustableHillshadeScreenState
extends State<AdjustableHillshadeScreen> {
MapMetricsController? mapController;
double lightDirection = 315.0;
double exaggeration = 0.5;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Adjustable 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(46.8182, 8.2275),
zoom: 9.0,
),
onStyleLoaded: () {
_addHillshade();
},
),
// Controls
Positioned(
bottom: 16,
left: 16,
right: 16,
child: Card(
child: Padding(
padding: EdgeInsets.all(12),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Icon(Icons.wb_sunny, size: 18),
SizedBox(width: 8),
Text('Light: ${lightDirection.toInt()}°'),
Expanded(
child: Slider(
value: lightDirection,
min: 0,
max: 360,
onChanged: (val) {
setState(() => lightDirection = val);
mapController?.setPaintProperty(
'hillshade-layer',
'hillshade-illumination-direction',
val,
);
},
),
),
],
),
Row(
children: [
Icon(Icons.terrain, size: 18),
SizedBox(width: 8),
Text('Relief: ${exaggeration.toStringAsFixed(1)}'),
Expanded(
child: Slider(
value: exaggeration,
min: 0.0,
max: 1.0,
onChanged: (val) {
setState(() => exaggeration = val);
mapController?.setPaintProperty(
'hillshade-layer',
'hillshade-exaggeration',
val,
);
},
),
),
],
),
],
),
),
),
),
],
),
);
}
void _addHillshade() {
mapController?.addRasterDemSource(
'hillshade-source',
'https://gateway.mapmetrics.org/terrain/{z}/{x}/{y}.png',
tileSize: 256,
);
mapController?.addHillshadeLayer(
'hillshade-layer',
'hillshade-source',
hillshadeExaggeration: exaggeration,
hillshadeIlluminationDirection: lightDirection,
);
}
}Hillshade with Location Presets
Jump to famous mountain ranges to see the hillshade effect:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class HillshadePresetsScreen extends StatefulWidget {
@override
_HillshadePresetsScreenState createState() =>
_HillshadePresetsScreenState();
}
class _HillshadePresetsScreenState extends State<HillshadePresetsScreen> {
MapMetricsController? mapController;
final List<Map<String, dynamic>> presets = [
{'name': 'Swiss Alps', 'lat': 46.818, 'lng': 8.228, 'zoom': 9.0},
{'name': 'Norwegian Fjords', 'lat': 61.0, 'lng': 6.5, 'zoom': 8.0},
{'name': 'Pyrenees', 'lat': 42.695, 'lng': 0.041, 'zoom': 8.0},
{'name': 'Scottish Highlands', 'lat': 57.0, 'lng': -5.0, 'zoom': 8.0},
{'name': 'Dolomites', 'lat': 46.410, 'lng': 11.844, 'zoom': 10.0},
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Hillshade Presets')),
body: Column(
children: [
Container(
padding: EdgeInsets.all(8),
child: Wrap(
spacing: 6,
runSpacing: 6,
children: presets.map((p) {
return ActionChip(
avatar: Icon(Icons.terrain, size: 16),
label: Text(p['name'], style: TextStyle(fontSize: 12)),
onPressed: () {
mapController?.animateCamera(
CameraUpdate.newLatLngZoom(
LatLng(p['lat'], p['lng']),
p['zoom'],
),
);
},
);
}).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: 9.0,
),
onStyleLoaded: () {
mapController?.addRasterDemSource(
'hillshade-source',
'https://gateway.mapmetrics.org/terrain/{z}/{x}/{y}.png',
tileSize: 256,
);
mapController?.addHillshadeLayer(
'hillshade-layer',
'hillshade-source',
hillshadeExaggeration: 0.5,
hillshadeIlluminationDirection: 315.0,
);
},
),
),
],
),
);
}
}Hillshade Properties
| Property | Type | Default | Description |
|---|---|---|---|
hillshadeExaggeration | double | 0.5 | Intensity of the shading (0.0 - 1.0) |
hillshadeIlluminationDirection | double | 335.0 | Sun angle in degrees (0-360) |
hillshadeShadowColor | String | #000000 | Color of shaded areas |
hillshadeHighlightColor | String | #ffffff | Color of illuminated areas |
hillshadeAccentColor | String | #000000 | Color for emphasizing terrain |
Next Steps
- 3D Terrain — Full 3D terrain elevation
- Add Contour Lines — Elevation contour lines
- Satellite Terrain — Satellite with elevation
Tip: Hillshade works on flat 2D maps (no tilt needed) and is lighter on performance than full 3D terrain. It's ideal for hiking apps where you want terrain visibility without the rendering overhead of 3D.