Skip to content

Both copy text to your clipboard — Build with AI copies a setup prompt to paste into Claude Code, Cursor, Codex or Copilot; Copy page as Markdown copies this page to paste into a chat. How it works

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:

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 source
  • HillshadeStyleLayer — renders shaded relief from that DEM source (a flat visual effect, not a deformed 3D surface)
  • MapOptions.initPitch / initBearing, plus MapController.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

dart
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

PropertyDescription
RasterDemSource.tilesTerrain-RGB/Terrarium DEM tile URL template
RasterDemSource.tileSizeTile size in pixels
RasterDemSource.encodingRasterDemMapboxEncoding() (default), RasterDemTerrariumEncoding(), or a custom encoding
HillshadeStyleLayerRenders 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


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.