Skip to content

Satellite Map with Terrain Elevation in Flutter

This tutorial shows how to display satellite imagery combined with 3D terrain elevation — ideal for outdoor, hiking, and geographic exploration apps.

Prerequisites

Before you begin, ensure you have:

Basic Satellite View

Switch to a satellite style URL for aerial imagery:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class SatelliteMapScreen extends StatefulWidget {
  @override
  _SatelliteMapScreenState createState() => _SatelliteMapScreenState();
}

class _SatelliteMapScreenState extends State<SatelliteMapScreen> {
  MapMetricsController? mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Satellite View')),
      body: MapMetrics(
        styleUrl:
            'https://gateway.mapmetrics.org/styles/YOUR_SATELLITE_STYLE_ID?token=YOUR_API_KEY',
        onMapCreated: (MapMetricsController controller) {
          mapController = controller;
        },
        initialCameraPosition: CameraPosition(
          target: LatLng(46.8182, 8.2275), // Swiss Alps
          zoom: 10.0,
          tilt: 60.0,
          bearing: 30.0,
        ),
      ),
    );
  }
}

Toggle Between Map and Satellite

Let users switch between standard map and satellite views:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class MapToggleScreen extends StatefulWidget {
  @override
  _MapToggleScreenState createState() => _MapToggleScreenState();
}

class _MapToggleScreenState extends State<MapToggleScreen> {
  MapMetricsController? mapController;
  bool isSatellite = false;

  final String mapStyleUrl =
      'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY';
  final String satelliteStyleUrl =
      'https://gateway.mapmetrics.org/styles/YOUR_SATELLITE_STYLE_ID?token=YOUR_API_KEY';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Map / Satellite Toggle')),
      body: Stack(
        children: [
          MapMetrics(
            styleUrl: isSatellite ? satelliteStyleUrl : mapStyleUrl,
            onMapCreated: (MapMetricsController controller) {
              mapController = controller;
            },
            initialCameraPosition: CameraPosition(
              target: LatLng(48.8584, 2.2945),
              zoom: 15.0,
              tilt: isSatellite ? 45.0 : 0.0,
            ),
          ),
          // Toggle button
          Positioned(
            top: 16,
            right: 16,
            child: Container(
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(8),
                boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4)],
              ),
              child: ToggleButtons(
                isSelected: [!isSatellite, isSatellite],
                onPressed: (index) {
                  setState(() {
                    isSatellite = index == 1;
                  });
                },
                borderRadius: BorderRadius.circular(8),
                children: [
                  Padding(
                    padding: EdgeInsets.symmetric(horizontal: 12),
                    child: Row(
                      children: [
                        Icon(Icons.map, size: 18),
                        SizedBox(width: 4),
                        Text('Map'),
                      ],
                    ),
                  ),
                  Padding(
                    padding: EdgeInsets.symmetric(horizontal: 12),
                    child: Row(
                      children: [
                        Icon(Icons.satellite, size: 18),
                        SizedBox(width: 4),
                        Text('Satellite'),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Satellite with 3D Terrain

Combine satellite imagery with terrain elevation for a dramatic effect:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class SatelliteTerrainScreen extends StatefulWidget {
  @override
  _SatelliteTerrainScreenState createState() =>
      _SatelliteTerrainScreenState();
}

class _SatelliteTerrainScreenState extends State<SatelliteTerrainScreen> {
  MapMetricsController? mapController;
  double exaggeration = 1.5;
  bool terrainEnabled = true;

  final List<Map<String, dynamic>> locations = [
    {'name': 'Swiss Alps', 'lat': 46.818, 'lng': 8.228, 'zoom': 10.0, 'bearing': 30.0},
    {'name': 'Grand Canyon', 'lat': 36.107, 'lng': -112.113, 'zoom': 11.0, 'bearing': 90.0},
    {'name': 'Mount Fuji', 'lat': 35.361, 'lng': 138.727, 'zoom': 11.0, 'bearing': 200.0},
    {'name': 'Himalayas', 'lat': 27.988, 'lng': 86.925, 'zoom': 10.0, 'bearing': 45.0},
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Satellite + Terrain')),
      body: Stack(
        children: [
          MapMetrics(
            styleUrl:
                'https://gateway.mapmetrics.org/styles/YOUR_SATELLITE_STYLE_ID?token=YOUR_API_KEY',
            onMapCreated: (MapMetricsController controller) {
              mapController = controller;
            },
            initialCameraPosition: CameraPosition(
              target: LatLng(46.818, 8.228),
              zoom: 10.0,
              tilt: 60.0,
              bearing: 30.0,
            ),
            onStyleLoaded: () {
              if (terrainEnabled) _enableTerrain();
            },
          ),
          // Location buttons
          Positioned(
            bottom: 80,
            left: 8,
            right: 8,
            child: SizedBox(
              height: 40,
              child: ListView.separated(
                scrollDirection: Axis.horizontal,
                itemCount: locations.length,
                separatorBuilder: (_, __) => SizedBox(width: 6),
                itemBuilder: (context, i) {
                  final loc = locations[i];
                  return ElevatedButton(
                    onPressed: () {
                      mapController?.animateCamera(
                        CameraUpdate.newCameraPosition(
                          CameraPosition(
                            target: LatLng(loc['lat'], loc['lng']),
                            zoom: loc['zoom'],
                            tilt: 60.0,
                            bearing: loc['bearing'],
                          ),
                        ),
                      );
                    },
                    child: Text(loc['name'], style: TextStyle(fontSize: 11)),
                    style: ElevatedButton.styleFrom(
                      padding: EdgeInsets.symmetric(horizontal: 12),
                    ),
                  );
                },
              ),
            ),
          ),
          // Terrain toggle
          Positioned(
            bottom: 16,
            left: 16,
            right: 16,
            child: Card(
              child: Padding(
                padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
                child: Row(
                  children: [
                    Text('3D Terrain'),
                    Switch(
                      value: terrainEnabled,
                      onChanged: (val) {
                        setState(() => terrainEnabled = val);
                        if (val) {
                          _enableTerrain();
                        } else {
                          mapController?.setTerrain(
                              'terrain-source', exaggeration: 0.0);
                        }
                      },
                    ),
                    Expanded(
                      child: Slider(
                        value: exaggeration,
                        min: 0.5,
                        max: 3.0,
                        divisions: 25,
                        label: '${exaggeration.toStringAsFixed(1)}x',
                        onChanged: terrainEnabled
                            ? (val) {
                                setState(() => exaggeration = val);
                                mapController?.setTerrain(
                                    'terrain-source', exaggeration: val);
                              }
                            : null,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  void _enableTerrain() {
    mapController?.addRasterDemSource(
      'terrain-source',
      'https://gateway.mapmetrics.org/terrain/{z}/{x}/{y}.png',
      tileSize: 256,
    );
    mapController?.setTerrain('terrain-source', exaggeration: exaggeration);
  }
}

Next Steps


Tip: Satellite + terrain is very data-heavy. For production apps, enable terrain only when the user zooms past level 8 to save bandwidth and improve load times at global zoom levels.