Sky, Fog, and Terrain in Flutter
This tutorial shows how to add a shaded-relief terrain look and combine it with camera tilt for a more immersive map view.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style ID from the MapMetrics Portal
What's Actually Supported
The real MapMetrics Flutter SDK does not expose runtime APIs for a sky gradient layer, atmospheric fog, or 3D-deformed terrain elevation (no addSkyLayer, setFog, or setTerrain with an exaggeration parameter exist on MapController or StyleController). What it does expose is:
RasterDemSource— a terrain-RGB/Terrarium elevation tile sourceHillshadeStyleLayer— renders shaded relief from that DEM source (a flat visual effect, not a deformed 3D surface)MapOptions.initPitch/initBearing, plusMapController.animateCamera(pitch: ..., bearing: ...)— real camera tilt and rotation
So this tutorial builds a hillshaded terrain look combined with camera tilt, which is the closest real equivalent to the "3D atmosphere" idea. True sky/fog atmosphere and elevated 3D terrain are not available in this SDK version — if you need them, they'd have to come from the map style JSON itself (a portal/style concern, not something this Flutter API controls).
Hillshaded Terrain with Camera Tilt
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class SkyFogTerrainScreen extends StatefulWidget {
@override
_SkyFogTerrainScreenState createState() => _SkyFogTerrainScreenState();
}
class _SkyFogTerrainScreenState extends State<SkyFogTerrainScreen> {
MapController? mapController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Terrain & Camera Tilt')),
body: MapMetricsView(
options: MapOptions(
initStyle:
'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
initCenter: Position(8.2275, 46.8182), // lng, lat — Swiss Alps
initZoom: 10.0,
initPitch: 70.0,
initBearing: 30.0,
),
onMapCreated: (MapController controller) {
mapController = controller;
},
onStyleLoaded: (StyleController style) {
_addHillshade(style);
},
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton.small(
heroTag: 'immersive',
onPressed: _setImmersiveView,
child: Icon(Icons.landscape),
tooltip: 'Immersive',
),
SizedBox(height: 8),
FloatingActionButton.small(
heroTag: 'overhead',
onPressed: _setOverheadView,
child: Icon(Icons.map),
tooltip: 'Overhead',
),
],
),
);
}
Future<void> _addHillshade(StyleController style) async {
await style.addSource(
const RasterDemSource(
id: 'terrain-source',
tiles: [
'https://gateway.mapmetrics-atlas.net/terrain/{z}/{x}/{y}.png',
],
tileSize: 256,
),
);
await style.addLayer(
const HillshadeStyleLayer(
id: 'hillshade',
sourceId: 'terrain-source',
),
);
}
void _setImmersiveView() {
mapController?.animateCamera(
center: Position(8.2275, 46.8182),
zoom: 10.0,
pitch: 70.0,
bearing: 30.0,
);
}
void _setOverheadView() {
mapController?.animateCamera(
center: Position(8.2275, 46.8182),
zoom: 10.0,
pitch: 0.0,
bearing: 0.0,
);
}
}Terrain Properties
| Property | Description |
|---|---|
RasterDemSource.tiles | Terrain-RGB/Terrarium DEM tile URL template |
RasterDemSource.tileSize | Tile size in pixels |
RasterDemSource.encoding | RasterDemMapboxEncoding() (default), RasterDemTerrariumEncoding(), or a custom encoding |
HillshadeStyleLayer | Renders shaded relief from the DEM source — no exposed exaggeration/paint tuning yet |
Sky gradients and fog blending, and true elevation-deformed 3D terrain, aren't controllable from this table — see What's Actually Supported above.
Next Steps
- 3D Buildings — Extruded buildings
- Set Pitch and Bearing — Camera tilt and rotation basics
Tip: Hillshading reads best at a high initPitch (60–80°) combined with a bearing that puts light-facing slopes toward the camera. At pitch: 0.0 (overhead view) the shading is still visible, just flatter-looking since there's no true elevation to view from an angle.