Render World Copies in Flutter
This tutorial shows how to control whether the map renders copies of the world when zoomed out — and how to handle wrapping at the antimeridian.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Toggle World Copies
Enable or disable world repetition when zoomed out:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class WorldCopiesScreen extends StatefulWidget {
@override
_WorldCopiesScreenState createState() => _WorldCopiesScreenState();
}
class _WorldCopiesScreenState extends State<WorldCopiesScreen> {
MapMetricsController? mapController;
bool renderWorldCopies = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('World Copies'),
actions: [
Row(
children: [
Text('Copies', style: TextStyle(color: Colors.white)),
Switch(
value: renderWorldCopies,
onChanged: (val) {
setState(() => renderWorldCopies = val);
mapController?.setRenderWorldCopies(val);
},
activeColor: Colors.white,
),
],
),
],
),
body: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(20.0, 0.0),
zoom: 1.0,
),
renderWorldCopies: renderWorldCopies,
),
);
}
}Antimeridian-Crossing Routes
Draw a route that crosses the international date line (180th meridian):
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class AntimeridianRouteScreen extends StatefulWidget {
@override
_AntimeridianRouteScreenState createState() =>
_AntimeridianRouteScreenState();
}
class _AntimeridianRouteScreenState extends State<AntimeridianRouteScreen> {
MapMetricsController? mapController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Antimeridian Route')),
body: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(35.0, 180.0), // Center on antimeridian
zoom: 2.0,
),
renderWorldCopies: true,
polylines: {
// Route: Tokyo → Honolulu (crosses antimeridian)
Polyline(
polylineId: PolylineId('trans_pacific'),
points: [
LatLng(35.6762, 139.6503), // Tokyo
LatLng(35.0, 160.0),
LatLng(30.0, 180.0), // Antimeridian
LatLng(25.0, -170.0),
LatLng(21.3069, -157.8583), // Honolulu
],
color: Colors.blue,
width: 3,
),
},
markers: {
Marker(
markerId: MarkerId('tokyo'),
position: LatLng(35.6762, 139.6503),
infoWindow: InfoWindow(title: 'Tokyo'),
),
Marker(
markerId: MarkerId('honolulu'),
position: LatLng(21.3069, -157.8583),
infoWindow: InfoWindow(title: 'Honolulu'),
),
},
),
);
}
}Global Data Visualization
Display global data with proper world wrapping:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class GlobalDataScreen extends StatefulWidget {
@override
_GlobalDataScreenState createState() => _GlobalDataScreenState();
}
class _GlobalDataScreenState extends State<GlobalDataScreen> {
MapMetricsController? mapController;
bool showCopies = true;
final List<Map<String, dynamic>> globalOffices = [
{'city': 'New York', 'lat': 40.713, 'lng': -74.006},
{'city': 'London', 'lat': 51.507, 'lng': -0.128},
{'city': 'Dubai', 'lat': 25.205, 'lng': 55.271},
{'city': 'Mumbai', 'lat': 19.076, 'lng': 72.878},
{'city': 'Singapore', 'lat': 1.352, 'lng': 103.820},
{'city': 'Tokyo', 'lat': 35.676, 'lng': 139.650},
{'city': 'Sydney', 'lat': -33.869, 'lng': 151.209},
{'city': 'Sao Paulo', 'lat': -23.551, 'lng': -46.633},
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Global Offices')),
body: Stack(
children: [
MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(20.0, 0.0),
zoom: 1.5,
),
renderWorldCopies: showCopies,
markers: globalOffices.map((office) {
return Marker(
markerId: MarkerId(office['city']),
position: LatLng(office['lat'], office['lng']),
infoWindow: InfoWindow(title: office['city']),
);
}).toSet(),
),
Positioned(
bottom: 16,
left: 16,
child: Card(
child: Padding(
padding: EdgeInsets.all(8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('World copies'),
Switch(
value: showCopies,
onChanged: (val) {
setState(() => showCopies = val);
mapController?.setRenderWorldCopies(val);
},
),
],
),
),
),
),
],
),
);
}
}When to Enable/Disable World Copies
| Scenario | World Copies | Reason |
|---|---|---|
| Global flight map | On | Routes can wrap naturally |
| Country-level app | Off | Prevents confusion at edges |
| City-level app | Doesn't matter | Not visible at high zoom |
| Dashboard/analytics | On | Shows complete global data |
| Embedded preview | Off | Cleaner single-world view |
Next Steps
- Display the Whole World — Global map view
- Restrict Map Panning — Lock to a region
- Arc Layer — Flight route arcs
Tip: When renderWorldCopies is false, the map stops at the edges of one world. This is useful for apps that restrict the view to a single country or region — users can't accidentally pan into a duplicate world.