Skip to content

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:

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

ScenarioWorld CopiesReason
Global flight mapOnRoutes can wrap naturally
Country-level appOffPrevents confusion at edges
City-level appDoesn't matterNot visible at high zoom
Dashboard/analyticsOnShows complete global data
Embedded previewOffCleaner single-world view

Next Steps


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.