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:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
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
| View | Pitch | Bearing | Best For |
|---|---|---|---|
| Top Down | 0° | 0° | 2D overview, data visualization |
| Slight Tilt | 30° | 0° | General browsing |
| Street View | 60° | 0° | Street-level exploration |
| Bird's Eye | 45° | -45° | Urban areas, buildings |
| Cinematic | 50° | 130° | Dramatic presentation |
Next Steps
- Fly to a Location — Combine fly-to with pitch and bearing changes
- Fullscreen Map — Immersive fullscreen experience
- Jump to Locations — Tour with different perspectives
Tip: Combine pitch with a 3D map style to see buildings and terrain in perspective. Higher pitch values create a more dramatic 3D effect.