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

Set Pitch and Bearing in Flutter ​

This tutorial shows how to control the map's pitch (tilt) and bearing (rotation) to create 3D perspectives and custom viewing angles.

Prerequisites ​

Before you begin, ensure you have:

What Are Pitch and Bearing? ​

  • Pitch (tilt): The angle of the camera from 0° (looking straight down) to 60°–85° (looking towards the horizon). Higher pitch creates a more 3D perspective.
  • Bearing (rotation): The compass direction the camera faces, from -180° to 180° (or 0° to 360°). 0° faces north, 90° faces east.

Basic Pitch and Bearing ​

Set the initial pitch and bearing on MapOptions:

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

class PitchBearingScreen extends StatefulWidget {
  @override
  _PitchBearingScreenState createState() => _PitchBearingScreenState();
}

class _PitchBearingScreenState extends State<PitchBearingScreen> {
  MapController? mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Pitch & Bearing')),
      body: MapMetricsView(
        options: MapOptions(
          initStyle:
              'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
          initCenter: Position(-74.0060, 40.7128), // lng, lat — New York
          initZoom: 15.0,
          initBearing: 45.0, // Rotated 45°
          initPitch: 60.0,   // Tilted for 3D view
        ),
        onMapCreated: (MapController controller) {
          mapController = controller;
        },
      ),
    );
  }
}

Interactive Controls ​

Let users adjust pitch and bearing with sliders:

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

class InteractivePitchBearingScreen extends StatefulWidget {
  @override
  _InteractivePitchBearingScreenState createState() =>
      _InteractivePitchBearingScreenState();
}

class _InteractivePitchBearingScreenState
    extends State<InteractivePitchBearingScreen> {
  MapController? mapController;
  double pitch = 0.0;
  double bearing = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Adjust Perspective')),
      body: Column(
        children: [
          // Controls
          Container(
            padding: EdgeInsets.all(12),
            color: Colors.grey[100],
            child: Column(
              children: [
                // Pitch slider
                Row(
                  children: [
                    SizedBox(width: 70, child: Text('Pitch: ${pitch.toInt()}°')),
                    Expanded(
                      child: Slider(
                        value: pitch,
                        min: 0,
                        max: 60,
                        onChanged: (value) {
                          setState(() {
                            pitch = value;
                          });
                          _updateCamera();
                        },
                      ),
                    ),
                  ],
                ),
                // Bearing slider
                Row(
                  children: [
                    SizedBox(
                      width: 70,
                      child: Text('Bearing: ${bearing.toInt()}°'),
                    ),
                    Expanded(
                      child: Slider(
                        value: bearing,
                        min: 0,
                        max: 360,
                        onChanged: (value) {
                          setState(() {
                            bearing = value;
                          });
                          _updateCamera();
                        },
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
          // Map
          Expanded(
            child: MapMetricsView(
              options: MapOptions(
                initStyle:
                    'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
                initCenter: Position(-74.0060, 40.7128),
                initZoom: 15.0,
              ),
              onMapCreated: (controller) => mapController = controller,
            ),
          ),
        ],
      ),
    );
  }

  void _updateCamera() {
    mapController?.animateCamera(
      center: Position(-74.0060, 40.7128),
      zoom: 15.0,
      bearing: bearing,
      pitch: pitch,
    );
  }
}

Common Presets ​

Use preset buttons for commonly used perspectives:

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

class PresetViewsScreen extends StatefulWidget {
  @override
  _PresetViewsScreenState createState() => _PresetViewsScreenState();
}

class _PresetViewsScreenState extends State<PresetViewsScreen> {
  MapController? mapController;

  final List<Map<String, dynamic>> presets = [
    {
      'name': 'Top Down',
      'icon': Icons.arrow_downward,
      'pitch': 0.0,
      'bearing': 0.0,
      'zoom': 15.0,
    },
    {
      'name': 'Street View',
      'icon': Icons.streetview,
      'pitch': 60.0,
      'bearing': 0.0,
      'zoom': 17.0,
    },
    {
      'name': 'Bird\'s Eye',
      'icon': Icons.flight,
      'pitch': 45.0,
      'bearing': -45.0,
      'zoom': 16.0,
    },
    {
      'name': 'Cinematic',
      'icon': Icons.movie,
      'pitch': 50.0,
      'bearing': 130.0,
      'zoom': 16.0,
    },
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('View Presets')),
      body: Column(
        children: [
          // Preset buttons
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            padding: EdgeInsets.all(8),
            child: Row(
              children: presets.map((preset) {
                return Padding(
                  padding: EdgeInsets.only(right: 8),
                  child: ElevatedButton.icon(
                    onPressed: () => _applyPreset(preset),
                    icon: Icon(preset['icon']),
                    label: Text(preset['name']),
                  ),
                );
              }).toList(),
            ),
          ),
          // Map
          Expanded(
            child: MapMetricsView(
              options: MapOptions(
                initStyle:
                    'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
                initCenter: Position(-74.0060, 40.7128),
                initZoom: 15.0,
              ),
              onMapCreated: (controller) => mapController = controller,
            ),
          ),
        ],
      ),
    );
  }

  void _applyPreset(Map<String, dynamic> preset) {
    mapController?.animateCamera(
      center: Position(-74.0060, 40.7128),
      zoom: preset['zoom'] as double,
      bearing: preset['bearing'] as double,
      pitch: preset['pitch'] as double,
    );
  }
}

Common Perspective Values ​

ViewPitchBearingBest For
Top Down0°0°2D overview, data visualization
Slight Tilt30°0°General browsing
Street View60°0°Street-level exploration
Bird's Eye45°-45°Urban areas, buildings
Cinematic50°130°Dramatic presentation

Next Steps ​


Tip: Combine pitch with a 3D map style to see buildings and terrain in perspective. Higher pitch values create a more dramatic 3D effect.