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 Down2D overview, data visualization
Slight Tilt30°General browsing
Street View60°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.