Skip to content

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 in the camera position:

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

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Pitch & Bearing')),
      body: MapMetrics(
        styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
        onMapCreated: (MapMetricsController controller) {
          mapController = controller;
        },
        initialCameraPosition: CameraPosition(
          target: LatLng(40.7128, -74.0060), // New York
          zoom: 15.0,
          bearing: 45.0,  // Rotated 45°
          tilt: 60.0,     // Tilted for 3D view
        ),
      ),
    );
  }
}

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> {
  MapMetricsController? 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: MapMetrics(
              styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
              onMapCreated: (controller) => mapController = controller,
              initialCameraPosition: CameraPosition(
                target: LatLng(40.7128, -74.0060),
                zoom: 15.0,
              ),
            ),
          ),
        ],
      ),
    );
  }

  void _updateCamera() {
    mapController?.animateCamera(
      CameraUpdate.newCameraPosition(
        CameraPosition(
          target: LatLng(40.7128, -74.0060),
          zoom: 15.0,
          bearing: bearing,
          tilt: 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> {
  MapMetricsController? 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: MapMetrics(
              styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
              onMapCreated: (controller) => mapController = controller,
              initialCameraPosition: CameraPosition(
                target: LatLng(40.7128, -74.0060),
                zoom: 15.0,
              ),
            ),
          ),
        ],
      ),
    );
  }

  void _applyPreset(Map<String, dynamic> preset) {
    mapController?.animateCamera(
      CameraUpdate.newCameraPosition(
        CameraPosition(
          target: LatLng(40.7128, -74.0060),
          zoom: preset['zoom'],
          bearing: preset['bearing'],
          tilt: preset['pitch'],
        ),
      ),
    );
  }
}

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.