# flutter\_map

Flutter's №1 non-commercially aimed map client: it's easy-to-use, versatile, vendor-free, fully cross-platform, and 100% pure-Flutter

{% hint style="success" %}

## Welcome to v8 🎉

Less than a half of our users are using v8 & benefiting from heaps of major new features and fixes. Consider upgrading!&#x20;

* If you're upgrading, find out [What's New In v8.2?](/getting-started/new-in-v8)
* If you're new here, welcome! Feel free to look around and experiment, and check out [Installation](/getting-started/installation) when you're ready.
* If you're looking for documentation for an older version, use the version selector.  [v7](https://docs.fleaflet.dev/v7/) documentation is still available.
  {% endhint %}

| [![GitHub source](https://gist.github.com/cxmeel/0dbc95191f239b631c3874f4ccf114e2/raw/github.svg)](https://github.com/fleaflet/flutter_map) [![pub.dev package](https://gist.github.com/cxmeel/0dbc95191f239b631c3874f4ccf114e2/raw/download.svg)](https://pub.dev/packages/flutter_map)    [![Join our Discord](https://gist.github.com/cxmeel/0dbc95191f239b631c3874f4ccf114e2/raw/discord.svg)](https://discord.gg/BwpEsjqMAH) [![Support us](https://gist.github.com/cxmeel/0dbc95191f239b631c3874f4ccf114e2/raw/github_sponsor.svg)](https://github.com/sponsors/fleaflet) |
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |

{% embed url="<https://demo.fleaflet.dev>" %}

## Why choose flutter\_map?

<table data-card-size="large" data-view="cards" data-full-width="false"><thead><tr><th align="center"></th><th align="center"></th></tr></thead><tbody><tr><td align="center">🗺️ <strong>No more vendor lock-in: better flexibility, quality, and price</strong></td><td align="center"><p>We natively support any static <a data-footnote-ref href="#user-content-fn-1">raster*</a> tiles! <strong>Bring your own tiles</strong> from your own server, the user's device, a tile container, or another externally-operated tile server. Use any service, but always be free to change to get the best fit, quality, and price.<br></p><p>Still want to use those familiar maps? Consider great quality and better priced alternatives, or use the more mainstream Google Maps or Mapbox Maps with flutter_map<a data-footnote-ref href="#user-content-fn-2">*</a>.</p></td></tr><tr><td align="center">🚀 <strong>Stress-free setup &#x26; easy-to-use</strong></td><td align="center">Migrating from a commercial library (such as Google Maps) has never been easier. No more complex platform-specific setup, no more buggy &#x26; limited platform views (we're 100% pure-Flutter), and no more lacking-documentation &#x26; support. Just setup a simple widget, grab a string from your tile server, and you're done! And, it'll work across all the platforms Flutter supports.<br><br>Check out our <a data-mention href="/pages/YICQhcdQtAOWmU2QJcuB#code-demo">/pages/YICQhcdQtAOWmU2QJcuB#code-demo</a> to see just how simple it is.</td></tr><tr><td align="center">🧩 <strong>Customize and expand endlessly</strong></td><td align="center">Add interactive and highly customizable polygons, polylines, and markers (which support widget children) to your map easily and quickly. And because we're 100% Flutter, it's easy to add your own stuff on top without messing around in platform views.<br><br>A huge community of developers maintain an ecosystem of plugins for you to supercharge flutter_map with.</td></tr><tr><td align="center">👋 <strong>But don't just take it from us...</strong></td><td align="center">Hundreds of thousands of apps and users choose flutter_map for mapping in their Flutter app, making us Flutter's №1 non-commercially aimed map client on pub.dev.<br><br>Check out some independent reviews, comparisons, and studies, and see who's using flutter_map right now: <a data-mention href="/pages/eqVurD9aPws4irN9ZUCf">/pages/eqVurD9aPws4irN9ZUCf</a></td></tr></tbody></table>

{% content-ref url="/pages/eqVurD9aPws4irN9ZUCf" %}
[Showcase & Case Studies](/why-and-how/showcase)
{% endcontent-ref %}

{% hint style="info" %}

## Vector tile support

If you're looking for [vector tiles](/why-and-how/how-does-it-work/raster-vs-vector-tiles), we don't currently support them natively. We only support raster tiles at the moment.

However, [options are available](/why-and-how/how-does-it-work/raster-vs-vector-tiles#using-vector-tiles), and the we and the community are actively exploring & developing future support!
{% endhint %}

## Code Demo

Setting up a map is simpler than making your lunch-time coffee! It can be accomplished in just under 30 lines and a minute or two to install.

This code snippet demonstrates **everything** you need for a simple map (in under 30 lines!), but of course, FM is capable of much more than just this, and you could find yourself lost in the many options available and possibilities opened!

{% code lineNumbers="true" fullWidth="true" %}

```dart
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';

@override
Widget build(BuildContext context) {
  return FlutterMap(
    options: MapOptions(
      initialCenter: LatLng(51.509364, -0.128928), // Center the map over London, UK
      initialZoom: 9.2,
    ),
    children: [
      TileLayer( // Bring your own tiles
        urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', // For demonstration only
        userAgentPackageName: /*'com.example.app'*/, // Add your app identifier
        // And many more recommended properties!
      ),
      RichAttributionWidget( // Include a stylish prebuilt attribution widget that meets all requirments
        attributions: [
          TextSourceAttribution(
            'OpenStreetMap contributors',
            onTap: () => launchUrl(Uri.parse('https://openstreetmap.org/copyright')), // (external)
          ),
          // Also add images...
        ],
      ),
    ],
  );
}
```

{% endcode %}

{% hint style="warning" %}
This map uses the OpenStreetMap public tile servers, which are NOT free to use by everyone.
{% endhint %}

## Get Help

Not quite sure about something? No worries, we're here to help!

* Check the [#faqs](#faqs "mention") below, and double check the documentation
* Then, for bug reports & feature requests: check for previous issues, then ask in [GitHub Issues](https://github.com/fleaflet/flutter_map/issues)
* Then, for support & everything else: ask in [flutter\_map Discord server](https://discord.gg/BwpEsjqMAH) or [GitHub Discussions](https://github.com/fleaflet/flutter_map/discussions)

{% hint style="info" %}
We're a community maintained project and the maintainers would greatly appreciate any help in implementing features and fixing bugs! Feel free to jump in: <https://github.com/fleaflet/flutter_map/blob/master/CONTRIBUTING.md>.

Please remember that we are volunteers and cannot guarantee support. The standard Code of Conduct is here to keep our community a safe and friendly place for everyone: <https://github.com/fleaflet/flutter_map/blob/master/CODE_OF_CONDUCT.md>.
{% endhint %}

### FAQs

We get quite a lot of similar questions, so please check if your question is here before you ask!

{% content-ref url="/pages/lL0cVxkdhQ8OmMlS1zdD" %}
[How Does It Work?](/why-and-how/how-does-it-work)
{% endcontent-ref %}

<details>

<summary>How can I use a custom map style?<br>How can I prevent POI/labels rotating when the map rotates?<br>How can I remove certain POI/labels from the map?</summary>

Unfortunately, this library cannot change the tiles you give it: it has no control over the tiles displayed in the `TileLayer`. This is a limitation of the technology, not this library.

This is because raster tiles are just images generated by a 3rd party tile server (dictated by your URL template), and therefore cannot be changed by the library that displays the tiles. Filters can be applied to the entire tile image, such as an emulated dark mode.

However, tilesets can be styled. This is the most effective way of using custom styles. These methods may help you with this:&#x20;

* You may wish to use a commercial service like Mapbox Studio, which allows you to style multiple tilesets. See [Using Mapbox](/tile-servers/using-mapbox).
* Alternatively, you can experiment with vector tiles. These are not pre-rendered, and so allow any style you desire to be applied on the fly. See [Raster vs Vector Tiles](/why-and-how/how-does-it-work/raster-vs-vector-tiles#vector-tiles).
* Your last option is to serve tiles yourself. See [Other Options](/tile-servers/other-options).

</details>

<details>

<summary>How can I route a user between two locations?<br>Why does the <code>Polyline</code> only go in a straight line between two points?</summary>

See [Polyline Layer](/layers/polyline-layer#routing-navigation).

</details>

<details>

<summary>How can I add a <code>Marker</code> where the user's location is?<br>How can I center the map on the user's location?</summary>

This is beyond the scope of flutter\_map. However, you can use the [community maintained plugin 'flutter\_map\_location\_marker'](https://github.com/tlserver/flutter_map_location_marker) to do this.

Alternatively, use the 'location' and 'compass' packages to generate a stream of the user's location and heading, and feed that to a `Marker` using a `StreamBuilder`.

</details>

<details>

<summary>Why don't any map tiles appear?</summary>

Check the debugging steps in [Installation](/getting-started/installation#platform-configuration).

</details>

<details>

<summary>Why are the layers glitching on some platforms?</summary>

Check the debugging steps in [Installation](/getting-started/installation#platform-configuration).

</details>

<details>

<summary>Why does the map disappear/go grey when I zoom in far?<br>Why does the map stop zooming in even though I know there are more zoom levels?</summary>

If tiles are disappearing when you zoom in, the default grey background of the `FlutterMap` widget will shine through. This usually means that the tile server doesn't support these higher zoom levels.

If you know that there are more tiles available further zoomed in, but flutter\_map isn't showing them and scaling a particular zoom level instead, it's likely because the `TileLayer.maxNativeZoom` property is set too low (it defaults to 19).

To set/change the zoom level at which FM starts scaling tiles, change the `TileLayer.maxNativeZoom` property. To set/change the max zoom level that can actually be zoomed to (hard limit), use `MapOptions.maxZoom`.

</details>

<details>

<summary>How can I make the map 3D, or view it as a globe?</summary>

Unfortunately, this isn't supported, partially due to lack of time on the maintainer's part to implement this feature, partially due to technical limitations. PRs are welcome!

</details>

[^1]: See below for information about vector tile support.

[^2]: It may cost more to use services which provide their own SDKs through flutter\_map, but there's a reason they do that ;)


# Showcase & Case Studies

We think flutter\_map hits the spot for many Flutter apps: [flutter\_map](/#why-choose-flutter_map). But don't just take it from us - check out who else is using flutter\_map, and what they think of it!

## Community Studies

{% embed url="<https://archive.org/details/incorporating-maps-into-flutter-a-study-of-mapping-sdks-and-their-integration-in>" fullWidth="false" %}
Original: <https://www.theseus.fi/bitstream/handle/10024/820026/Ushakov_Sergey.pdf>\
Archive: <https://archive.org/details/incorporating-maps-into-flutter-a-study-of-mapping-sdks-and-their-integration-in>
{% endembed %}

{% embed url="<https://player.vimeo.com/video/995398561?autopause=0&autoplay=0&byline=0&color=00adef&portrait=0&title=0>" fullWidth="false" %}
A talk at Fluttercon Europe 2024 comparing solutions to displays markers on maps between different SDKs, with a look towards flutter\_map's future at the end\
Original: <https://www.droidcon.com/2024/09/03/the-quest-to-display-widget-markers-on-a-map/>
{% endembed %}

## Project Showcase

These great projects all make use of flutter\_map!

But there's more: check out the [dependents list on GitHub](https://github.com/fleaflet/flutter_map/network/dependents), and the <kbd>#showcase</kbd> channel for more projects on our Discord server.

### Non-OSS Sponsors

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Map Marker</strong></td><td>Your places organizer</td><td><a href="/files/l8EEhkIz3wMTGGIVeV2j">/files/l8EEhkIz3wMTGGIVeV2j</a></td><td><a href="https://www.mapmarker.app/">https://www.mapmarker.app/</a></td></tr><tr><td><strong>Your advert here!</strong></td><td><p>Want to advertise your project here? For more information, and to apply, please see:</p><ul><li><a href="https://docs.google.com/forms/d/e/1FAIpQLSeXALT0XVnWdl8vTcYQUz9l3mC7j63Et1MIkkEnnn7BgToRtw/viewform?usp=sf_link">The application form for non-OSS projects</a></li><li><a href="https://docs.google.com/forms/d/e/1FAIpQLSeM3RgRc-QG7diODXd29DzuQWAWlutUQC3uR_b0cSwbaOkOjg/viewform?usp=sf_link">The application form for OSS and/or non-profit projects</a> (it's free!)</li></ul></td><td><a href="/files/SXX6mr1Eq9i7kTxfiNmF">/files/SXX6mr1Eq9i7kTxfiNmF</a></td><td></td></tr></tbody></table>

### OSS and/or Non-Profit Projects

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th><select multiple><option value="288089d75dba4cc2930ef1e879241692" label="Open Source" color="blue"></option><option value="602c8c9d2d4b4c9bbdc1b704dbed0fe5" label="100% Non-Profit" color="blue"></option></select></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><strong>Every Door</strong></td><td><span data-option="288089d75dba4cc2930ef1e879241692">Open Source, </span><span data-option="602c8c9d2d4b4c9bbdc1b704dbed0fe5">100% Non-Profit</span></td><td>The most efficient OpenStreetMap editor for surveying shops and benches</td><td><a href="https://every-door.app/">https://every-door.app/</a></td><td><a href="/files/gLjBq3BCAhLGPaTuLrFe">/files/gLjBq3BCAhLGPaTuLrFe</a></td></tr><tr><td><strong>Ente Photos</strong></td><td><span data-option="288089d75dba4cc2930ef1e879241692">Open Source</span></td><td>End-to-end encrypted alternative to Google Photos </td><td><a href="https://ente.io/">https://ente.io/</a></td><td><a href="/files/muAERaZeGhotv3DmmbyK">/files/muAERaZeGhotv3DmmbyK</a></td></tr><tr><td>🇳🇱 <strong>Stichting Zeilvaart Warmond (Track &#x26; Trace)</strong></td><td><span data-option="602c8c9d2d4b4c9bbdc1b704dbed0fe5">100% Non-Profit</span></td><td>Follow participating ships during the race, and use the replay function after the race to analyse the performance</td><td><a href="https://tt.zeilvaartwarmond.nl/">https://tt.zeilvaartwarmond.nl/</a></td><td><a href="/files/SCR0oO1a99zssPP4PPjI">/files/SCR0oO1a99zssPP4PPjI</a></td></tr><tr><td><strong>Your advert here!</strong></td><td></td><td><p>Want to advertise your project here? For more information, and to apply, please see:</p><ul><li><a href="https://docs.google.com/forms/d/e/1FAIpQLSeXALT0XVnWdl8vTcYQUz9l3mC7j63Et1MIkkEnnn7BgToRtw/viewform?usp=sf_link">The application form for non-OSS projects</a></li><li><a href="https://docs.google.com/forms/d/e/1FAIpQLSeM3RgRc-QG7diODXd29DzuQWAWlutUQC3uR_b0cSwbaOkOjg/viewform?usp=sf_link">The application form for OSS and/or non-profit projects</a> (it's free!)</li></ul></td><td></td><td><a href="/files/SXX6mr1Eq9i7kTxfiNmF">/files/SXX6mr1Eq9i7kTxfiNmF</a></td></tr></tbody></table>


# How Does It Work?

A high level overview for those new to 'web' maps

{% hint style="success" %}
Unlike other popular mapping solutions, flutter\_map doesn't come with an opinion on the best map style/tiles to use, so you'll need to **bring your own tiles** - either using a service, such as those listed in Tile Servers, or by creating and using your own custom ones!

We then allow you to add more on top of these tiles, and control and customize as far as you need.

**It's a client to display** [**'tiled & WMS web' maps**](https://en.wikipedia.org/wiki/Tiled_web_map) **and other map features - not a map itself.**
{% endhint %}

{% stepper %}
{% step %}

### 👀 Tile Layer

The basis of a map is the layer which shows square images, known as 'tiles'. When placed adjacent, this creates a single map! This can be panned (moved), rotated, and zoomed, to load new tiles dynamically. To show more detail, more images of the same dimensions are loaded in place.

<figure><img src="/files/nwpp1RZEkonQC2p1J1bJ" alt=""><figcaption><p><a href="https://commons.wikimedia.org/wiki/File:XYZ_Tiles.png">https://commons.wikimedia.org/wiki/File:XYZ_Tiles.png</a></p></figcaption></figure>

There's loads of ways to source (see Tile Servers), store ([Raster vs Vector Tiles](/why-and-how/how-does-it-work/raster-vs-vector-tiles)), and reference (eg. XYZ vs WMS) tiles! We support most of them, *except vector tiles*. This documentation primarily uses examples referring to [Slippy Maps](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames) implemented with XYZ referencing, but we also support many other kinds of maps.

However, you don't need to worry about most of this! Just follow the instructions from your source and it's easy to figure out how to use them in flutter\_map.

See [Tile Layer](/layers/tile-layer)for more information.
{% endstep %}

{% step %}

### 🤩 More Layers... More... More...

You can put any other kind of layer (or `Widget`) on top of your `TileLayer`. You can even put another `TileLayer` in! See our [Layers](#layers) catalogue, [make your own layers](/plugins/create/layers) directly in Flutter, or use one of the excellent [community-maintained plugins](/plugins/list)!
{% endstep %}

{% step %}

### 🛠️ Configure The Map

Once it looks how you imagined, you need it to act like you imagined. flutter\_map provides comprehensive customizability for gesture/interactivity control & initial positioning. See [Options](/usage/options) for more info.
{% endstep %}

{% step %}

### 🎮 Control The Map

You can also control the map programmatically using a simple controller pattern. See [Programmatic Interaction](/usage/programmatic-interaction) for more info.
{% endstep %}
{% endstepper %}

{% hint style="info" %}
Most map client libraries will work in a similar way, so if you've used [leaflet.js](https://leafletjs.com/) or [OpenLayers](https://openlayers.org/) before, you'll be right at home :smile:.
{% endhint %}

See the code demo on the landing page to see how easy it is and how it all fits together in code, and see what's possible in our example app.

{% content-ref url="/pages/YICQhcdQtAOWmU2QJcuB" %}
[flutter\_map](/)
{% endcontent-ref %}

{% content-ref url="/pages/fk8bCmKls3TyJr7oCUWG" %}
[Demo & Examples](/why-and-how/demo-and-examples)
{% endcontent-ref %}


# Raster vs Vector Tiles

{% hint style="info" %}
It is important to note that 'flutter\_map' only supports raster tiles natively. Vector tiles can be used with a community maintained plugin.

This is described in more detail at the bottom of this page.
{% endhint %}

There are 2 main types of tiles a server can serve: raster and vector; each has their own advantages and drawbacks. This page is designed to help you choose a type for your app, and help you use vector tiles if you choose to.

## Raster Tiles

Raster tiles are the 'older' type of tile, and are raster images (usually .png or .jpg). These tiles are good because they can render quickly and easily, can be viewed without special software, and are readily available from most mapping services. As such, this makes them the popular choice for beginners.

However, raster tiles cannot be easily themed: a theme needs a whole new set of map tiles. This makes apps using light and dark themes have mismatching maps. As well as this, raster tiles usually have larger file sizes meaning slower download times, and they can become blurred/pixelated when viewed at a larger scale: a problem for users when zooming between zoom levels. Another issue is that shapes/text inside tiles cannot be rotated, hence the name 'static tiles': therefore, rotating the map will not rotate the name of a road, for example.

## Vector Tiles

Vector tiles can be considered the 'newer' standard. These images might contain a specialised format (such as .pbf) dictating the mathematics and coordinates used to draw lines and shapes. Because these tiles are drawn at render time instead of at request/server time, theming can be used to make the map fit in better with an app's theme. The math-based image means that the images/tiles can be scaled without any loss of clarity.

However it does add complexity to the rendering process as each element needs to be parsed and painted individually, meaning an impact to performance. Text elements and certain shapes can also be rotated (unlike raster tiles) to match the user's orientation, not the orientation of the map; but calculating this rotation needs to be done every frame, meaning an even larger impact on performance.

### Using Vector Tiles

Due to the complications mentioned above, 'flutter\_map' does not natively support vector tiles. However, vector tiles can be used with a [community maintained plugin (`vector_map_tiles`)](https://github.com/greensopinion/flutter-vector-map-tiles) to do this.

{% hint style="warning" %}
Worried about vector tiles performance?

Using vector tiles may significantly cut FPS and introduce jank, and that's because of the amount of UI work that must be performed on the main thread.

The community and FM maintainers are looking to improve the situation!

Keep up to date and subscribe to the issue: <https://github.com/greensopinion/flutter-vector-map-tiles/issues/120>.
{% endhint %}


# Demo & Examples

flutter\_map provides an example application showcasing much of its functionality. In some cases, the example app contains undocumented functionality, so it's definitely worth checking out!

{% hint style="success" %}
For a quick code demo, check out the landing page: [flutter\_map](/#code-demo)!
{% endhint %}

## Web Demo

{% hint style="info" %}
Note that the web demo is built automatically from the ['master' branch](https://github.com/fleaflet/flutter_map), so may not reflect the the latest release on pub.dev.
{% endhint %}

{% hint style="warning" %}
Please don't abuse the web demo! It runs on limited bandwidth and won't hold up to thousands of loads.

If you're going to be straining the application, please see [#prebuilt-artifacts](#prebuilt-artifacts "mention"), and serve the application yourself.
{% endhint %}

{% embed url="<https://demo.fleaflet.dev>" %}

## Prebuilt Artifacts

If you can't build from source for your platform, our GitHub Actions CI system compiles the example app to GitHub Artifacts for Windows, Web, and Android.

The Windows and Android artifacts just require unzipping and installing the .exe or .apk found inside.

The Web artifact requires unzipping and serving, as it contains more than one unbundled file. You may be able to use [dhttpd](https://pub.dev/packages/dhttpd) for this purpose.

{% hint style="info" %}
Note that these artifacts are built automatically from the ['master' branch](https://github.com/fleaflet/flutter_map), so may not reflect the the latest release on pub.dev.
{% endhint %}

{% embed url="<https://nightly.link/fleaflet/flutter_map/workflows/master/master>" %}
Latest Build Artifacts (thanks [nightly](https://nightly.link/))
{% endembed %}

## Build From Source

If you need to use the example app on another platform, you can build from source, using the 'example' directory of the repository.

{% @github-files/github-code-block url="<https://github.com/fleaflet/flutter_map/tree/master/example>" %}


# What's New In v8.2?

## Overview

Here's some highlights since v8.0:

{% hint style="success" %}

## **Automatically enabled lightweight built-in caching & aborting of in-flight HTTP requests for obsolete tiles**

The `NetworkTileProvider` has had a massive functionality boost!

Built-in caching helps you, your users, and the tile server, and is enabled by default for maximum ease. You could also switch it out with a custom implementation, or disable it altogether if you prefer. Find out more in [Caching](/layers/tile-layer/caching).

You may be using the `CancellableNetworkTileProvider`, which allowed in-flight HTTP requests to be aborted when the tiles would no longer be displayed, helping to improve performance and usability. Unfortunately, it isn't compatible with built-in caching. Fortunately, it's also been deprecated - its functionality is now available in the core! 'package:http' v1.5.0-beta ([#1773](https://github.com/dart-lang/http/pull/1773)) supports aborting requests with the 3 default clients natively, so Dio is no longer required. This makes it easier for you and for us!
{% endhint %}

{% hint style="success" %}

## &#x20;**Inverted filling for `PolygonLayer` & multi-yet-single world support for `Poly*Layer`**

This continues the work on multi-world support (thanks monsieurtanuki), and fixes an issue that occured where users used a `Polygon` covering the entire world, with holes as cut outs.

*This feature was bounty-funded, thanks to our generous* [Supporters](/thanks/supporters)*, and the community! We hope to open more bounties in future.*
{% endhint %}

{% hint style="success" %}

## No more grey background at the North and South edges of your map (optionally)

Thanks to the community, a new `ContainCameraLatitude` `CameraConstraint` is available, which keeps just the world in view at all times. At the moment, it still needs enabling manually.

Check out the effect in our demo for [multi-world functionality](https://demo.fleaflet.dev/repeated_worlds). You can enable it in your project just by passing `CameraConstraint.containLatitude()` to the `MapOptions.cameraConstraint` option.
{% endhint %}

{% hint style="success" %}

## Polygon label placement improvements

This is split into 3 parts:

* The old method of picking a placement algorithm has been deprecated and been replaced with a new, extensible system - it's still just as easy to use as the old one
* Thanks to the community, a new placement algorithm has been added: an improved centroid algorithm using the 'signed area centroid' algorithm - this is the new default, but the old algorithm is still available
* The polylabel placement algorithm has been given a fresh lick of paint and uses a more modern Dart implementation to improve performance and customizability

See how to migrate to the new system below.
{% endhint %}

{% hint style="success" %}

## Documentation improvements

This documentation has also had a bit of a renewal!

* Follow the new guide to setup a `TileLayer` as we recommend: [Tile Layer](/layers/tile-layer#recommended-setup). More to come soon!
* The guide for interactive layers has been simplified, reworked, and example added. Check it out: [Layer Interactivity](/layers/layer-interactivity).
* We've added some information about using flutter\_map with the OpenStreetMap public tile servers: [Using OpenStreetMap (direct)](/tile-servers/using-openstreetmap-direct).
  {% endhint %}

{% hint style="warning" %}

## Information will appear in console when a `TileLayer` is loaded using one of the OpenStreetMap tile servers (in debug mode)

Additionally, where an appropriate User-Agent header (which identifies your app to the server) is not set - for example, through `TileLayer.userAgentPackageName`, or directly through the tile provider's HTTP headers configuration - a warning will appear in console (in debug mode), advising you to set a UA.

In future, we may block users which do not set a valid UA identifier for this server.

For more information, see [Using OpenStreetMap (direct)](/tile-servers/using-openstreetmap-direct).
{% endhint %}

That's already a lot, but it's only scratching the surface. Alongside the community, we've improved our example app, [reduced the size of our demo & package](https://github.com/fleaflet/flutter_map/pull/2056), [added even more customizability and fine-grained control](#user-content-fn-1)[^1] - not even to mention the multiple bug fixes and other performance improvements.

Why not check out the CHANGELOG for the full list of curated changes, and the full commit and contributor listing if you like all the details:

{% embed url="<https://pub.dev/packages/flutter_map/changelog>" %}
CHANGELOG
{% endembed %}

{% embed url="<https://github.com/fleaflet/flutter_map/compare/v8.1.1...v8.2.0>" %}

{% hint style="info" %}
For completeness, here were the highlights from v8.0:

* Unbounded horizontal scrolling
* Keyboard controls for map gestures
* Performance improvements (particularly with `Polygon/lineLayer`)
  {% endhint %}

## Migration

### To v8.2

{% hint style="success" %}
v8.2 doesn't contain any API breaking changes, but it does contain deprecations and a small change in potential display output - we suggest preparing for the next breaking release whenever you can
{% endhint %}

<details>

<summary>Changes to <code>Polygon</code> label placement</summary>

It's usually simple to follow the deprecation messages/warnings in your IDE. The changes are described here for completeness.

There's two main changes:

* The default placement algorithm has been changed\
  The new default algorithm adopts the old name (`centroid`), with the old name becoming `simpleCentroid` - it's an improvement over the old algorithm
* The `Polygon.labelPlacement` property & `PolygonLabelPlacement` enum have been deprecated, replaced with `Polygon.labelPlacementCalculator` and `PolygonLabelPlacementCalculator`  respectively

Here's the mapping of old enum values to new objects:

* old default / `.centroid` -> `.centroid()` (new algorithm)
* `.centroidWithMultiWorld` -> `.simpleMultiWorldCentroid()`
* `.polylabel` -> `.polylabel()`
* (new) `.simpleCentroid()`

{% hint style="warning" %}
Note that only the `simpleMultiWorldCentroid` calculator supports polygons which may lie across the anti-meridian.
{% endhint %}

</details>

<details>

<summary>Deprecation of official <code>CancellableNetworkTileProvider</code> plugin</summary>

As described above, its primary purpose is now fulfilled by default in the `NetworkTileProvider`. You can switch back to that and remove the dependency from your project.

</details>

### To v8.0

{% hint style="success" %}
**Migrating to v8 should be pain-free for most apps, but some major changes are likely for plugins.**

Some breaking changes have been made. The amount of migration required will depend on how much your project uses more advanced functionality. Basic apps are unlikely to require migration.
{% endhint %}

<details>

<summary>Most uses of <a href="https://api.flutter.dev/flutter/dart-math/Point-class.html"><code>Point&#x3C;double></code></a> replaced by <a href="https://api.flutter.dev/flutter/dart-ui/Offset-class.html"><code>Offset</code></a> &#x26; <a href="https://api.flutter.dev/flutter/dart-ui/Size-class.html"><code>Size</code></a>, <a href="https://pub.dev/documentation/flutter_map/7.0.1/flutter_map/Bounds-class.html"><code>Bounds&#x3C;double></code></a> by <a href="https://api.flutter.dev/flutter/dart-ui/Rect-class.html"><code>Rect</code></a>, and <a href="https://pub.dev/documentation/flutter_map/7.0.1/flutter_map/Bounds-class.html"><code>Bounds&#x3C;int></code></a> by <code>(IntegerBounds)</code></summary>

With the exception of some areas, uses of 'dart:math' objects, such as `Point`, have been replaced by equivalents from 'dart:ui' and Flutter libraries. There's multiple reasons for this:

* These classes have been described as [legacy since Feb 2024](https://github.com/dart-lang/sdk/commit/885126e51bf2d0c612a42ba55395ac4f4d9f7b42) in Dart/Flutter, and will be [deprecated in future](https://github.com/dart-lang/sdk/issues/54852)
* This reduces internal casting (which we did a whole lot) and usage of generic types ([which are inefficient](https://github.com/dart-lang/sdk/issues/53912)), which has increased performance by around a millisecond or three (in a simple example)
* The tooling and functionality provided by Dart/Flutter reduce the amount we need to maintain internally (reducing duplication), and work better together (such as easily building `Rect`s from `Offset`s and `Size`s

This breaks a large number of coordinate handling functions, such as those converting between geographic coordinates and screen space coordinates (the changed ones) in the `MapCamera`. We've also renamed some of these functions to remove references to 'point' and replace them with 'offset'.

Most migrations should be self explanatory. If necessary, you can [view the PR](https://github.com/fleaflet/flutter_map/pull/1996) to see what happened to a method you were using - there's very likely a close replacement! Some methods have been moved to internal usage only, but there's always easy alternatives.

Some external libraries still use the previous objects, and some of our use-cases are just not yet ready to be replaced by these options yet, so you may still find some of the old objects hiding around the codebase. `IntegerBounds` is internal only.

</details>

<details>

<summary><code>TileLayer.tileSize</code> replaced by <code>tileDimension</code></summary>

Just changing the argument identifier should be enough - we've just restricted the type to be an integer. You can't get tiles in fractional pixels anyway!

This renaming is also persisted throughout the internals.

</details>

[^1]: such as [#2070](https://github.com/fleaflet/flutter_map/pull/2070) & [#2101](https://github.com/fleaflet/flutter_map/pull/2101)


# Installation

## Depend On It

Depend on flutter\_map from [pub.dev](https://pub.dev/packages/flutter_map/install) as normal! Use the command line or add the dependency manually to your pubspec.yaml.

```sh
flutter pub add flutter_map latlong2
```

<details>

<summary>Depend from GitHub</summary>

{% hint style="warning" %}
Unreleased commits from the source repo may be unstable.
{% endhint %}

If you urgently need the latest version, a specific branch, or a specific fork, you can use this method.

We recommend depending on us as normal, then adding the following lines to your pubspec, as a new root object:

{% code title="pubspec.yaml" %}

```yaml
dependency_overrides:
    flutter_map:
        git:
            url: https://github.com/fleaflet/flutter_map.git
            # ref: master (or commit hash, branch, or tag)
```

{% endcode %}

</details>

## Platform Configuration

{% hint style="success" %}
Most apps that already communicate over the Internet won't need to change their configuration.
{% endhint %}

{% tabs %}
{% tab title="Android" %}
Add the following line to `android\app\src\main\AndroidManifest.xml` to enable the INTERNET permission in release builds.

<pre class="language-xml"><code class="lang-xml">&#x3C;manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
<strong>    &#x3C;uses-permission android:name="android.permission.INTERNET"/>
</strong>    ...
&#x3C;/manifest>
</code></pre>

{% endtab %}

{% tab title="Web" %}

#### Wasm/Renderer

{% hint style="success" %}
We support Wasm! Build and run your app with the '-wasm' flag and benefit from potentially improved performance when the browser can handle Wasm.
{% endhint %}

#### CORS

On the web platform, [CORS ](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)restrictions designed to protect resources on websites and control where they can be loaded from. Some tile servers may not be intended for external consumption, or may be incorrectly configured, which could prevent tiles from loading. If tiles load correctly on platforms other than the web, then this is likely the cause.

See the [Flutter documentation](https://docs.flutter.dev/platform-integration/web/web-images#cross-origin-resource-sharing-cors) for more details. We load images using a standard `Image` widget.
{% endtab %}

{% tab title="MacOS" %}
Add the following lines to `macos/Runner/Release.entitlements`**:**

<pre class="language-xml"><code class="lang-xml">...
&#x3C;dict>
    ...
<strong>    &#x3C;key>com.apple.security.network.client&#x3C;/key>
</strong><strong>    &#x3C;true/>
</strong>    ...
&#x3C;/dict>
...
</code></pre>

{% endtab %}
{% endtabs %}

{% hint style="info" %}

## Having issues loading tiles?

1. Check you've correctly configured your `TileLayer`: [Tile Layer](/layers/tile-layer)
2. Check you've followed the steps above for your platform
3. Use Flutter DevTools on native platforms, or the browser devtools on web, and check the HTTP responses of tile requests
4. Try requesting a tile manually using your browser or a command line utility which supports setting any required headers (for example, for authorization)
   {% endhint %}

{% hint style="info" %}

## Map looking wrong or layers glitching?

If you're testing on a platform which is using [Impeller](https://docs.flutter.dev/perf/impeller), try running the app without Impeller.

If you're not sure whether you're running with Impeller on mobile (particularly on Android devices where support is patchy), check the first lines of the console output when you run your app in debug mode.

```sh
flutter run --no-enable-impeller
```

If this resolves the issue, unfortunately there's nothing flutter\_map can do. We recommend reporting the issue to the Flutter team, and reaching out to us on the flutter\_map Discord server so we can support reproduction and resolution.

***

If you're running on the web, some features may not work as expected due to limitations or bugs within Flutter. For example, check the [Polygon Layer](/layers/polygon-layer) documentation.
{% endhint %}


# Base Widget

```dart
FlutterMap(
    mapController: MapController(),
    options: MapOptions(),
    children: [],
);
```

Start by adding some [Layers](/layers/tile-layer) to `children`, then configure the map in [Options](/usage/options). Additionally, if required, add a `MapController`: [Broken mention](broken://pages/UW2gppPcXFfE46FRhWT6).

{% hint style="info" %}
The map widget will expand to fill its constraints. To avoid errors about infinite/unspecified sizes, ensure the map is contained within a constrained widget.
{% endhint %}

### Keep Alive

If the map is displayed lazily in something like a `PageView`, changing the page and unloading the map will cause it to reset to its [initial positioning](/usage/options#initial-positioning).

To prevent this, set `MapOptions.keepAlive` `true`, which will activate an internal `AutomaticKeepAliveClientMixin`. This will retain the internal state container in memory, even when it would otherwise be disposed.


# Unbounded Horizontal Scrolling

Since v8 & v8.2, flutter\_map supports unbounded horizontal (longitudinally) scrolling for the default map projection. This means users can keep scrolling left and right (when North is up) and never hit an edge! Feature layers, such as the `PolygonLayer`, can also take advantage of this functionality.

<div data-full-width="true"><figure><img src="/files/HVaWxuaTXSOEar3PQfuE" alt=""><figcaption></figcaption></figure></div>

## Enabling/disabling unbounded horizontal scrolling

Within the codebase, unbounded horizontal scrolling is referred to as `replicatesWorldLongitude`, and is set on the CRS/projection level.

The default projection, `Epsg3857`, enables the functionality by default.

<details>

<summary>Disabling unbounded horizontal scrolling</summary>

To disable the functionality, change the projection. If you want to keep using `Epsg3857`, create the following class, and pass it to `MapOptions.crs`:

```dart
class Epsg3857NoRepeat extends Epsg3857 {
  const Epsg3857NoRepeat();

  @override
  bool get replicatesWorldLongitude => false;
}
```

</details>

## Constraining the camera vertically/latitudinally

It's now possible to remove the grey edges that appear at the top and bottom of the map when zoomed far out.

To do this, set the `MapOptions.cameraConstraint` parameter:

```dart
FlutterMap(
    options: MapOptions(
      cameraConstraint: const CameraConstraint.containLatitude(),
    ),
    children: [],
),
```

## Feature layers & multi-worlds

Each square of map that is repeated longitudinally is referred to as a "world". By default, the feature layers (for example, `PolygonLayer`, `PolylineLayer`, `CircleLayer`, and `MarkerLayer`) will repeat their features across the layers, so that each world looks identical.

In the `PolylineLayer` & `PolygonLayer`, this can be disabled by setting the `drawInSingleWorld` property.

<div data-full-width="true"><figure><img src="/files/JNJhMGjpK1raaap2xcQm" alt=""><figcaption><p><code>drawInSingleWorld: false</code> (default)</p></figcaption></figure> <figure><img src="/files/THZX5nn5cQE87xZODH6b" alt=""><figcaption><p><code>drawInSingleWorld: true</code></p></figcaption></figure></div>


# Options

To dictate & restrict what the map can and should do, regardless of its contents, it needs some guidance!

It provides options that can be categorized into three main parts:

* [Initial positioning](#initial-positioning)\
  Defines the location of the map when it is first loaded
* [Permanent rules](#permanent-rules)\
  Defines restrictions that last throughout the map's lifetime
* [Event handling](/usage/programmatic-interaction#reacting-to-map-events)\
  Defines methods that are called on specific map events

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/MapOptions-class.html>" %}

## Initial Positioning

{% hint style="info" %}
Changing these properties after the map has already been built for the first time will have no effect: they only apply on initialisation.

To control the map programatically, use a `MapController`: [Broken mention](broken://pages/UW2gppPcXFfE46FRhWT6).
{% endhint %}

One part of `MapOptions` responsibilities is to define how the map should be positioned when first loaded. There's two ways to do this (that are incompatible):

* `initialCenter` (`LatLng`) & `initialZoom`
* `initialCameraFit`
  * by bounds (circumscribed[^1]): `CameraFit.bounds`
  * by bounds (inscribed[^2]): `CameraFit.insideBounds`
  * by coordinates (circumscribed[^3]): `CameraFit.coordinates`

It is possible to also set the map's `initialRotation` in degrees, if you don't want it North (0°) facing initially.

If rotation is enabled/allowed, if using `initialCameraFit`, prefer defining it by coordinates for a more intended/tight fit.

## Permanent Rules

One part of `MapOptions` responsibilities is to define the restrictions and limitations of the map and what users can/cannot do with it.

Some of the options are described elsewhere in this documentation, in context. In addition, the API docs show all the available options, and below is a partial list of options:

* `cameraConstraint`
  * camera bounds inside bounds: `CameraConstraint.bounds`
  * camera center inside bounds: `CameraConstraint.center`
  * *unconstrained (default): `CameraConstraint.unconstrained`*
* `maxZoom` and `minZoom`\
  Sets a hard limit on the maximum and minimum amounts that the map can be zoomed
* [`interactionOptions`](/usage/options/interaction-options)\
  Configures the gestures that the user can use to interact with the map - for example, disable rotation or configure cursor/keyboard rotation

{% hint style="success" %}
Instead of `maxZoom` (or in addition to), consider setting `maxNativeZoom` per `TileLayer` instead, to allow tiles to scale (and lose quality) on the final zoom level, instead of setting a hard limit.
{% endhint %}

## Custom CRS

FM does have some support for using alternative CRSs.&#x20;

{% content-ref url="/pages/nTZ2CIbH2eECsJUZS0im" %}
[Custom CRSs](/usage/options/custom-crss)
{% endcontent-ref %}

[^1]: Bounds inside camera

[^2]: Camera inside bounds

[^3]: Coordinates inside camera, as tightly as possible


# Interaction Options

The `InteractionOptions` object passed to `MapOptions.interactiveOptions` configures the gestures that the user can use to interact with the map. For example, disable rotation or configure cursor/keyboard rotation.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/InteractionOptions-class.html>" %}

## Flags

`flags` is a [bitfield](https://en.wikipedia.org/wiki/Bit_field) that enables and disables the vast majority of gestures. Although technically the type is of `int`, it is usually set with a combination of `InteractiveFlag`s.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/InteractiveFlag-class.html>" %}

{% hint style="warning" %}
Note that some gestures must be configured by other means, either instead of using flags, or in addition to.
{% endhint %}

By default, `all` gestures are enabled, but a non-interactive map can be created using `none` (and other options in addition).

{% hint style="info" %}
The recommended way to create an entirely non-interactive map is to wrap the `FlutterMap` widget in an `IgnorePointer` widget.
{% endhint %}

Otherwise, to set flags, there's two methods:

* Allow ONLY specified interactions, with the bitwise 'OR' (`|`) operator\
  For example, `InteractiveFlag.drag | InteractiveFlag.rotate`, allows the map to ONLY be dragged or rotated (by the touchscreen), except any other options configured separately (such as keyboard options)
* Disable specified interactions, using the `&` and `~` operators\
  For example, `~InteractiveFlag.rotate` (which is equivalent to `InteractiveFlag.all & ~InteractiveFlag.rotate`)&#x20;

For example, to disable flinging:

```dart
options: MapOptions(
    interactionOptions: InteractionOptions(
        flags: ~InteractiveFlag.flingAnimation,
    ),
),
```

## Cursor/Keyboard Rotation

Cursor/keyboard rotation is designed for desktop platforms, and allows the cursor to be used to set the rotation of the map whilst a (customizable) keyboard key (by default, any of the 'Control' keys) is held down.

The `CursorKeyboardRotationOptions` object passed to the property with the corresponding name configures this behaviour. The `CursorKeyboardRotationOptions.disabled()` constructor can be used to disable cursor/keyboard rotation.

There's many customization options, see the API docs for more information:

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/CursorKeyboardRotationOptions-class.html>" %}

## Keyboard Gestures

Keyboard gestures can be configured through `KeyboardOptions`. By default, the map can be panned via the arrow keys. Additionally, panning using the WASD keys can be enabled, as well as rotation with Q & E, and zooming with R & F. All keys are physical and based on the QWERTY keyboard, so on other keyboards, the positions will be the same, not necessary the characters.

Leaping occurs when the trigger key is pressed momentarily instead of being held down. This can also be customized.

## "Win" Gestures

{% hint style="warning" %}
This is advanced behaviour that affects how gestures 'win' in the gesture arena, and does not usually need changing.
{% endhint %}


# Custom CRSs

## Projection

To define a `Proj4Crs` (custom CRS) you have to register a projection of `proj4.Projection`. For that you must import `proj4dart` library as follows:

```dart
import 'package:proj4dart/proj4dart.dart' as proj4;
```

You can create and register your custom projection in multiple ways, but the recommended is to use a **Proj4 definition string** from [epsg.io](https://epsg.io). For example for `EPSG:3413` (*WGS 84 / NSIDC Sea Ice Polar Stereographic North*) you can find it [here](https://epsg.io/3413.proj4). This is how a Proj4 definition string looks like:

```dart
+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m + no_defs
```

With this **Proj4 definition string** and a **string identifier** register your `proj4.Projection` like this:

```dart
var customProjection = proj4.Projection.add('EPSG:3413',
    '+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
```

For more possible ways to register `proj4.Projection` see [proj4dart documentation](https://github.com/maRci002/proj4dart).

## Coordinate Reference System (CRS)

You can use your previously registered `proj4.Projection` to create a custom CRS of type `Proj4Crs`. You can use the following parameters:

* `<String>` `code` (required): string identifier for the selected CRS, e.g. `EPSG:3413`
* `<proj4.Projection>` `proj4Projection` (required): the `proj4.Projection` object you wish to use
* `<Bounds>` `bounds`: bounds of the CRS in projected coordinates
* `<List<double>>` `resolutions`: an array of zoom factors (projection units per pixel, eg. meters/pixel)
* `<List<double>>` `scales`: scale factors (pixels per projection unit); specify either scales or resolutions, but not both!
* `<List<Point>>` `origins`: tile origin in projected coordinates (for TileLayer). Why is it needed? GeoServer by default can define different origins (top left coordinates) for each zoom levels. In case of origin mismatch the tile will be drawn on the wrong place: the map will jump at such zoom levels. If your origins vary with zoom levels the number of origins must match the number of resolutions. You can get the desired origins from a `GetCapabilities` WMTS call from geoserver e.g. `http://[ip:port]/geoserver/gwc/service/wmts?request=GetCapabilities`. This results an XML, and you have to look up for the `TopLeftCorner`s for each TileMatrix of your TileMatrixSet.<br>
* `<Transformation>` `transformation`: the transformation to use when transforming projected coordinates into pixel coordinates

An example:

```dart
var epsg3413 = proj4.Projection.add('EPSG:3413',
    '+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');

final resolutions = <double>[
  32768,
  16384,
  8192,
  4096,
  2048,
  1024,
  512,
  256,
  128,
];

final epsg3413Bounds = Bounds<double>(
  Point<double>(-4511619.0, -4511336.0),
  Point<double>(4510883.0, 4510996.0),
);

var maxZoom = (resolutions.length - 1).toDouble();

var epsg3413CRS = Proj4Crs.fromFactory(
  code: 'EPSG:3413',
  proj4Projection: epsg3413,
  resolutions: resolutions,
  bounds: epsg3413Bounds,
  origins: [Point(0, 0)],
  scales: null,
  transformation: null,
);
```

## Usage

Proj4Crs has multiple uses:

* Set `FlutterMap`'s default CRS:

  ```dart
    FlutterMap(
      options: MapOptions(
        // Set the default CRS
        crs: epsg3413CRS,
        center: LatLng(65.05166470332148, -19.171744826394896),
        zoom: 3.0,
        maxZoom: maxZoom,
      ),
      layers: [],
    );
  ```
* Set a WMS layer's CRS

  ```dart
    TileLayer(
      wmsOptions: WMSTileLayerOptions(
        // Set the WMS layer's CRS
        crs: epsg3413CRS,
        transparent: true,
        format: 'image/jpeg',
        baseUrl:
            'https://www.gebco.net/data_and_products/gebco_web_services/north_polar_view_wms/mapserv?',
        layers: ['gebco_north_polar_view'],
      ),
    );
  ```

For complete code (with point transformation from one projection to another) see the page source code. This is how it looks like:


# Layers

To display anything on the map, you'll need to include at least one layer. This is usually a [`TileLayer`](/layers/tile-layer), which displays the map tiles themselves: without it, the map isn't really a very good map!

<div align="center" data-full-width="false"><figure><img src="/files/4TSKf8wAVxm2hyeUMMom" alt="Example FlutterMap widget, containing multiple feature layers, atop a TileLayer" width="563"><figcaption><p>Example <code>FlutterMap</code> widget, containing multiple feature layers, atop a <code>TileLayer</code></p></figcaption></figure></div>

To insert a layer, add it to the `children` property. Other layers (sometimes referred to as 'feature layers', as they are map features) can then be stacked on top, where the last widget in the `children` list is topmost. For example, you might display a [`MarkerLayer`](/layers/marker-layer), or any widget as your own custom layer ([Layers](/plugins/create/layers))!

{% hint style="info" %}
It is possible to add more than one `TileLayer`! Transparency in one layer will reveal the layers underneath.
{% endhint %}

{% hint style="info" %}
To display a widget in a sized and positioned box, similar to [Overlay Image Layer](/layers/overlay-image-layer), try the community maintained [flutter\_map\_polywidget plugin](https://github.com/TimBaumgart/flutter_map_polywidget)!
{% endhint %}

Each layer is isolated from the other layers, and so handles its own independent logic and handling. However, they can access and modify the internal state of the map, as well as respond to changes.

## Mobile vs Static Layers

Most layers are 'mobile', such as the `TileLayer`. These use a `MobileLayerTransformer` widget internally, which enables the layer to properly move and rotate with the map's current camera.

However, some layers are 'static', such as the [`AttributionLayer`](/layers/attribution-layer)s. These aren't designed to move nor rotate with the map, and usually make use of a widget like `Align` and/or `SizedBox.expand` to achieve this.

Both of these layer types are defined in the same `children` list. Most of the time, static layers go atop mobile layers, so should be at the end of the list.

## Layers With Elements: "Feature Layers"

Some layers - such as `PolygonLayer` - take 'elements' - such as `Polygon`s - as an argument, which are then displayed by the layer. They are usually displayed bottom-to-top in the order of the list (like a `Stack`).

{% hint style="info" %}
Since v7, it has not been possible to add elements to layers in an imperative style.

Flutter is a [declarative UI](https://docs.flutter.dev/data-and-backend/state-mgmt/declarative) framework: the UI is a function of the state. It is not necessarily the state's job to change the UI, the state just requests the UI rebuild itself using the new state. Since v7, FM also now mostly follows this convention (although with the exception of the `MapController`, which is a special exception to this rule).

This means that code such as `MarkerLayer.children.add(newElement)` is invalid.

Instead, in this case, a list of the coordinates (and any extra information required to build each `Marker`) should be maintained, then this list used to build a list of `Markers` at build time, for example, by using `List.map` directly in the `MarkerLayer.children` argument.
{% endhint %}

### Hit Testing & Interactivity

Some layers that use elements also support interactivity via hit testing. This is described in more detail on another page:

{% content-ref url="/pages/eErmULMNQoTVxxGRc11H" %}
[Layer Interactivity](/layers/layer-interactivity)
{% endcontent-ref %}


# Programmatic Interaction

In addition to the user interacting with the map, for example by using their cursor, trackpad, or touchscreen, the map can be controlled programmatically.

{% hint style="warning" %}
Changing the state of `MapOptions.initial*` will not update the map. Map control in 'flutter\_map' is imperative.
{% endhint %}

Programmatic interaction consists of two parts: reading and setting information about the map.

To use programmatic interaction, it's important to understand the difference between the map's 'camera' and the map's 'controller', and their relationship.

## Camera

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/MapCamera-class.html>" %}

The map's camera - or `MapCamera` - is an object that holds information to be read about the map's current viewport, such as:

* the coordinates of the geographic location at the `center` of the map
* the current `rotation` of the map (in degrees, where 0° is North)
* the current `zoom` level of the map

For example:

```dart
final MapCamera mapCamera = getMapCamera(); // See below
print(mapCamera.zoom);
```

## Controller

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/MapController-class.html>" %}

Similarly to other map client projects, the controller - `MapController` - allows the map's viewport/camera to be modified/set using methods, such as:

* move the camera to a new location and zoom level, using either:
  * `move`, which accepts a new center coordinate and zoom level
  * `fitCamera`, which allows more advanced positioning using coordinate boundaries and screen-space padding
* `rotate` the camera to a new number of degrees from North

For example:

```dart
final MapController mapController = getMapController(); // See below
mapController.move(LatLng(51.505, -0.124), 9); // Position the map to show London, UK
```

## Accessing the 'aspects'

Together, the `MapCamera` and `MapController` are two aspects of the `MapInheritedModel`.

{% hint style="info" %}
`MapOptions` is also an aspect of the same inherited model, but this is not useful here since it cannot be exposed outside of map layers.
{% endhint %}

{% tabs %}
{% tab title="From inside a map layer" %}
This means that you can get both the camera and the controller from within a layer of the map, given the map's `BuildContext`.

The `MapCamera.of` & `MapController.of` methods accept this context, and return their respective aspect. They also subscribe the layer for further updates: using `MapCamera.of` means that widget/layer will rebuild every time the `MapCamera` changes (for example, when the map's location changes).

For example:

```dart
return FlutterMap(
    // ...
    children: [
        Builder(
            builder: (context) {
                // This is a map layer
                final camera = MapCamera.of(context);
                final controller = MapController.of(context);
                
                // This will automatically update to display the current zoom level
                return Text(camera.zoom.toString());
            },
        ),
    ],
);
```

{% hint style="warning" %}
Using `MapController.of(context).camera` is an anti-pattern and not recommended.
{% endhint %}
{% endtab %}

{% tab title="From outside of the map" %}
If you want to access either aspect from outside of the map - for example, to reposition the map when the user presses a button, or to use the map's location in an API call - you'll need to:

{% stepper %}
{% step %}

### Declare an external `MapController`

The `MapController` object can be constructed, like in the example below. We recommend using a `StatefulWidget`.

{% hint style="info" %}
The `MapController` works similarly to a `ScrollController`.
{% endhint %}
{% endstep %}

{% step %}

### Attach the controller to the map

The default controller that `FlutterMap` uses and passes around internally can be overridden with the external controller using `FlutterMap.controller`.

For example:

<pre class="language-dart"><code class="lang-dart">// (`StatefulWidget` definition) ...

class _MapViewState extends State&#x3C;MapView> {
    final _mapController = MapController();
    
    @override
    Widget build(BuildContext context) {
        return FlutterMap(
<strong>            controller: _mapController,
</strong>            // ...
        );
    }
}
</code></pre>

{% hint style="warning" %}
The `MapController` does not become safe to use until after it has been fully initialised and attached to the map widget, which occurs during the first build of the map.

This means it cannot be used directly within `initState`, for example.

See [#the-controller-is-not-safe-to-use-until-attached](#the-controller-is-not-safe-to-use-until-attached "mention") for more information.
{% endhint %}
{% endstep %}
{% endstepper %}

Then, the external controller controls the map, and you can access the `MapCamera` from outside of the map using `MapController.camera`. For example:

```dart
double getCurrentZoomLevel(MapController controller) {
    return controller.camera.zoom;
}
```

### Pitfalls of an external controller

<details>

<summary>The controller is not safe to use until attached</summary>

Attachment to a map usually takes a single frame and is done in the initial widget build, but it's good practise not to assume this.

The controller definitely can not be used in `initState` (at least without adding a post-frame callback, which is not a recommended practise).

In most use cases, the controller will be used to respond to user actions, such as the `onPressed` callback of a button. In this case, it's usually safe to assume the controller is ready to use - unless, for example, there is a `FutureBuilder` wrapped around the `FlutterMap` which has not built the map yet.

If you need to hook into when the controller will be attached to the map, use `MapOptions.onMapReady`. For example:

```dart
final _mapControllerReady = Completer<void>();

return FlutterMap(
    mapController: _mapController,
    options: MapOptions(
        onMapReady: () {
            // `_mapController` safe to use
            _mapControllerReady.complete();
        },
    ),
);
```

{% hint style="warning" %}
`MapController` methods that control the map (such as changing its position) should not be used directly in `onMapReady` - see [issue #1507](https://github.com/fleaflet/flutter_map/issues/1507).

Configure the initial map position in `MapOptions` instead.
{% endhint %}

</details>

<details>

<summary>Construct the controller in the widget tree &#x26; don't pass it up the tree</summary>

To avoid issues, it's best practise to construct the `MapController` in the widget tree (for example, as a field on a `State`), either in a parent of the map-containing widget, or in the same widget.

Therefore, don't construct the controller directly in a state model, such as Provider or Bloc. Construct the controller as far up the widget tree as is necessary to ensure all widgets that need it are children, then use a dependency injection or inheritance to pass it to children (or pass it into a state model when constructing it).&#x20;

</details>

<details>

<summary>The <code>MapController</code> does not animate the map</summary>

When you use a method on the controller, the map state is updated immediately.

If you want to use animations, consider using the [community maintained plugin `flutter_map_animations`](https://github.com/TesteurManiak/flutter_map_animations).

</details>
{% endtab %}
{% endtabs %}

## Reacting to map events

To imperatively react to changes to the map camera, there's multiple methods available.

{% hint style="info" %}
Remember that when using `MapCamera.of`, that widget will automatically rebuild when the camera changes.
{% endhint %}

### Simple events

If you prefer a callback-based pattern and need to capture user interaction events with the widget (with limited information about the event or its effect on the map itself), the following callbacks are available through `MapOptions`:

* `onTap`
* `onLongPress`
* `onPointerDown`/`onPointerMove`/`onPointerUp`/`onPointerHover`/`onPointerCancel`

These callbacks are also available, which are not strictly caused by user interaction events and give information about the map, but are provided for ease-of-use:

* `onPositionChanged`: called when the map moves
* `onMapReady`: called when the `MapController` is attached

For example:

```dart
return FlutterMap(
    options: MapOptions(
        // ...
        onTap: (tapPosition, point) {
            final screenTapped = tapPosition.global;
            final coordinatesTapped = point;
        },
        onPositionChanged: (camera, hasGesture) {
            if (hasGesture) {
                disableUserLocationFollow();
            }
            print(camera.center);
        },
    ),
    // ...
);
```

{% hint style="info" %}
The `onTap` callback and `MapEventTap` event may be emitted 250ms after the actual tap occurred, as this is the acceptable delay between the two taps in a double tap zoom gesture.

If your project would benefit from a faster reaction to taps, disable the double tap zoom gesture, which will allow taps to be handled immediately:

```dart
options: MapOptions(
    interactionOptions: InteractionOptions(
        flags: ~InteractiveFlag.doubleTapZoom,
    ),
),
```

{% endhint %}

### Multiple or complex event handling

There's two methods to handle raw `MapEvent`s, which are emitted whenever the `MapCamera` changes, and contain detailed information, such as the source of the event, and, for some events, the old and new camera.

{% tabs %}
{% tab title="Using an options callback" %}
`MapOptions` has a callback named `onMapEvent`. For example:

```dart
options: MapOptions(
    onMapEvent: (evt) {
        if (evt is MapEventMove) {
            final oldCamera = evt.oldCamera;
            final newCamera = evt.newCamera;
        }
    },
),
```

{% endtab %}

{% tab title="Through an external controller" %}
In addition to controlling the map, the `MapController` also has a getter called `mapEventStream`. For example:

```dart
StreamSubscription<MapEvent> listenToMapEvents(MapController controller) {
    return mapController.mapEventStream.listen((evt) {
        if (evt is MapEventMove) {
            final oldCamera = evt.oldCamera;
            final newCamera = evt.newCamera;
        }
    });
}
```

{% endtab %}
{% endtabs %}


# Tile Layer

{% hint style="warning" %}

## You must comply with all the terms set by your tile server

It is your own responsibility to comply with any appropriate restrictions and requirements set by your chosen tile server/provider. Always read their terms of service. Failure to do so may lead to any punishment, at the tile server's discretion.

Using the OpenStreetMap public tile server? Check [Using OpenStreetMap (direct)](/tile-servers/using-openstreetmap-direct).
{% endhint %}

The base layer of most maps is a `TileLayer`, which displays square raster images in a 2D continuously-pannable grid, where each image is loaded as/just before it comes into view.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/TileLayer-class.html>" %}

```dart
TileLayer(
  urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
  userAgentPackageName: /*'com.example.app'*/, // Add your app identifier
  // + many other options
),
```

## Recommended Setup

{% stepper %}
{% step %}

### Choose a map source

flutter\_map doesn't provide tiles, so you'll need to bring your own raster tiles! There's multiple different supported sources.

{% tabs %}
{% tab title="Slippy Map/CARTO (XYZ)" %}
If you have a URL with placeholders for X, Y, and Z values, this is probably what you need to set up. This is the most common format for raster tiles, although many satellite tiles will instead use WMS.

{% tabs %}
{% tab title="From a network tile server" %}
Set the `urlTemplate` parameter to the template provided by the tile server - usually it can be copied directly from an account portal or documentation. You may also need to copy an API/access key.

<details>

<summary>(Advanced) Fallback URL Template</summary>

It's also possible to specify a `fallbackUrl` template, used if fetching a tile from the primary `urlTemplate` fails (which has the same format as this). It follows the same format, and supports the same placeholders.

{% hint style="warning" %}
Specifying a `fallbackUrl` does have negative effects on performance and efficiency. Avoid specifying `fallbackUrl` unless necessary.

See in-code documentation and [Tile Providers](/layers/tile-layer/tile-providers) for more information.
{% endhint %}

{% hint style="info" %}
Some `TileProvider`s may not support/provide any functionality for `fallbackUrl` template.
{% endhint %}

</details>

#### Placeholders

As well as the standard XYZ placeholders in the template, the following placeholders may also be used:

* `{s}`: subdomains (see below)
* `{r}`: native retina mode - see step 4 for more information
* `{d}`: reflects the `tileDimension` property (see below)

Additional placeholders can also be added freely to the template, and are filled in with the specified values in `additionalOptions`. This can be used to easier add switchable styles or access tokens, for example.

<details>

<summary>Subdomains</summary>

{% hint style="warning" %}
Subdomains are now usually [considered redundant](https://github.com/openstreetmap/operations/issues/737) due to the usage of HTTP/2 & HTTP/3 which don't have the same restrictions.

Usage of subdomains will also hinder Flutter's ability to cache tiles, potentially leading to increased tile requests and costs.

If the server supports HTTP/2 or HTTP/3 ([how to check](https://stackoverflow.com/a/71288871/11846040)), avoid using subdomains.
{% endhint %}

Some tile servers provide mirrors/redirects of the main tile server on/via subdomains, such as 'a', 'b', 'c'.

These were necessary to bypass browsers' limitations on simultaneous HTTP connections, thus increasing the number of tiles that can load at once.

To use subdomains, add the `{s}` placeholder, and specify the available subdomains in `TileLayer.subdomains`. flutter\_map will then fill the placeholder with one of these values based on internal logic.

</details>

<details>

<summary>Tile Dimension</summary>

Some tile servers will use 512x512px tiles instead of 256x256px, such as Mapbox. Using these larger tiles can help reduce tile requests, and when combined with [Retina Mode](#retina-mode), it can give the same resolution.

To use these tiles, set `tileDimension` to the actual dimensions of the tiles (otherwise they will appear to small), such as `512`. Also set `zoomOffset` to the result of `-((d/256) - 1)` - ie. `-1` for x512px tiles (otherwise they will appear at the wrong geographical locations).

The `{d}` placeholder/parameter may also be used in the URL to pass through the value of `tileDimension`.

</details>
{% endtab %}

{% tab title="From offline/on-device sources" %}
See [Offline Mapping](/tile-servers/offline-mapping) for detailed info and potential approaches to supporting offline users.

#### From the app's assets (bundled offline)

1. Set the `tileProvider` to `AssetTileProvider()`
2. Set the `urlTemplate` to the path to each tile from the assets directory, using the placeholders as necessary. For example:

   ```
   assets/map/{z}/{x}/{y}.png
   ```
3. Add each lowest level directory to the pubspec's assets listing.&#x20;

#### From the filesystem (filesystem/dynamic offline)

1. Set the `tileProvider` to `FileTileProvider()`
2. Set the `urlTemplate` to the path to each tile within the filesystem, using the placeholders as necessary
3. Ensure the app has any necessary permissions to read from the filesystem
   {% endtab %}
   {% endtabs %}
   {% endtab %}

{% tab title="WMS" %}
WMS tile servers have a base URL and a number of layers. flutter\_map can automatically put these together to fetch the correct tiles.

Create a `WMSTileLayerOptions` and pass it to the `wmsOptions` parameter. Define the `baseUrl` as needed, and for each layer string, add it as an item of a list passed to `layers`. You may also need to change other options, check the [full API documentation](https://pub.dev/documentation/flutter_map/latest/flutter_map/WMSTileLayerOptions-class.html), or follow the [example app](https://github.com/fleaflet/flutter_map/blob/master/example/lib/pages/wms_tile_layer.dart).

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/WMSTileLayerOptions-class.html>" %}
{% endtab %}

{% tab title="TMS" %}
TMS is also supported. Follow the instructions for the XYZ source, and enable the `TileLayer.tms` setting.
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Identify your client

It's important to identify your app to tile servers using the HTTP 'User-Agent' header (if they're not your own, and especially if their free or their ToS specifies to do so). This avoids potential issues with the tile server blocking your app because they do not know why so many tiles are being requested by unidentified clients - this can escalate to flutter\_map being blocked as a whole if too many apps do not identify themselves (which has happened in the past!).

Set the `userAgentPackageName` parameter to your app's package name (often in the format `com.example.app`), or another unique identifier. flutter\_map will identify your client to the server via the header as:

`flutter_map (<userAgentPackageName>)`

{% hint style="info" %}
In some cases, you may be able to skip this step:

* Your app runs solely on the web: the 'User-Agent' header cannot be changed, and will always identify your users' browsers
* The tile server is your own, or you are using entirely offline mapping
  {% endhint %}
  {% endstep %}

{% step %}

### Manually set up a tile provider to optimize tile loading

The `TileProvider` is responsible for fetching tiles for the `TileLayer`. By default, the `TileLayer` creates a `NetworkTileProvider` every time it is constructed. `TileProvider`s are attached to the lifecycle of the `TileLayer` they are used within, and automatically disposed when their `TileLayer` is disposed.

However, this can cause performance issues or glitches for many apps. For example, the HTTP client can be manually constructed to be long-living, which will keep connections to a tile server open, increasing tile loading speeds.

<figure><img src="/files/wRGsLIWerEfNafWaZ9ox" alt="Flowchart describing the best method to optimize a tile layer &#x26; tile provider setup. Is your &#x60;FlutterMap&#x60; or &#x60;TileLayer&#x60; rebuilt (frequently)? Or, are you using a different tile provider to the default? If not, don&#x27;t worry about it, the &#x60;TileLayer&#x60; will do it for you. Otherwise, does your tile provider (or its properties) change frequently, or depend on the build method? If it does, construct a tile provider within the build method if necessary, but manually create a HTTP client outside of the build method and pass it in. Otherwise, do you need to reuse your tile provider across multiple different (volatile) tile layers? If you do, construct a tile provider outside of the build method, but also manually create a HTTP client and pass it in. Otherwise, just construct a tile provider as normal, but outside of the build method."><figcaption></figcaption></figure>

If you're not using a different tile provider, such as one provided by a plugin or one for offline mapping, then installing and using the official `CancellableNetworkTileProvider` plugin may be beneficial, especially on the web. See [Tile Providers](/layers/tile-layer/tile-providers#cancellablenetworktileprovider) for more information.

See [Tile Providers](/layers/tile-layer/tile-providers) for more information about tile providers generally.
{% endstep %}

{% step %}

### Enable retina mode (if supported by your tiles)

Retina mode improves the resolution of map tiles, an effect particularly visible on high density (aka. retina) displays.

Raster map tiles can look especially pixelated on retina displays, so some servers support [high-resolution "@2x" tiles](https://wiki.openstreetmap.org/wiki/High-resolution_tiles), which are tiles at twice the resolution of normal tiles.

Where the display is high density, and the server supports retina tiles - usually indicated by an `{r}` placeholder in the URL template - it is recommended to enable retina mode.

To enable retina mode in these circumstances, use the following:

```dart
    retinaMode: RetinaMode.isHighDensity(context),
```

Note that where tiles are larger than the standard x256px (such as x512px), retina mode can help make them appear very similar to x256px tiles, but still retain the other benefits of larger tiles. In this case, consider fixing `retinaMode` to `true`, depending on your own tests.

<details>

<summary>Emulating retina mode</summary>

It is also possible to emulate retina mode, even when the server does not natively support it. If `retinaMode` is `true`, and no `{r}` placeholder is present, flutter\_map will emulate it by requesting four tiles at a larger zoom level and combining them together in place of one.

Emulating retina mode has multiple negative effects:

* it increases tile requests
* it likely causes text/labels and POI markers embedded in the tiles to become smaller and unreadable
* it decreases the effective maximum zoom by 1

Therefore, carefully consider whether emulating retina mode is appropriate for your application, and disable it if necessary. Always prefer native retina tiles if they are available.

</details>
{% endstep %}

{% step %}

### Set the maximum zoom level covered by your tiles

Set the `maxNativeZoom` parameter to the maximum zoom level covered by your tile source. This will make flutter\_map scale the tiles at this level when zooming in further, instead of attempting to load new tiles at the higher zoom level (which will fail).

You can also set `MapOptions.maxZoom`, which is an absolute zoom limit for users. It is recommended to set this to a few levels greater than the maximum zoom level covered by any of your tile layers.
{% endstep %}
{% endstepper %}

## Other Properties

### `panBuffer`

To make a more seamless experience, tiles outside the current viewable area can be 'preloaded', with the aim of minimizing the amount of non-tile space a user sees.

`panBuffer` sets the number of surrounding rows and columns around the viewable tiles that should be loaded, and defaults to 1.

{% hint style="warning" %}
Specifying a `panBuffer` too high may result in slower tile requests for all tiles (including those that are visible), and a higher load on the tile server. The effect is amplified on larger map dimensions/screen sizes.
{% endhint %}

### Tile Update Transformers

{% hint style="info" %}
`TileUpdateTransformer`(`s`) is a power-user feature. Most applications won't require it.
{% endhint %}

A `TileUpdateTransformer` restricts and limits `TileUpdateEvent`s (which are emitted 'by' `MapEvent`s), which cause tiles to update.

For example, a transformer can delay (throttle or debounce) updates through one of the built-in transformers, or pause updates during an animation, or force updates even when a `MapEvent` wasn't emitted.

For more information, see:

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/TileUpdateTransformer.html>" %}


# Tile Providers

A `TileProvider` works with a `TileLayer` to supply tiles (usually images) when given tile coordinates.

Tile providers may support compatible [caching providers](/layers/tile-layer/caching) (including built-in caching), or may implement caching themselves.

Tiles are usually dynamically requested from the network/Internet, using the default `NetworkTileProvider`. Tiles can also come from the app's assets, the filesystem, a container/bundle, or any other source.

## `NetworkTileProvider`

This tile provider uses the `TileLayer.urlTemplate` to get the appropriate tile from the a network, usually the Internet.

{% hint style="warning" %}
Specifying any `fallbackUrl` (even if it is not used) in the `TileLayer` will prevent loaded tiles from being cached in memory.

This is to avoid issues where the `urlTemplate` is flaky (sometimes works, sometimes doesn't), to prevent potentially different tilesets being displayed at the same time.
{% endhint %}

For more information, check the API documentation.

## Local Tile Providers

These tile providers use the `urlTemplate` to get the appropriate tile from the asset store of the application, or from a file on the users device, respectively.

{% hint style="warning" %}
Specifying any `fallbackUrl` (even if it is not used) in the `TileLayer` will reduce the performance of these providers.

It will cause [23% slower asset tile requests](https://github.com/fleaflet/flutter_map/issues/1436#issuecomment-1569663004) with `AssetTileProvider`,  and will cause main thread blocking when requesting tiles from `FileTileProvider`.
{% endhint %}

### `AssetTileProvider`

This tile providers uses the `templateUrl` to get the appropriate tile from the asset store of the application.

{% hint style="info" %}
Asset management in Flutter leaves a lot to be desired! Unfortunately, every single sub-directory (to the level of tiles) must be listed.
{% endhint %}

### `FileTileProvider`

This tile providers uses the `templateUrl` to get the appropriate tile from the a path/directory/file on the user's device - either internal application storage or external storage.

{% hint style="warning" %}
On the web, `FileTileProvider()` will throw an `UnsupportedError` when a tile request is attempted, due to the lack of the web platform's access to the local filesystem.

If you know you are running on the web platform, use a [`NetworkTileProvider`](#network-tile-provider) or a custom tile provider.
{% endhint %}

## Offline Mapping

{% content-ref url="/pages/bpcomOuvTGL5xNNpSAYp" %}
[Offline Mapping](/tile-servers/offline-mapping)
{% endcontent-ref %}


# Caching

since v8.2

flutter\_map supports integration of basic map tile caching (with compatible tile providers) through caching providers & provides an automatically-enabled implementation on non-web platforms, known as built-in caching.

{% hint style="warning" %}
Built-in caching is not a replacement for caching which can better guarantee resilience. It provides no guarantees as to the safety of cached tiles, which may become unexpectedly lost/inaccessible at any time.

It should not be relied upon where not having cached tiles may lead to a dangerous situation - for example, offline mapping. See [Offline Mapping](/tile-servers/offline-mapping) for information about implementing more appropriate solutions.
{% endhint %}

Caching aims to:

* Improve developer experience by:
  * **Reducing the costs** of using tile servers, by reducing duplicate tile requests
  * **Keep your app lightweight** - the built-in cache doesn't ship any binaries or databases, just a couple extra libraries you probably already use
* Improve user experience by:
  * **Reducing tile loading durations**, as fetching from the cache is very quick
  * **Reducing network/Internet usage**, which may be limited or metered/expensive (eg. mobile broadband)
* **Improve compliance** with tile server requirements, by reducing the strain on them
* Be extensible, customizable, and integrate with multiple tile providers

It does, however, come at the expense of usage of on-device storage capacity.

***

Offline mapping and caching can also be implemented in other ways. See [Offline Mapping](/tile-servers/offline-mapping) for more information.

{% hint style="info" %}
Some plugins which perform caching or offline mapping may instead provide a dedicated `TileProvider`.

In this case, built-in caching & caching providers are not applicable, and will not be used (unless the provider explicitly supports usage of caching providers).&#x20;
{% endhint %}

## Configuring the built-in provider

{% hint style="success" %}
Built-in caching is enabled by default on non-web platforms, using the `BuiltInMapCachingProvider` implementation.

On web platforms, the browser usually performs caching automatically.
{% endhint %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/BuiltInMapCachingProvider-class.html>" %}

To configure the default provider, provide arguments to the `getOrCreateInstance` factory constructor. Usually this is done when constructing the `TileLayer`/`TileProvider`:

<pre class="language-dart" data-title="configured_built_in.dart"><code class="lang-dart">TileLayer(
    urlTemplate: '...',
    userAgentPackageName: '...',
    tileProvider: NetworkTileProvider(
<strong>        cachingProvider: BuiltInMapCachingProvider.getOrCreateInstance(
</strong><strong>            maxCacheSize: 1_000_000_000, // 1 GB is the default
</strong><strong>        ),
</strong>    ),
);
</code></pre>

{% hint style="info" %}
It is not possible to change the configuration after the provider instance has been created (without first `destroy`ing it).

This means if you configure the provider in the first tile provider/tile layer used (or indeed outside of the map context, such as in the `main` method), the configuration does not need to be manually specified in each tile provider.

***

It is possible to change the configuration after a cache on the filesystem has already been created.
{% endhint %}

By default, caching occurs in a platform provided cache directory. The operating system may clear this at any time.

By default, a 1 GB (soft) limit is applied to the built-in caching. This limit is only applied when the cache provider is initialised (usually when the first tiles are loaded on each app session).

HTTP headers are used to determine how long a tile is considered 'fresh' - this fulfills the requirements of many tile servers. However, setting `overrideFreshAge` allows the HTTP headers to be overridden, and the tile to be stored and used for a set duration.

The `tileKeyGenerator` can be customized. The callback accepts the tile's URL, and converts it to a key used to uniquely identify the tile. By default, it generates a UUID from the entire URL string. However, in some cases, the default behaviour should be changed:

<details>

<summary>Using a custom <code>tileKeyGenerator</code></summary>

Where parts of the URL are volatile or do not represent the tile's&#x20;contents/image - for example, API keys contained with the query&#x20;parameters - this should be modified to remove the volatile portions.

Otherwise, tiles stored with an old/rejected volatile portion will not be utilised by the cache, and will waste storage space.

Keys must be usable as filenames on all intended platform filesystems.

***

Implementations may use the static utility method `uuidTileKeyGenerator` if they just wish to modify the input URL.

Convenient methods to modify URLs can be found by first parsing it to a [`Uri`](https://api.flutter.dev/flutter/dart-core/Uri-class.html) using `Uri.parse`, working on it (such as with [`replace`](https://api.flutter.dev/flutter/dart-core/Uri/replace.html)), then converting it back to a string.

Alternatively, the raw URL string could be worked on manually, such as by using regular expression to extract certain parts.

</details>

### Deleting the cache

With the default `BuiltInMapCachingProvider`, it is possible to delete the cache contents in two ways:

* When the app is running, `destroy` the current instance and set the `deleteCache` argument to `true` (then optionally create a new instance if required, which happens automatically on the next tile load by default)
* When the app is not running, users may delete the storage directory
  * If the default cache directory is used, users may do this by 'clearing the app cache' through their operating system, for example. On some platforms, this may need to be done manually (which may be difficult for less technical users), whilst on others, it may be a simple action.

## Using other providers

You can also use any other `MapCachingProvider` implementation, such as provided by plugins, or [create one yourself](/plugins/create/caching-providers)! They may support the web platform, unlike the built-in cache.

You should check that plugin's documentation for information about initialisation & configuration. You will always need to pass it to the `cachingProvider` argument of a compatible `TileProvider`.

<pre class="language-dart" data-title="custom.dart"><code class="lang-dart">TileLayer(
    urlTemplate: '...',
    userAgentPackageName: '...',
    tileProvider: NetworkTileProvider(
<strong>        cachingProvider: CustomMapCachingProvider(),
</strong>    ),
);
</code></pre>

## Disabling built-in caching

{% hint style="warning" %}
Before disabling built-in caching, you should check that you can still be compliant with any requirements imposed by your tile server.

It is your own responsibility to comply with any appropriate restrictions and requirements set by your chosen tile server/provider. Always read their Terms of Service. Failure to do so may lead to any punishment, at the tile server's discretion.

The built-in caching is designed to be compliant with the caching requirement for the [OpenStreetMap public tile server](/tile-servers/using-openstreetmap-direct). Disabling it may make your project non-compliant.
{% endhint %}

{% hint style="info" %}
This is not necessary when running on the web.
{% endhint %}

If you prefer to disable built-in caching, use the `DisabledMapCachingProvider` on each tile provider:&#x20;

{% code title="disabled.dart" %}

```dart
TileLayer(
    urlTemplate: '...',
    userAgentPackageName: '...',
    tileProvider: NetworkTileProvider(
        cachingProvider: const DisabledMapCachingProvider(),
    ),
);
```

{% endcode %}


# Marker Layer

You can add single point features - including arbitrary widgets - to maps using `MarkerLayer` and `Marker`s.

{% hint style="success" %}
No more image only markers! [Unlike *other* ](https://github.com/flutter/flutter/issues/24213)😉[^1][ popular mapping libraries](https://github.com/flutter/flutter/issues/24213), we allow usage of any widget as the marker.
{% endhint %}

<div data-full-width="true"><figure><img src="/files/7kB3zICQ5rtWIDI1ybc6" alt=""><figcaption><p>A variety of <code>Marker</code>s on a rotated map</p></figcaption></figure></div>

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/MarkerLayer-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/Marker-class.html>" %}

```dart
MarkerLayer(
  markers: [
    Marker(
      point: LatLng(30, 40),
      width: 80,
      height: 80,
      child: FlutterLogo(),
    ),
  ],
),
```

{% hint style="warning" %}
Excessive use of markers may create performance issues.

Consider using a clustering plugin to merge nearby markers together, reducing the work that needs to be done when rendering: [Plugins List](/plugins/list#marker-clustering).
{% endhint %}

## Alignment

The marker widget will be centered over the geographic coordinate by default. However, this can be changed with the `alignment` argument, which aligns the widget relative to the point.

The center of rotation when `rotate` is `true` will be the point.

The default alignment for all `Marker`s within a `MarkerLayer` can be set by changing the same property on the `MarkerLayer`.

## Rotation

It is possible to enable the `Marker` to automatically counter-rotate to the camera's rotation, to ensure it remains facing upwards, via the `rotate` argument.

The default alignment for all `Marker`s within a `MarkerLayer` can be set by changing the same property on the `MarkerLayer`.

{% hint style="info" %}
There is no built-in support to rotate markers to a specific degree. However, this is easy to implement through a rotating widget, such as `Transform.rotate`.
{% endhint %}

## Handling Gestures

There is no built-in support to handle gestures on `Marker`s, such as taps. However, this is easy to implement using a standard `GestureDetector`.

[^1]: [Google Maps \*wink \*wink](https://github.com/flutter/flutter/issues/24213)


# Polygon Layer

You can add areas/shapes formed from coordinates to maps using `PolygonLayer` and `Polygon`s.

<div data-full-width="true"><figure><img src="/files/LhPvHybNbOM7VJthNuUB" alt=""><figcaption><p>A variety of <code>Polygon</code>s including labels, holes, mixed colors &#x26; opacities, and dotted borders</p></figcaption></figure></div>

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/PolygonLayer-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/Polygon-class.html>" %}

```dart
PolygonLayer(
  polygons: [
    Polygon(
      points: [LatLng(30, 40), LatLng(20, 50), LatLng(25, 45)],
      color: Colors.blue,
    ),
  ],
),
```

## Interactivity

`PolygonLayer`s and `Polygons`s support hit detection and interactivity.

{% content-ref url="/pages/eErmULMNQoTVxxGRc11H" %}
[Layer Interactivity](/layers/layer-interactivity)
{% endcontent-ref %}

## Multi-Worlds

The `PolygonLayer` paints its `Polygon`s across all visible worlds by default. This can be changed.

{% content-ref url="/pages/bDTpJLEc0sgNb0l9Ng6H" %}
[Unbounded Horizontal Scrolling](/usage/basics/unbounded-horizontal-scrolling)
{% endcontent-ref %}

## Performance Optimizations

flutter\_map includes many performance optimizations built in, especially as of v7. Some are enabled by default, but may be only 'weakly' applied, and others must be enabled manually. There are also some other actions that can be taken externally to improve performance

The following list is ordered from least 'intrusive'/extreme, to most intrusive. Remember to consider the potential risks of enabling an optimization before doing so.

{% hint style="success" %}
The example application includes a stress test which loads multiple `Polygon`s from an optimized format, with a total of 138,000 points.
{% endhint %}

<details>

<summary>Culling <em>(enabled by default)</em></summary>

To improve performance, polygons that are entirely offscreen are effectively removed - they are not processed or painted/rendered. This is enabled by default, and may be disabled using the `polygonCulling` parameter, although this is not recommended.

</details>

<details>

<summary>Batching <em>(enabled by default, but improvable with effort)</em></summary>

{% hint style="warning" %}
Overlapping colors that are not completely opaque will not receive the 'darkening'/layering effect - the overlapping area will just be the single colour.&#x20;
{% endhint %}

To improve performance, polygons that are similar in appearance, and borders that are similar in appearance, are drawn to the underlying canvas in batches, to reduce the number of draw calls. This cannot be disabled.

To further improve performance, consider defining all `Polygon` `points` in a clockwise order, and place similar appearance `Polygon`s adjacent to each other in the `polygons` list (where elevation does not matter).

</details>

<details>

<summary>Simplification <em>(enabled by default, adjustable)</em></summary>

{% hint style="warning" %}
On layers with (many) only small polygons (those with few points), disabling simplification may yield better performance.
{% endhint %}

To improve performance, polygon outlines (`points`) are 'simplified' before the polygons are culled and painted/rendered. The well-known [Ramer–Douglas–Peucker algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm) is used to perform this, and is enabled by default.

To adjust the quality and performance of the simplification, the maximum distance between removable points can be adjusted through the `simplificationTolerance` parameter. Increasing this value (from its default of 0.5) results in a more jagged, less accurate (lower quality) simplification, with improved performance; and vice versa. Many applications use a value in the range 1 - 1.5. To disable simplification, set `simplificationTolerance` to 0.&#x20;

***

Simplification algorithms reduce the number of points in each line by removing unnecessary points that are 'too close' to other points which create tiny line segments invisible to the eye. This reduces the number of draw calls and strain on the raster/render thread. This should have minimal negative visual impact (high quality), but should drastically improve performance.

For this reason, polygons can be more simplified at lower zoom levels (more zoomed out) and less simplified at higher zoom levels (more zoomed in), where the effect of culling on performance improves and trades-off. This is done by scaling the `simplificationTolerance` parameter (see below) automatically internally based on the zoom level.

</details>

<details>

<summary>Painter Fill Method</summary>

See [#painter-fill-method-1](#painter-fill-method-1 "mention") for more information. `evenOdd` is more performant, but at the expense of potential visual glitches.

</details>

<details>

<summary>Performant Rendering, with <code>drawVertices</code> <em>(disabled by default)</em></summary>

{% hint style="warning" %}
Self-intersecting (complex) `Polygon`s are not supported by the triangulation algorithm, and could cause errors.

Holes are supported.
{% endhint %}

{% hint style="warning" %}
This pathway may be slower than the standard pathway, especially when used on a large scale but with simplification disabled, or used on an especially small scale.

It is intended for use when prior profiling indicates more performance is required after other methods are already in use.
{% endhint %}

{% hint style="warning" %}
Rarely, some visible artefacts may be introduced by the triangulation algorithm.
{% endhint %}

Polygons (and similar other features) are usually drawn directly onto a `Canvas`, using built-in methods such as `drawPolygon` and `drawLine`. However, these can be relatively slow, and will slow the raster thread when used at a large scale.

Therefore, to improve performance, it's possible to optionally set the `useAltRendering` flag on the `PolygonLayer`. This will use an alternative, specialised, rendering pathway, which *may* lead to an overall performance improvement, particularly at a large scale.

***

There's two main steps to this alternative rendering algorithm:

1. Cut each `Polygon` into multiple triangles through a process known as [triangulation](https://en.wikipedia.org/wiki/Polygon_triangulation). flutter\_map uses an earcutting algorithm through [dart\_earcut](https://pub.dev/packages/dart_earcut) (a port of an algorithm initially developed at Mapbox intended for super-large scale triangulation).
2. Draw each triangle onto the canvas via the lower-level, faster [`drawVertices`](https://api.flutter.dev/flutter/dart-ui/Canvas/drawVertices.html) method. Borders are then drawn as normal.

</details>

<details>

<summary>Use No/Thin Borders or Cheaper <code>StrokeCap</code>s/<code>StrokeJoin</code>s <em>(external)</em></summary>

To further improve performance, consider using no border, or a hairline 1px border (remembering to consider the difference between device and logical pixels). Alternatively, consider using `StrokeCap.butt`/`StrokeCap.square` & `StrokeJoin.miter`/`StrokeJoin.bevel`.\
These are much cheaper for the rendering engine (particularly Skia), as it does not have to perform as many calculations.&#x20;

</details>

## Painter Fill Method

The default renderer supports two painter fill methods using different Flutter APIs. These fill methods change the way Flutter decides what is considered within a polygon (and should be filled), and what is outside. This can change the way particularly intersections and overlaps appear visually.

This can be set using the `painterFillMethod` property and the `PolygonPainterFillMethod` enum.&#x20;

Many apps will not need to change from the default method. Before changing the method, profile and test for visual glitches thouroughly.

{% tabs %}
{% tab title="Path.combine" %}
The `PolygonPainterFillMethod.pathCombine` option uses lesser-used Flutter APIs: the [`Path.combine`](https://api.flutter.dev/flutter/dart-ui/Path/combine.html) constructor. This allows for more intricate [`PathOperation`](https://api.flutter.dev/flutter/dart-ui/PathOperation.html)s, such as `difference` and `union`.

This gives the correct/intended/best visual results on native platforms. It's the default on native (non-web) platforms.

However, on the web, it causes major visual glitches due to a [Flutter issue](https://github.com/flutter/flutter/issues/124675).

Additionally, it has slightly worse performance (especially at scale) than [#even-odd](#even-odd "mention"). The hit to performance is unlikely to be significant or even noticeable in many applications, but applications drawing many polygons may see a slow of about 2ms (as tested in the example app's stress test).
{% endtab %}

{% tab title="Even/Odd" %}
The `PolygonPainterFillMethod.evenOdd` option uses more simple Flutter APIs: the [`PathFillType`](https://api.flutter.dev/flutter/dart-ui/PathFillType.html)`.evenOdd` [rule](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule).

This gives the best performance, and works on the web. This is the default when targeting the web.

However, it yields unintended results in certain edge cases when polygons intersect when [#inverted-filling](#inverted-filling "mention") is used, or when polygon holes can intersect with other holes.

<div align="center"><figure><img src="/files/PcGEweIcrTBS09X8Ni2Q" alt="" width="301"><figcaption><p>An example of a visual defect caused by using the <code>.evenOdd</code> setting</p></figcaption></figure></div>
{% endtab %}
{% endtabs %}

## Inverted Filling

{% hint style="warning" %}
On the web, inverted filling may not work as expected in some cases. It will not match the behaviour seen on native platforms.

Avoid allowing polygons to intersect, and avoid using holes within polygons. See the second image below for a demonstration of the issues.&#x20;

This is due to multiple limitations/bugs within Flutter. See <https://github.com/flutter/flutter/issues/124675> and <https://github.com/flutter/flutter/issues/149743> for the problematic bug reports.
{% endhint %}

Inverted filling (`invertedFill`) allows a color to be applied to all parts of the map outside a polygon. Transparently filled polygons will reveal the layers beneath without the inverted fill color.

<div align="center" data-full-width="false"><figure><img src="/files/Pd8zRz6MTY96dSTsDuXA" alt="" width="375"><figcaption><p>Inverted filling working correctly (native)</p></figcaption></figure> <figure><img src="/files/UIcG5TmBCjDSxoaldHfr" alt="" width="375"><figcaption><p>Inverted filling broken on web</p></figcaption></figure></div>

## Polygon Manipulation

'flutter\_map' doesn't provide any public methods to manipulate polygons, as these would be deemed out of scope.

However, some useful methods can be found in libraries such as 'latlong2' and ['poly\_bool\_dart'](https://github.com/mohammedX6/poly_bool_dart). These can be applied to the input of `Polygon`'s `points` argument, and the map will do it's best to try to render them. However, more complex polygons - such as those with holes - may be painted inaccurately, and may therefore require manual adjustment (of `holePointsList`, for example).


# Polyline Layer

You can add lines formed from coordinates to maps using `PolylineLayer` and `Polyline`s.

<div data-full-width="true"><figure><img src="/files/QNvqASM7YhRcYLsYRsIr" alt=""><figcaption><p>A variety of <code>Polyline</code>s, including gradients, mixed opacity and colors including borders, and dashes</p></figcaption></figure></div>

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/PolylineLayer-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/Polyline-class.html>" %}

```dart
PolylineLayer(
  polylines: [
    Polyline(
      points: [LatLng(30, 40), LatLng(20, 50), LatLng(25, 45)],
      color: Colors.blue,
    ),
  ],
),
```

## Pattern

`Polyline`s support a `solid`, `dotted`, and `dashed` style, through a `StrokePattern`passed as an argument to`Polyline.pattern`. These are flexible, and spacing and sizing may be customized.

`dotted` and `dashed` patterns should 'fit' the `Polyline` they are applied to, otherwise the final point in that line may not be visually clear. The fit can be adjusted when defining the pattern through the `PatternFit` enum.

<div align="center" data-full-width="false"><figure><img src="/files/x41nSAvTRrIa0PxRqonG" alt="" width="375"><figcaption><p>Illustration of the 5 types of <code>PatternFit</code> applied to a <code>dashed</code> <code>Polyline</code><br>From left to right: <code>none</code>, <code>appendDot</code>, <code>extendFinalDash</code>, <code>scaleUp</code> (default), <code>scaleDown</code></p></figcaption></figure></div>

## Interactivity

`PolylineLayer`s and `Polyline`s support hit detection and interactivity.

{% content-ref url="/pages/eErmULMNQoTVxxGRc11H" %}
[Layer Interactivity](/layers/layer-interactivity)
{% endcontent-ref %}

{% hint style="info" %}
If any polylines are very thin, it is recommended for accessibility reasons to increase the size of the 'hitbox' (the area where a hit is detected on a polyline) to larger than the line itself in order to make it easier to tap/interact with the polyline.

The `minimumHitbox` argument adjusts the minimum size of the hitbox - the size of the hitbox will usually be the entire visual area/thickness of the polyline (and border), but will be no less than this value. It defaults to 10.
{% endhint %}

## Multi-Worlds

The `PolylineLayer` paints its `Polyline`s across all visible worlds by default. This can be changed.

{% content-ref url="/pages/bDTpJLEc0sgNb0l9Ng6H" %}
[Unbounded Horizontal Scrolling](/usage/basics/unbounded-horizontal-scrolling)
{% endcontent-ref %}

## Performance Optimizations

{% hint style="success" %}
The example application includes a stress test which generates a `Polyline` with 200,000 points.
{% endhint %}

### Culling

To improve performance, line segments that are entirely offscreen are effectively removed - they are not processed or painted/rendered. This is enabled by default and disabling it is not recommended.

{% hint style="warning" %}
Polylines that are particularly wide (due to their `strokeWidth`/`borderStrokeWidth` may be improperly culled if using the default configuration. This is because culling decisions are made on the 'infinitely thin line' joining the `points`, not the visible line, for performance reasons.

Therefore, the `cullingMargin` parameter is provided, which effectively increases the distance a segement needs to be from the viewport edges before it can be culled. Increase this value from its default if line segements are visibly disappearing unexpectedly.
{% endhint %}

{% hint style="warning" %}
Culling cannot be applied to polylines with a gradient fill, as this would cause inconsistent gradients.

These will be automatically internally excluded from culling: it is not necessary to disable it layer-wide - unless all polylines have gradient fills, in which case that may yield better performance.

Avoid using these if performance is of importance. Instead, try using multiple polylines with coarser color differences.
{% endhint %}

### Simplification

To improve performance, polylines are 'simplified' before being culled and painted/rendered. The well-known [Ramer–Douglas–Peucker algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm) is used to perform this, and is enabled by default.

> Simplification algorithms reduce the number of points in each line by removing unnecessary points that are 'too close' to other points which create tiny line segements invisible to the eye. This reduces the number of draw calls and strain on the raster/render thread. This should have minimal negative visual impact (high quality), but should drastically improve performance.
>
> For this reason, polylines can be more simplified at lower zoom levels (more zoomed out) and less simplified at higher zoom levels (more zoomed in), where the effect of culling on performance improves and trades-off. This is done by scaling the `simplificationTolerance` parameter (see below) automatically internally based on the zoom level.

To adjust the quality and performance of the simplification, the maximum distance between removable points can be adjusted through the `simplificationTolerance` parameter. Increasing this value (from its default of 0.4) results in a more jagged, less accurate (lower quality) simplification, with improved performance; and vice versa. Many applications use a value in the range 0 - 1.

To disable simplification, set `simplificationTolerance` to 0.&#x20;

{% hint style="warning" %}
The simplification step must run before culling, to avoid the polyline appearing to change when interacting with the map (due to the first and last points of the polyline changing, influencing the rest of the simplified points).

Therefore, reducing/disabling simplification will yield better performance on complex polylines that are out of the camera view (and therefore culled without requiring the potentially expensive simplification). However, using simplification will likely improve performance overall - it does this by reducing the load on the raster thread and slightly increasing the load on the UI/build/widget thread.
{% endhint %}

{% hint style="warning" %}
On layers with (many) only 'short' polylines (those with few points), disabling simplification may yield better performance.
{% endhint %}

## Routing/Navigation

Routing is out of scope for 'flutter\_map'. However, if you can get a list of coordinates from a 3rd party, then you can use polylines to show them!

Good open source options that can be self-hosted include [OSRM](http://project-osrm.org/) (which includes a public demo server) and [Valhalla](https://github.com/valhalla/valhalla). You can also use a commercial solution such as Mapbox or Google Maps - these can often give more accurate/preferable results because they can use their traffic data when routing.

### Converting Formats

You may have a polyline with 'Google Polyline Encoding' (which is a lossy compression algorithm to convert coordinates into a string and back). These are often returned from routing engines, for example. In this case, you'll need to decode the polyline to the correct format first, before you can use it in a `Polyline`'s `points` argument.

One way to accomplish this is to use another Flutter library called ['google\_polyline\_algorithm'](https://pub.dev/packages/google_polyline_algorithm), together with a custom method. You can use the code snippet below, which can just be pasted into a file and imported whenever needed:

{% code title="unpack\_polyline.dart" %}

```dart
import 'package:latlong2/latlong.dart';
export 'package:google_polyline_algorithm/google_polyline_algorithm.dart'
    show decodePolyline;

extension PolylineExt on List<List<num>> {
  List<LatLng> unpackPolyline() =>
      map((p) => LatLng(p[0].toDouble(), p[1].toDouble())).toList();
}
```

{% endcode %}

You can then use the package and the above snippet by doing:

```dart
import 'unpack_polyline.dart';

decodePolyline('<encoded-polyline>').unpackPolyline(); // Returns `List<LatLng>` for a map polyline
```


# Circle Layer

You can add circle areas to maps by making them out of a center coordinate and radius using `CircleLayer` and `CircleMarker`s.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/CircleLayer-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/CircleMarker-class.html>" %}

<figure><img src="/files/3qRSt3ShwF2m6AZhvS4o" alt=""><figcaption><p>An example <code>CircleMarker</code></p></figcaption></figure>

```dart
CircleLayer(
  circles: [
    CircleMarker(
      point: LatLng(51.50739215592943, -0.127709825533512),
      radius: 10000,
      useRadiusInMeter: true,
    ),
  ],
),
```

{% hint style="warning" %}
Excessive use of circles may create performance issues.
{% endhint %}

## Interactivity

`CircleLayer`s and `CircleMarker`s support hit detection and interactivity.

{% content-ref url="/pages/eErmULMNQoTVxxGRc11H" %}
[Layer Interactivity](/layers/layer-interactivity)
{% endcontent-ref %}


# Overlay Image Layer

You can overlay images on the map (for example, town or floor plans) using `OverlayImageLayer` and `OverlayImage`s or `RotatedOverlayImage`s.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/OverlayImageLayer-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/BaseOverlayImage-class.html>" %}

<figure><img src="/files/1efO215vqz7Ug1w8BCO3" alt=""><figcaption><p>Example <code>RotatedOverlayImage</code></p></figcaption></figure>

```dart
OverlayImageLayer(
  overlayImages: [
    OverlayImage( // Unrotated
      bounds: LatLngBounds(
        LatLng(45.3367881884556, 14.159452282322459),
        LatLng(45.264129635422826, 14.252585831779033),
      ),
      imageProvider: NetworkImage(),
    ),
  ],
),
```

{% hint style="warning" %}
There have been issues in the past where these images failed to appear properly, sometimes not showing up at all, sometimes showing up malformed or corrupted.

If this issue occurs to you, and you're using Impeller, try disabling Impeller at launch/build time to see if the issue rectifies itself. If it does, this is an Impeller issue, and should be reported to the Flutter team.
{% endhint %}

## Rotation & Skewing

`RotatedOverlayImage` supports rotation and parallelogram skewing, by accepting 3 points instead of 2.

To calculate a rotation without skewing, given a center and a 3rd corner, see <https://stackoverflow.com/a/78064659/11846040>.


# Attribution Layer

Before publishing your app to users, you should credit any sources you use, according to their Terms of Service.

There are two built in methods to provide attribution, `RichAttributionWidget` and `SimpleAttributionWidget`, but you can also build your own using a simple `Align` widget.

{% hint style="warning" %}
You must comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

*The OpenStreetMap Tile Server (as used above) ToS can be* [*found here*](https://operations.osmfoundation.org/policies/tiles)*. Other servers may have different terms.*
{% endhint %}

{% hint style="success" %}
Please consider crediting flutter\_map. It helps us to gain more awareness, which helps make this project better for everyone!
{% endhint %}

## `RichAttributionWidget`

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/RichAttributionWidget-class.html>" %}

An animated, interactive attribution layer that supports both logos/images (displayed permanently) and text (displayed in a popup controlled by an icon button adjacent to the logos).

It is heavily customizable (in both animation and contents), and designed to easily meet the needs of most ToSs out of the box.

<div align="center" data-full-width="false"><figure><img src="/files/WtJ0CmBqZ0RXTgX3te72" alt="An icon and a button displayed over a map, in the bottom right corner"><figcaption><p>Closed <code>RichAttributionWidget</code></p></figcaption></figure> <figure><img src="/files/vtAA1uBs2HJBKlRZfShE" alt="A white box with attribution text displayed over a map" width="375"><figcaption><p>Opened <code>RichAttributionWidget</code>, as in the example app</p></figcaption></figure></div>

<pre class="language-dart"><code class="lang-dart">children: [
  RichAttributionWidget(
    attributions: [
<strong>      // Suggested attribution for the OpenStreetMap public tile server
</strong>      TextSourceAttribution(
        'OpenStreetMap contributors',
        onTap: () => launchUrl(Uri.parse('https://openstreetmap.org/copyright')),
      ),
    ],
  ),
],
</code></pre>

For more information about configuration and all the many options this supports, see the in-code API documentation.

## `SimpleAttributionWidget`

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/SimpleAttributionWidget-class.html>" %}

We also provide a more 'classic' styled box, similar to those found on many web maps. These are less customizable, but might be preferred over `RichAttributionWidget` for maps with limited interactivity.

<figure><img src="/files/6BnEAEMCqKasgrKKXMyB" alt=""><figcaption><p><code>SimpleAttributionWidget</code>, as in the example app</p></figcaption></figure>

```dart
children: [
  SimpleAttributionWidget(
    source: Text('OpenStreetMap contributors'),
  ),
],
```


# Layer Interactivity

{% hint style="info" %}
Layer interactivity is different to map interactivity. See [Interaction Options](/usage/options/interaction-options) to control map interactivity.
{% endhint %}

The following layers support 'interactivity':

* [Polyline Layer](/layers/polyline-layer)
* [Polygon Layer](/layers/polygon-layer)
* [Circle Layer](/layers/circle-layer)

***

* These layers don't provide their own 'gesture' callbacks, such as `onTap`
* These layers automatically perform [hit testing](#user-content-fn-1)[^1] with Flutter APIs
  * This means layers report hit on elements through the standard Flutter hit system, and can therefore be detected & handled externally through standard widgets: see [#detecting-hits-and-gestures](#detecting-hits-and-gestures "mention")
  * For advanced information about how flutter\_map hit tests, see [Hit Testing Behaviour](/layers/layer-interactivity/hit-testing-behaviour)
* This may optionally be combined with flutter\_map APIs
  * This allows individual hit elements to be identified externally, through a mechanism of a notifier and element metadata: see [#identifying-hit-elements](#identifying-hit-elements "mention")

## Detecting hits & gestures

You may be used to using widgets such as `GestureDetector` or `MouseRegion` to detect gestures on other normal widgets. These widgets ask the child to decide whether they were hit, before doing their own logic - e.g. converting the hit to the appropriate callback depending on the gesture.

Because flutter\_map's layers are just widgets, they can also be wrapped with other widgets and inserted into the map's `children`.

This means you can simply wrap layers with `GestureDetector`s (for example) which will execute callbacks when the layer is hit. Layers tell Flutter they were hit *only* if at least one of their elements (such as a `Polygon`) were hit.

Here's an example of how you would detect taps/clicks on polygons, and convert a cursor to a click indicator when hovering over a polygon:

<pre class="language-dart"><code class="lang-dart">class _InteractivityDemoState extends State&#x3C;InteractivityDemo> {
    Widget build(BuildContext context) {
        return FlutterMap(
            // ...
            children: [
                // ...
<strong>                MouseRegion(
</strong><strong>                    hitTestBehavior: HitTestBehavior.deferToChild,
</strong><strong>                    cursor: SystemMouseCursors.click,
</strong><strong>                    child: GestureDetector(
</strong><strong>                        onTap: () {
</strong><strong>                            // ...
</strong><strong>                        },
</strong>                        child: PolygonLayer(
                            // ...
                        ),
                    ),
                ),
            ],
        );
    }
);
</code></pre>

## Identifying hit elements

To identify which elements (such as `Polygon`s) were hit, flutter\_map APIs are required:

* A `LayerHitNotifier` exposes results of hit tests
* Elements may have metadata known as `hitValue`s attached, which identify that specific element - these are then exposed by the hit notifier's events/values.
* The entire system may be strongly typed through type parameters on various parts, if all the `hitValue`s within a layer share the same type

{% stepper %}
{% step %}

### Create a hit notifier

In your widget, define a new field to hold the notifier:

<pre class="language-dart"><code class="lang-dart">class _InteractivityDemoState extends State&#x3C;InteractivityDemo> {
<strong>    final LayerHitNotifier&#x3C;String> hitNotifier = ValueNotifier(null);
</strong>}
</code></pre>

In this example, the types of the `hitValue` identifiers will be `String`s.

<details>

<summary>(Advanced) Listening to the notifier directly</summary>

If you wish to be notified about all\* hit testing events, you could use the `Listener` widget.

If you need to identify hit elements and don't necessarily need the output of a `Listener`, it's possible to listen to the notifier directly:

<pre class="language-dart"><code class="lang-dart">class _InteractivityDemoState extends State&#x3C;InteractivityDemo> {
    final LayerHitNotifier&#x3C;String> hitNotifier = ValueNotifier(null)
<strong>        ..addListener(() {
</strong><strong>            final LayerHitResult&#x3C;String>? result = hitNotifier.value;
</strong><strong>            // ...
</strong><strong>        });
</strong>}
</code></pre>

This also allows handling of `null` notifier `value`s (results). A `null` result means that the last hit test executed determined there was no hit on the layer at all. Note that the listener's callback is only executed if the previous value was not `null` (i.e. it will not be repeatedly executed for every missed hit).

</details>
{% endstep %}

{% step %}

### Attach the hit notifier to a layer

Pass the notifier to the `hitNotifier` parameter of supported layers. You'll also need to set the type parameter of the layer.

For example, for the `PolygonLayer`:

<pre class="language-dart"><code class="lang-dart">class _InteractivityDemoState extends State&#x3C;InteractivityDemo> {
    Widget build(BuildContext context) {
        return FlutterMap(
            // ...
            children: [
                // ...
<strong>                PolygonLayer&#x3C;String>(
</strong><strong>                    hitNotifier: hitNotifier,
</strong>                    polygons: [
                        // ...
                    ],
                ),
            ],
        );
    }
}
</code></pre>

{% endstep %}

{% step %}

### Add hit values to elements

These can be anything useful, and are exposed when their element is hit. Remember to set the element's type parameter.

<pre class="language-dart"><code class="lang-dart">polygons: [
<strong>    Polygon&#x3C;String>(
</strong>        points: [],
        label: "Flamingo Fields",
<strong>        hitValue: "Flamingo",
</strong>    ),
<strong>    Polygon&#x3C;String>(
</strong>        points: [],
        label: "Hedgehog House",
<strong>        hitValue: "Hedgehog",
</strong>    ),
    // ...
],
</code></pre>

{% endstep %}

{% step %}
&#x20; <a href="#detecting-hits-and-gestures" class="button primary" data-icon="arrow-progress">Detecting hits & gestures</a>
{% endstep %}

{% step %}

### Handle hits on elements

Once you have a callback (such as the callback to `GestureDetector.onTap`), you can handle individual hit events.

To do this, the notifier exposes events of type `LayerHitResult` when the layer is hit. These results can be retrieved through the notifier's `value` getter:

```dart
final LayerHitResult<String>? result = hitNotifier.value;
```

{% hint style="info" %}
Most users can ignore results which are `null` (when getting the result within a gesture callback, for example).
{% endhint %}

The result exposes 3 properties:

* `hitValues`: the hit values of all elements that were hit, ordered by their corresponding element, first-to-last, visually top-to-bottom
* `coordinate`: the geographic coordinate of the hit location (which may not lie on any element)
* `point`: the screen point of the hit location

Therefore, it's unnecessary to use `MapOptions.on...` in combination with layer interactivity to detect the position of a tap.

Elements without a hit value are not included in `hitValues`. Therefore, it may be empty if elements were hit but no `hitValue`s were defined.
{% endstep %}
{% endstepper %}

## Example

```dart
class _InteractivityDemoState extends State<InteractivityDemo> {
    final LayerHitNotifier<String> hitNotifier = ValueNotifier(null);

    Widget build(BuildContext context) {
        return FlutterMap(
            // ...
            children: [
                // ...
                MouseRegion(
                    hitTestBehavior: HitTestBehavior.deferToChild,
                    cursor: SystemMouseCursors.click,
                    child: GestureDetector(
                        onTap: () {
                            final LayerHitResult<String>? result = hitNotifier.value;
                            if (result == null) return;
                            
                            for (final hitValue in result.hitValues) {
                                print('Tapped on a $hitValue');
                            }
                            print('Living at ${result.coordinate}');
                        },
                        child: PolygonLayer<String>(
                            hitNotifier: hitNotifier,
                            polygons: [
                                Polygon<String>(
                                    points: [], // overlapping coordinates with 2nd
                                    label: "Flamingo Fields",
                                    hitValue: "Flamingo",
                                ),
                                Polygon<String>(
                                    points: [], // overlapping coordinates with 1st
                                    label: "Hedgehog House",
                                    hitValue: "Hedgehog",
                                ),
                            ],
                        ),
                    ),
                ),
            ],
        );
    }
);
```

[^1]: Determining whether the position resulting from a pointer event is within one or more elements of the layer, or within the layer at all.


# Hit Testing Behaviour

The behaviour of hit testing can be confusing at first. These rules define how hit testing usually behaves:

1. Gesture callbacks in `MapOptions` are always invoked, no matter what is within the layers or the result of `hitTest`s in those layers, with the exception of custom defined hit test behaviours (not those layers that support interactivity, see [Layer Interactivity](/layers/layer-interactivity)), such as applying `GestureDetector`s around `Marker.child`ren

{% hint style="warning" %}
`GestureDetector`s absorb hit tests, and so corresponding callbacks in `MapOptions` will not be invoked if they are defined/invoked in the `GestureDetector`.

Workarounds to resolve this are discussed below.
{% endhint %}

2. Hit testing is always\* performed on interactive-capable layers (see [Layer Interactivity](/layers/layer-interactivity)) even if they have not been set-up for interactivity: hit testing != interactivity
3. Non-interactable layers (such as [Overlay Image Layer](/layers/overlay-image-layer)) have no defined `hitTest`, and behaviour is situation dependent
4. A successful hit test (`true`) from an interactable layer will prevent hit testing on layers below it in the `children` stack

To change this behaviour, make use of these three widgets, wrapping them around layers when and as necessary:

* [`IgnorePointer`](https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html)
* [`AbsorbPointer`](https://api.flutter.dev/flutter/widgets/AbsorbPointer-class.html)
* `TranslucentPointer`: a general purpose 'widget' included with flutter\_map that allows the child to hit test as normal, but also allows widgets beneath it to hit test as normal, both seperately

***

For example, a marker with a `GestureDetector` child that detects taps beneath a `Polyline` will not detect a tap, no matter if the `PolylineLayer` has a defined `hitNotifier` or the `Polyline` has a defined `hitValue`. A defined `onTap` callback in `MapOptions` would be called however. If the `Marker` were no longer obscured by the `Polyline`, it's `onTap` callback would be fired instead of the one defined in `MapOptions`.

However, this behaviour could be changed by wrapping the `PolylineLayer` with a `TranslucentPointer`. This would allow interacitivity to function as normal, but also allow the `Marker` beneath to have it's `onTap` callback fired. Further wrapping another `TransclucentPointer` around the `MarkerLayer` would allow all 3 detections to function.


# Using OpenStreetMap (direct)

{% hint style="info" %}
This does not apply to users using OpenStreetMap data through other tile servers, only to users using the public OpenStreetMap tile servers directly.
{% endhint %}

{% hint style="danger" %}

## On non-web platforms, the inadequately identified requests to the public OpenStreetMap tile servers are blocked

Due to excessive usage, requests where the 'User-Agent' header is inadequately set have been blocked by the OpenStreetMap Foundation (who operate the tile servers).

The UA can be set through the `TileLayer.userAgentPackageName` argument (or manually).

**If this is either unspecified or set to a generic string (like 'com.example.app'), then requests will return a** [**blocked tile**](https://wiki.openstreetmap.org/wiki/Blocked_tiles)**.**

This does not apply to the web, where the UA cannot be set differently to what is provided by the browser.

To restore access, follow [#what-should-i-do](#what-should-i-do "mention").
{% endhint %}

flutter\_map wants to help keep map data available for everyone. One of the largest sources of this data is OpenStreetMap. OpenStreetMap data powers the majority of non-proprietary maps - from actual map tiles/images to navigation data - in existence today. The data itself is free for everyone under the [ODbL](https://opendatacommons.org/licenses/odbl/).

The OpenStreetMap Foundation run OpenStreetMap as a not-for-profit. They also provide a public tile server at <https://tile.openstreetmap.org>, which is run on donations and volunteering time. This server is used throughout this documentation for code examples, and in our demo app, and is available at the following template URL:

```
https://tile.openstreetmap.org/{z}/{x}/{y}.png
```

{% hint style="warning" %}

## The OpenStreetMap public tile server is NOT free to use by everyone

{% embed url="<https://operations.osmfoundation.org/policies/tiles/>" %}
OpenStreetMap public tile server usage policy
{% endembed %}

flutter\_map can be setup to conform to these requirements - but it may not conform by default.

It is your own responsibility to comply with any appropriate restrictions and requirements set by your chosen tile server/provider. Always read their Terms of Service. Failure to do so may lead to any punishment, at the tile server's discretion.
{% endhint %}

The OpenStreetMap public tile server is without cost (for users), but, "without cost" ≠ "without restriction" ≠ "open".

<details>

<summary>Why has unidentified traffic been blocked?</summary>

[Data collected by OSM](https://planet.openstreetmap.org/tile_logs/) on 2025-06-09 shows flutter\_map as the largest single user-agent in terms of the average number of tile requests made per second over the day.

Whilst it is true that there are multiple user agents smaller who make up an overall much larger portion of total usage - for example, leaflet.js's usage is split across the browsers' user-agents, as is flutter\_map usage on the web - the usage of flutter\_map cannot be ignored.

The top 7 user-agents are shown below, in order (with traffic rounded to the nearest whole tile). ('Missed' tiles are those which required fresh rendering, and are more expensive than most other requests.)

<table><thead><tr><th width="453.3333740234375">User-Agent</th><th width="120">tiles/second</th><th width="110">"missed" t/s</th></tr></thead><tbody><tr><td><mark style="background-color:green;"><strong>flutter_map (*)</strong></mark><br><em>This represents the</em> <a data-footnote-ref href="#user-content-fn-1"><em>majority</em></a> <em>of FM users on non-web platforms.</em></td><td>1610</td><td>53</td></tr><tr><td>Mozilla/5.0 QGIS/*</td><td>1155</td><td>358</td></tr><tr><td>Mozilla/5.0 ...</td><td>476</td><td>33</td></tr><tr><td>com.facebook.katana</td><td>263</td><td>3</td></tr><tr><td><mark style="background-color:green;"><strong>Dart/* (dart:io)</strong></mark><br><em>This represents FM users on older versions (not on web) &#x26; other Flutter mapping libraries not using FM (not on web).</em></td><td>182</td><td>17</td></tr><tr><td>Mozilla/5.0 ...</td><td>175</td><td>6</td></tr><tr><td>Mozilla/5.0 ...</td><td>171</td><td>14</td></tr></tbody></table>

Looking at data revealed on 2025-05-28, the vast majority of daily tile requests (more than 99 million) came from unidentified apps using flutter\_map. Nearly 8 million came from apps using the 'com.example.app' identifier (which is inadequate), and around 6.5 million came from apps copying the example app's identifier (which caused OSM to block that identifier as well).

</details>

## What should I do?

{% stepper %}
{% step %}

### Consider switching tile servers

Our docs list multiple alternatives, many of which have free tiers suitable for hobbyists, affordable pricing for commercial usage, and one which is extremely flexible.

Most of these are built off the same OpenStreetMap data, so the actual information contained within the map won't change (although a change in style may not show some data).

If you're a commercial user and want the best balance of flexibility and affordability, consider setting up your own private tile server, based on the OpenStreetMap data! In any case, the OpenStreetMap tile server doesn't offer uptime guarantees, which may be important to your business.

If you're sticking with OpenStreetMap's server, consider preparing a backup.
{% endstep %}

{% step %}

### Read the OpenStreetMap Tile Usage Policy

If you still want to use OpenStreetMap, you must read the policy and comply with its restrictions and requirements. It also contains some background info as to why this is important.

{% embed url="<https://operations.osmfoundation.org/policies/tiles/>" %}
OpenStreetMap public tile server usage policy
{% endembed %}

To note two important general terms:

> Should any users or patterns of usage nevertheless cause problems to the service, access may still be blocked without prior notice.

> This policy may change at any time subject to the needs and constraints of the project. Commercial services, or those that seek donations, should be especially aware that access may be withdrawn at any point: you may no longer be able to serve your paying customers if access is withdrawn.

Also note all the other requirements, which may require you to make adjustments to your project...
{% endstep %}

{% step %}

### Make all necessary adjustments

Check the OSM policy for all the adjustments you might need to make. Here's some common ones:

* **Enable conforming caching**

  <div data-gb-custom-block data-tag="hint" data-style="success" class="hint hint-success"><p>v8.2.0 introduces automatically enabled <a data-mention href="/pages/4d32gLWYU6dgow1QgXXD">/pages/4d32gLWYU6dgow1QgXXD</a>! This is designed to meet the caching requirements of the usage policy. Upgrade to v8.2.0 to enable this functionality.</p></div>

  There's also other options to implement caching or offline mapping to meet the requirements, and go beyond the capabilities of the built-in caching.
* **Add sufficient attribution**

  <div data-gb-custom-block data-tag="hint" data-style="success" class="hint hint-success"><p>The <code>RichAttributionWidget</code> or <code>SimpleAttributionWidget</code> can both be used to setup attribution which looks great, is unintrusive, and is conforming - provided you add the necessary sources. See <a data-mention href="/pages/mqeXxeWT3FbYrRyK8iiy">/pages/mqeXxeWT3FbYrRyK8iiy</a> for more info and a simple code snippet you can add to meet the attribution requirement.</p></div>

  You can also add attribution in any other way that meets the requirements.
* **Set a more specific user-agent to identify your client**

  <div data-gb-custom-block data-tag="hint" data-style="success" class="hint hint-success"><p>You should set <code>TileLayer.userAgentPackageName</code>: see the <a data-mention href="/pages/sigQGN7PWlMiWVZ2JErs#recommended-setup">/pages/sigQGN7PWlMiWVZ2JErs#recommended-setup</a> for the <code>TileLayer</code>.<br>This is not necessary when running solely on the web, where it is not possible to set a User-Agent manually.</p></div>

{% endstep %}
{% endstepper %}

[^1]: Some users may use an entirely custom User-Agent. Most users using FMTC are not included.


# Using Mapbox

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Mapbox.

Mapbox's Maps home page: [mapbox.com/maps](https://docs.fleaflet.dev/tile-servers/www.mapbox.com/maps)\
Mapbox's Maps pricing page: [mapbox.com/pricing#maps](https://docs.fleaflet.dev/tile-servers/www.mapbox.com/pricing#maps)\
Mapbox's Maps documentation: [docs.mapbox.com/api/maps/static-tiles](https://docs.mapbox.com/api/maps/static-tiles)
{% endhint %}

{% hint style="warning" %}
Tile providers that also provide their own SDK solution to display tiles will often charge a higher price to use 3rd party libraries like flutter\_map to display their tiles. Just another reason to switch to an alternative provider.
{% endhint %}

To display their map tiles, Mapbox usually provides a 'Style URL' for map styles. However, to integrate with 3rd-party APIs, they also provide a 'CARTO Integration URL', and tiles requested through this endpoint consume the 'Static Tiles API' quota. This URL needs no extra configuration to integrate with flutter\_map.

## Integration

### Custom Styles

Mapbox supports creating and using custom styled maps through Studio.

{% hint style="warning" %}
Before attempting to use your style, ensure it actually has layers, and not just a Basemap, which will not appear in the tiles. The image below shows a style which will not work. If you only see blank tiles, but no errors, this is likely the cause.

To create a new style based on the Standard style, choose a template when creating the new style.

<img src="/files/g91QyG01z4FN826JPqvT" alt="" data-size="original"> &#x20;
{% endhint %}

1. Create a custom style using the editor
2. Click "Share...", or the share icon
3. Choose between Draft or Production
4. Scroll to the bottom of the dialog, and choose Third Party
5. Select "CARTO" from the dropdown menu
6. Click the copy button to copy the template URL, then paste it into your `TileLayer`

<figure><img src="/files/UWza3L1nDeO25zgCP6nS" alt="" width="563"><figcaption></figcaption></figure>

#### Configuring [Retina Mode](/layers/tile-layer#retina-mode)

The URL includes an '@2x' string, which forces usage of high-definition tiles on all displays, without extra setup.

Should you need to let flutter\_map interfere, and only use retina tiles on retina/high-density displays, replace it with the '{r}' placeholder, then see [Tile Layer](/layers/tile-layer#retina-mode) for more information.

#### Other configurations

The maximum zoom level that Mapbox supports is 22, so it is recommended to set `maxNativeZoom` or `maxZoom` as such.

#### Adding Attribution

{% hint style="warning" %}
Attribution is required, and quite extensive compared to some alternatives: see [docs.mapbox.com/help/getting-started/attribution](https://docs.mapbox.com/help/getting-started/attribution/).

Consider using the [Attribution Layer](/layers/attribution-layer#richattributionwidget), which meets the requirements by supporting both logo and text attribution.
{% endhint %}

### Prebuilt Styles

Mapbox offers a variety of ready-made map styles that don't require customization. An example URL can be found in [the example here](https://docs.mapbox.com/api/maps/static-tiles/#example-request-retrieve-raster-tiles-from-styles).

This URL should be used as above, although you may need to insert the placeholders manually.


# Using Google Maps

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Google Maps

Google Maps' home page: <https://developers.google.com/maps>\
Google Maps' pricing page: <https://mapsplatform.google.com/pricing/>\
Google Maps' documentation page: <https://developers.google.com/maps/documentation/tile/2d-tiles-overview>
{% endhint %}

{% hint style="success" %}
Raster 2D tiles from Google Maps is a relatively new offering, which makes Google Maps directly compatible with flutter\_map, whilst abiding by the Google Maps' ToS (which the previous method did not).
{% endhint %}

{% hint style="warning" %}
Tile providers that also provide their own SDK solution to display tiles will often charge a higher price to use 3rd party libraries like flutter\_map to display their tiles. Just another reason to switch to an alternative provider.
{% endhint %}

To display map tiles from Google Maps, a little more effort is needed, as they require a complex session token system.

Therefore, we haven't yet constructed a full guide, so please read the Google Developers documentation for more info:

{% embed url="<https://developers.google.com/maps/documentation/tile/2d-tiles-overview>" %}


# Using Tracestrack

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Tracestrack.

Tracestrack's home page: [tracestrack.com](https://tracestrack.com/)
{% endhint %}

Tracestrack has a wide variety of raster styles, including topographic and standard bases, in multiple different color schemes (light & dark). It also supports vector tiles, which makes it super flexible - use raster tiles now, and be ready to switch to vector tiles!

1. Use <https://console.tracestrack.com/explorer> to find your ideal style
2. Create an account
3. Copy the URL template from the result box at the bottom and paste it into your `TileLayer`


# Using Thunderforest

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Thunderforest (or Gravitystorm Limited).

Thunderstorm's home page: [thunderforest.com](https://www.thunderforest.com)\
Thunderstorm's pricing page: [thunderforest.com/pricing](https://www.thunderforest.com/pricing)\
Thunderstorm's documentation page: [thunderforest.com/docs/map-tiles-api](https://www.thunderforest.com/docs/map-tiles-api)
{% endhint %}

Thunderforest is a popular tiered-payment (with free tier) tile provider solution, especially for generic mapping applications. Note that using 'flutter\_map' uses up your 'Map Tiles API' requests.

{% embed url="<https://www.thunderforest.com/tutorials/flutter/>" %}
Visit our documentation over on Thunderforest's site (may be outdated)
{% endembed %}


# Using Stadia Maps

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Stadia Maps.

Stadia Maps' home page: [stadiamaps.com](https://stadiamaps.com/)\
Stadia Maps' pricing page: [stadiamaps.com/pricing](https://stadiamaps.com/pricing/)\
Stadia Maps' documentation page: [docs.stadiamaps.com](https://docs.stadiamaps.com/)
{% endhint %}

To display their map tiles, Stadia Maps usually provides a 'Static Maps Base URL' for map styles. However, to integrate with 3rd-party APIs, they also provide a 'Raster XYZ PNGs URL' , and tiles requested through this endpoint consume 'Styled Raster Map Tiles' credits. This URL needs no extra configuration to integrate with flutter\_map.

Retina tiles (high-DPI) tiles are available. Use the URLs containing '@2x' instead of '{r}'. The maximum zoom level that Stadia Maps supports is 20, so it is recommended to set `maxNativeZoom` or `maxZoom` as such.

{% hint style="warning" %}
Attribution is required, see [docs.stadiamaps.com/#legal-details-required-attribution](https://docs.stadiamaps.com/#legal-details-required-attribution).

Consider using the [Attribution Layer](/layers/attribution-layer#richattributionwidget) or [Attribution Layer](/layers/attribution-layer#simpleattributionwidget)s, which meet the requirements.
{% endhint %}

## Styles

Stadia Maps offers a variety of ready-made map styles that don't require customization. URLs are found with the style: see the available [map styles](https://docs.stadiamaps.com/themes). The URL should be used as above.

## Vector Usage

Stadia Maps' also provides vector tiles. For more information about using vector tiles, please see [Raster vs Vector Tiles](/why-and-how/how-does-it-work/raster-vs-vector-tiles#using-vector-tiles).&#x20;

However, please note that this method of integration is still experimental. Many of the Stadia Maps styles utilize advanced features of the Mapbox GL JSON style language which are not yet well-supported.


# Using Lima Labs

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Lima Labs.

Lima Labs' home page: [maps.lima-labs.com](https://maps.lima-labs.com/)\
Lima Labs' pricing page: [maps.lima-labs.com/index.php/pricing](https://maps.lima-labs.com/index.php/pricing/)
{% endhint %}

If you're looking for *extremely* low cost & often free (even for commercial usage), but still reliable, and low frills service, Lima Labs provides high quality raster tiles with one style based off the standard CARTO style.

1. Sign up for an account - Lima Labs operates manual verification, so you may have to wait a few days
2. Use the common URL ([https://cdn.lima-labs.com/0/0/0.png?api=](https://cdn.lima-labs.com/0/0/0.png?api=bear)\_\_\_) with your API key


# Using Bing Maps

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Bing Maps.

Bing Maps' home page: [microsoft.com/maps](https://www.microsoft.com/maps/)
{% endhint %}

To display map tiles from Bing Maps, a little more effort is needed, as they use a RESTful API with quadkeys, rather than the standard slippy map system.

Luckily, we've constructed all the code you should need below! Feel free to copy and paste into your projects.

{% hint style="info" %}
Thanks to [Luka Glušica](https://github.com/luka-glusica) for discovering the [basic initial implementation](https://github.com/fleaflet/flutter_map/issues/1197#issuecomment-1478763824).
{% endhint %}

{% hint style="warning" %}
Attribution is not demonstrated here, but may be required. Ensure you comply with Bing Maps' ToS.
{% endhint %}

{% code title="bing\_maps.dart" overflow="wrap" lineNumbers="true" %}

```dart
// All compatible imagery sets
enum BingMapsImagerySet {
  road('RoadOnDemand', zoomBounds: (min: 0, max: 21)),
  aerial('Aerial', zoomBounds: (min: 0, max: 20)),
  aerialLabels('AerialWithLabelsOnDemand', zoomBounds: (min: 0, max: 20)),
  canvasDark('CanvasDark', zoomBounds: (min: 0, max: 21)),
  canvasLight('CanvasLight', zoomBounds: (min: 0, max: 21)),
  canvasGray('CanvasGray', zoomBounds: (min: 0, max: 21)),
  ordnanceSurvey('OrdnanceSurvey', zoomBounds: (min: 12, max: 17));

  final String urlValue;
  final ({int min, int max}) zoomBounds;

  const BingMapsImagerySet(this.urlValue, {required this.zoomBounds});
}

// Custom tile provider that contains the quadkeys logic
// Note that you can also extend from the CancellableNetworkTileProvider
class BingMapsTileProvider extends NetworkTileProvider {
  BingMapsTileProvider({super.headers});

  String _getQuadKey(int x, int y, int z) {
    final quadKey = StringBuffer();
    for (int i = z; i > 0; i--) {
      int digit = 0;
      final int mask = 1 << (i - 1);
      if ((x & mask) != 0) digit++;
      if ((y & mask) != 0) digit += 2;
      quadKey.write(digit);
    }
    return quadKey.toString();
  }

  @override
  Map<String, String> generateReplacementMap(
    String urlTemplate,
    TileCoordinates coordinates,
    TileLayer options,
  ) =>
      super.generateReplacementMap(urlTemplate, coordinates, options)
        ..addAll(
          {
            'culture': 'en-GB', // Or your culture value of choice
            'subdomain': options.subdomains[
                (coordinates.x + coordinates.y) % options.subdomains.length],
            'quadkey': _getQuadKey(coordinates.x, coordinates.y, coordinates.z),
          },
        );
}

// Custom `TileLayer` wrapper that can be inserted into a `FlutterMap`
class BingMapsTileLayer extends StatelessWidget {
  const BingMapsTileLayer({
    super.key,
    required this.apiKey,
    required this.imagerySet,
  });

  final String apiKey;
  final BingMapsImagerySet imagerySet;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: http.get(
        Uri.parse(
          'http://dev.virtualearth.net/REST/V1/Imagery/Metadata/${imagerySet.urlValue}?output=json&include=ImageryProviders&key=$apiKey',
        ),
      ),
      builder: (context, response) {
        if (response.data == null) return const Placeholder();

        return TileLayer(
          urlTemplate: (((((jsonDecode(response.data!.body)
                          as Map<String, dynamic>)['resourceSets']
                      as List<dynamic>)[0] as Map<String, dynamic>)['resources']
                  as List<dynamic>)[0] as Map<String, dynamic>)['imageUrl']
              as String,
          tileProvider: BingMapsTileProvider(),
          subdomains: const ['t0', 't1', 't2', 't3'],
          minNativeZoom: imagerySet.zoomBounds.min,
          maxNativeZoom: imagerySet.zoomBounds.max,
        );
      },
    );
  }
}
```

{% endcode %}


# Offline Mapping

Using maps without an Internet connection is common requirement. Luckily, there are a few options available to you to implement offline mapping in your app.

* [Caching](#caching)\
  Automatically store tiles as the user loads them through interacting with the map, usually on a temporary basis
* [Bulk downloading](#bulk-downloading)\
  Download an entire area/region of tiles in one shot, ready for a known no-Internet situation
* [Bundling](#bundled-map-tiles)\
  Provide a set of pre-determined tiles to all users through app assets or the filesystem

## Caching

{% hint style="info" %}
Prior to v8.2, flutter\_map only provided caching in-memory. All cached tiles will be cleared after the app session is ended.

Since v8.2, this is no longer the case.
{% endhint %}

Caching is used usually to improve user experience by reducing network waiting times, not necessarily to prepare for no-Internet situations. Caching can be temporary (eg. in-memory/session-only, where the cache is cleared after the app is closed), or longer-term (eg. app cache, where the OS takes responsibility for clearing the app cache when necessary/when requested).

Temporary caching is automatically implemented by Flutter's `ImageCache` when using tile providers backed by `ImageProvider`s (such as the `NetworkTileProvider`).

However, many tile servers, such as the OpenStreetMap tile server, will require you to implement longer-term caching!&#x20;

Since v8.2.0, flutter\_map provides [Caching](/layers/tile-layer/caching) functionality built-in - this automatically enables long-term caching, and is extensible to allow other caching providers to be used. The default built-in provider satisfies many requirements of both users and tile servers automatically.

If you'd prefer not to use built-in caching, you can:

* **Try another `MapCachingProvider` implementation if you still only need simple caching**\
  These are compatible with the standard `NetworkTileProvider` (and cancellable version), as with built-in caching, but use a different storage mechanism.\
  You can create an implementation yourself, or use one provided by a 3rd-party/plugin. See [Caching](/layers/tile-layer/caching#using-other-providers) for more info.
* **Use a caching HTTP client**\
  `NetworkTileProvider.httpClient` can be used to set a custom HTTP client. Some packages, such as '[package:http\_cache\_client](https://pub.dev/packages/http_cache_client)', offer clients which perform HTTP caching, similar to built-in caching.
* **Use a different `TileProvider`**\
  Tile providers have full control over how a tile is generated to be displayed, and so there are many possibilities to integrate caching.
  * Create a [custom `TileProvider`](/plugins/create/tile-providers) with a caching `ImageProvider` (either custom or using a package such as '[package:cached\_network\_image](https://pub.dev/packages/cached_network_image)')
  * Use [flutter\_map\_cache](https://github.com/josxha/flutter_map_cache)\
    Caching created prior to built-in caching, lightweight and MIT licensed, but with more pre-provided options than built-in caching through 3rd-party storage implementations
  * Use [flutter\_map\_tile\_caching](https://github.com/JaffaKetchup/flutter_map_tile_caching)\
    Supports very advanced caching use-cases and [#bulk-downloading](#bulk-downloading "mention"), but GPL licensed

## Bulk Downloading

{% hint style="warning" %}
You must comply with the appropriate restrictions and terms of service set by your tile server. Always read the ToS before using a tile server. Failure to do so may lead to any punishment, at the tile server's discretion. Many tile servers will forbid or restrict bulk downloading, as it places additional strain on their servers.
{% endhint %}

Bulk downloading is used to prepare for known no-Internet situations by downloading map tiles, then serving these from local storage.

Bulk downloading is more complex than [#caching](#caching "mention"), especially for regions that are a non-rectangular shape. Implementing this can be very time consuming and prone to issues.

The [community maintained plugin 'flutter\_map\_tile\_caching'](https://github.com/JaffaKetchup/flutter_map_tile_caching) includes advanced bulk downloading functionality, of multiple different region shapes, and other functionality. It is however GPL licensed. To help choose whether FMTC or DIY is more appropriate for your use case, please see:

{% embed url="<https://fmtc.jaffaketchup.dev/is-fmtc-right-for-me>" %}

## Bundled Map Tiles

If you have a set of custom raster tiles that you need to provide to all your users, you may want to consider bundling them together, to make a them easier to deploy to your users. Bundles can be provided in two formats.

### Contained

Container formats, such as the traditional MBTiles, or the more recent PMTiles, store tiles usually in a database or binary internal format.

These require a special parser to read on demand, usually provided as a `TileProvider` by a plugin. The following community-maintained plugins are available to read these formats:

* [MBTiles](https://wiki.openstreetmap.org/wiki/MBTiles): [flutter\_map\_mbtiles](https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_mbtiles) ([vector\_map\_tiles\_mbtiles ](https://github.com/josxha/flutter_map_plugins/tree/main/vector_map_tiles_mbtiles)when using vector tiles)
* [PMTiles](https://github.com/protomaps/PMTiles): [flutter\_map\_pmtiles](https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_pmtiles) ([vector\_map\_tiles\_pmtiles](https://github.com/josxha/flutter_map_plugins/tree/main/vector_map_tiles_pmtiles) when using vector tiles, also works in online contexts)

### Uncontained

When uncontained, tiles are usually in a tree structure formed by directories, usually 'zoom/x/y.png'. These don't require special parsing, and can be provided directly to the `TileLayer` using one of the built-in local `TileProvider`s.

#### `AssetTileProvider`

You can ship an entire tile tree as part of your application bundle, and register it as assets in your app's pubspec.yaml.

This means that they will be downloaded together with your application, keeping setup simple, but at the expense of a larger application bundle size.

{% hint style="warning" %}
If using `AssetTileProvider`, every sub-directory of the tree must be listed seperately. See the example application's 'pubspec.yaml' for an example.
{% endhint %}

#### `FileTileProvider`

This allows for more flexibility: you could store a tile tree on a remote server, then download the entire tree later to the device's filesystem, perhaps after intial setup, or just an area that the user has selected.

This means that the setup may be more complicated for users, and it introduces a potentially long-running blocking action, but the application's bundle size will be much smaller.


# Other Options

## Other Servers

There are plenty of other tile servers you can choose from, free or paid. Most provide a static tile service/API, usually called Static Tiles or just Tile Requests (if no vector tiles are supported).

If you're responsible for a tile server, and want to have your tile server and setup instructions listed in this documentation, please get in touch!

A good catalogue of servers (usually called Providers elsewhere) can be found at the websites below:

{% embed url="<https://wiki.openstreetmap.org/wiki/Raster_tile_providers>" %}

{% embed url="<https://switch2osm.org/providers/>" %}

## Serving Your Own Tiles

Switch2OSM also provides detailed instructions on how to serve your own tiles: this can be surprisingly economical and enjoyable if you don't mind a few hours in a Linux console.

However, this will require a very high-spec computer, especially for larger areas, and hosting this might be more complicated than it's worth. It's very difficult to fully understand the technologies involved.

{% embed url="<https://switch2osm.org/serving-tiles/>" %}


# Plugins List

There are many independently maintained 'plugins' created by the 'flutter\_map' community that give extra, prebuilt functionality, saving you even more time and potentially money.

Some pages in this documentation provide direct links to these plugins to make it easier for you to find a suitable plugin.

However, if you're just browsing, a full list is provided below (in no particular order), containing many of the available plugins. You can click on any of the tiles to visit its GitHub repo or pub.dev package.

{% hint style="danger" %}
Although these plugins have been checked by 'flutter\_map' maintainers, we do not accept responsibility for any issues or threats posed by independently maintained plugins.

Use plugins at your own risk.
{% endhint %}

{% hint style="warning" %}
There is no guarantee that any of these plugins will support the latest version of flutter\_map. Please remain patient with the plugin authors/owners.
{% endhint %}

{% hint style="info" %}
Many plugins provide multiple methods to achieve similar goals. It is recommended to read the documentation of each potential plugin before using it in your project, as they might have slightly different feature sets or stability.
{% endhint %}

## Tools

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_animations (TesteurManiak)</strong></td><td>Replacement <code>MapController</code> which provides animated movement alternatives</td><td><a href="https://github.com/TesteurManiak/flutter_map_animations">https://github.com/TesteurManiak/flutter_map_animations</a></td></tr></tbody></table>

## Additional Layers

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>vector_map_tiles (greensopinion)</strong></td><td>Suite of tools and layers for working with vector maps and associated style files</td><td><a href="https://github.com/greensopinion/flutter-vector-map-tiles">https://github.com/greensopinion/flutter-vector-map-tiles</a></td></tr><tr><td><strong>flutter_map_geojson2 (Zverik)</strong></td><td>Parse GeoJSON into map features, with simplespec styling support</td><td><a href="https://pub.dev/packages/flutter_map_geojson2">https://pub.dev/packages/flutter_map_geojson2</a></td></tr><tr><td><strong>flutter_map_polywidget (TimBaumgart)</strong></td><td>Layer that allows any widget to be displayed inside a positioned box, similar to <a data-mention href="/pages/IvTwjpFpt0mYepO5ir9v">/pages/IvTwjpFpt0mYepO5ir9v</a></td><td><a href="https://pub.dev/packages/flutter_map_polywidget">https://pub.dev/packages/flutter_map_polywidget</a></td></tr><tr><td><strong>fluttermap_heatmap (tprebs)</strong></td><td>Layer that represents multiple data points in a density-to-color relationship</td><td><a href="https://github.com/tprebs/fluttermap_heatmap">https://github.com/tprebs/fluttermap_heatmap</a></td></tr><tr><td><strong>flutter_map_compass (josxha)</strong></td><td>Simple compass that illustrates the direction of the map rotation (not the user's real heading)</td><td><a href="https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_compass">https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_compass</a></td></tr></tbody></table>

## Offline Mapping

To help choose between one of these plugins or a DIY solution, see:

{% content-ref url="/pages/bpcomOuvTGL5xNNpSAYp" %}
[Offline Mapping](/tile-servers/offline-mapping)
{% endcontent-ref %}

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_tile_caching (JaffaKetchup)</strong></td><td>Advanced, performant, highly configurable caching &#x26; bulk downloading (under a GPL license)</td><td><a href="https://github.com/JaffaKetchup/flutter_map_tile_caching">https://github.com/JaffaKetchup/flutter_map_tile_caching</a></td></tr><tr><td><strong>flutter_map_cache (josxha)</strong></td><td>Lightweight mid-term tile caching with support for most storage backends and request cancellation</td><td><a href="https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_cache">https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_cache</a></td></tr></tbody></table>

## Tile Container Parsers/Providers

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_mbtiles (josxha)</strong></td><td>Offline and online MBTiles parser and provider (vector variant also available)</td><td><a href="https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_mbtiles">https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_mbtiles</a></td></tr><tr><td><strong>flutter_map_pmtiles (josxha)</strong></td><td>Offline and online PMTiles parser and provider (vector variant also available)</td><td><a href="https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_pmtiles">https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_pmtiles</a></td></tr></tbody></table>

## Better `Marker`s

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_location_marker (tlserver)</strong></td><td>Provides a prebuilt solution to display the user's location and heading/direction</td><td><a href="https://github.com/tlserver/flutter_map_location_marker">https://github.com/tlserver/flutter_map_location_marker</a></td></tr><tr><td><strong>flutter_map_marker_popup (rorystephenson)</strong></td><td>Provides a prebuilt solution to display a popup above a marker when tapped</td><td><a href="https://github.com/rorystephenson/flutter_map_marker_popup">https://github.com/rorystephenson/flutter_map_marker_popup</a></td></tr><tr><td><strong>flutter_map_floating_marker_titles (androidseb)</strong></td><td>Enables the display of 'floating' titles over markers</td><td><a href="https://github.com/androidseb/flutter_map_floating_marker_titles">https://github.com/androidseb/flutter_map_floating_marker_titles</a></td></tr><tr><td><strong>flutter_map_dragmarker (ibrierley)</strong></td><td>An interactive draggable <code>Marker</code></td><td><a href="https://github.com/ibrierley/flutter_map_dragmarker">https://github.com/ibrierley/flutter_map_dragmarker</a></td></tr></tbody></table>

### Marker Clustering

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_supercluster (rorystephenson)</strong></td><td>Superfast™ marker clustering solution, without animations</td><td><a href="https://github.com/rorystephenson/flutter_map_supercluster">https://github.com/rorystephenson/flutter_map_supercluster</a></td></tr><tr><td><strong>flutter_map_marker_cluster (lpongetti)</strong></td><td>Beautiful and animated marker clustering solution</td><td><a href="https://github.com/lpongetti/flutter_map_marker_cluster">https://github.com/lpongetti/flutter_map_marker_cluster</a></td></tr><tr><td><strong>flutter_map_radius_cluster (rorystephenson)</strong></td><td>Marker clustering solution with support for <code>async</code> marker searching within a radius</td><td><a href="https://github.com/rorystephenson/flutter_map_radius_cluster">https://github.com/rorystephenson/flutter_map_radius_cluster</a></td></tr></tbody></table>

## Better `Polyline`s & `Polygon`s

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_line_editor (ibrierley)</strong></td><td>Provides interactive dynamic creation &#x26; editing of <code>Polyline</code>s and <code>Polygon</code>s</td><td><a href="https://github.com/ibrierley/flutter_map_line_editor">https://github.com/ibrierley/flutter_map_line_editor</a></td></tr><tr><td><strong>flutter_map_polygon_editor (tdz4)</strong></td><td>Provides interactive dynamic creation &#x26; editing of <code>Polyline</code>s and <code>Polygon</code>s</td><td><a href="https://pub.dev/packages/flutter_map_polygon_editor">https://pub.dev/packages/flutter_map_polygon_editor</a></td></tr><tr><td><strong>line_animator (ibrierley)</strong></td><td>Interpolates along a set of points, allowing gradual drawing of lines and animating moving markers</td><td><a href="https://github.com/ibrierley/line_animator">https://github.com/ibrierley/line_animator</a></td></tr></tbody></table>

## Miscellaneous

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_rastercoords (jeremejazz)</strong></td><td>Basic utilities to make working with non-geographical tiled large images easier</td><td><a href="https://github.com/jeremejazz/flutter_map_rastercoords">https://github.com/jeremejazz/flutter_map_rastercoords</a></td></tr></tbody></table>


# Creating Plugins

Plugins extend flutter\_map's functionality. There's no specific definition, but they often offer additional utilities and/or layers.

If one of the [independently maintained plugins](/plugins/list) doesn't suit your needs, then you can create your own, to achieve maximum customizability.

## Submitting A New Plugin

If you've made your own plugin that you're willing to share, please let us know in the #plugins channel on the flutter\_map Discord server. We can then add it to the [Plugins List](/plugins/list). We're always looking forward to see what you've made!

When submitting a plugin, please ensure the plugin:

* *preferably* includes <kbd>flutter\_map\_</kbd> in the name, by convention
* *preferably* includes the <kbd>#flutter-map</kbd> pub.dev topic
* is available via a standard pub.dev package installation
* includes good documentation (at least to setup basic functionality), and a code example


# Tile Providers

One common requirement is a custom `TileProvider`, and potentially a custom `ImageProvider` inside. This will allow your plugin to intercept all tile requests made by a map, and take your own action(s), before finally returning a tile.

{% hint style="info" %}
Check the [Plugins List](/plugins/list) for providers that already implement the behaviour you wish to replicate.
{% endhint %}

## 1. Extending `TileProvider`

To create your own usable `TileProvider`, the first step is making a class that `extends` the abstract class, and adding a constructor.

The constructor should accept an argument of `super.headers`, without a `const`ant default.

```dart
class CustomTileProvider extends TileProvider {
    CustomTileProvider({
        // Suitably initialise your own custom properties
        super.headers, // Accept a `Map` of custom HTTP headers
    })
}
```

{% hint style="info" %}
If using an object that needs closing/cancelling, such as an `HttpClient`, override the `dispose` method.
{% endhint %}

## 2. Setup Tile Retrieval

`TileProvider`s must implement a method to return an `ImageProvider` (the image of a tile), given its coordinates and the `TileLayer` it is used within.

{% hint style="success" %}
It is best to put as much logic as possible into a custom `ImageProvider`, to avoid blocking the main thread.
{% endhint %}

There's two methods that could be called by flutter\_map internals to retrieve a tile: `getImage` or `getImageWithCancelLoadingSupport`.

Prefer overriding `getImageWithCancelLoadingSupport` for `TileProvider`s that can cancel the loading of a tile in-flight, if the tile is pruned before it is fully loaded. An example of a provider that may be able to do this is one that makes HTTP requests, as HTTP requests can be aborted on the web (although Dart does not 'natively' support it yet, so a library such as Dio is necessary). Otherwise, `getImage` must be overridden.

{% tabs %}
{% tab title="With Cancel Loading Support" %}
In addition to the coordinates and `TileLayer`, the method also takes a `Future<void>` that is completed when the tile is pruned. It should be listened to for completion (for example, with `then`), then used to trigger the cancellation.

For an example of this, see [Tile Providers](/layers/tile-layer/tile-providers#cancellablenetworktileprovider).

```dart
    @override
    bool get supportsCancelLoading => true;
    
    @override
    ImageProvider getImageWithCancelLoadingSupport(
        TileCoordinates coordinates,
        TileLayer options,
        Future<void> cancelLoading,
    ) =>
        CustomCancellableImageProvider(
            url: getTileUrl(coordinates, options),
            fallbackUrl: getTileFallbackUrl(coordinates, options),
            cancelLoading: cancelLoading,
            tileProvider: this,
        );
```

{% endtab %}

{% tab title="Without Cancel Loading Support" %}

```dart
    @override
    ImageProvider getImage(TileCoordinates coordinates, TileLayer options) =>
        CustomImageProvider(
            url: getTileUrl(coordinates, options),
            fallbackUrl: getTileFallbackUrl(coordinates, options),
            tileProvider: this,
        );
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
If developing a plugin, you may wish to adjust the 'User-Agent' header has been to further differentiate your plugin's traffic from vanilla 'flutter\_map' traffic.
{% endhint %}

Tile providers can support the `MapCachingProvider` contract/interface to support built-in caching. See [Caching Providers](/plugins/create/caching-providers).

## (Optionally) Override URL Generation

Some custom `TileProvider`s may want to change the way URLs are generated for tiles, given a coordinate.

It's possible to override:

* how the `urlTemplate`'s placeholders are populated: `populateTemplatePlaceholders`
* the values used to populate those placeholders: `generateReplacementMap`
* the generation method itself: `getTileUrl` and/or `getTileFallbackUrl`

{% hint style="warning" %}
Avoid overriding the generation method itself, as it is not usually necessary.
{% endhint %}


# Layers

Creating a new map layer is a great way to achieve a more custom, performant, map design. For example, it might be used to display a scale bar, or overlay a grid.

## 1. Creating A Layer Widget

It starts with a normal `StatelessWidget` or `StatefulWidget`, which then starts its widget tree with a widget dependent on whether the layer is designed to be either 'mobile' or 'static', depending on the purpose of the layer. For more information, see [Layers](/usage/layers#mobile-vs-static-layers).

{% tabs %}
{% tab title="Mobile Layers" %}

```dart
class CustomMobileLayer extends StatelessWidget {
  const CustomMobileLayer({super.key});

  @override
  Widget build(BuildContext context) {    
    return MobileLayerTransformer(
      child: // your child here
    );
  }
}
```

{% endtab %}

{% tab title="Static Layers" %}

```dart
class CustomStaticLayer extends StatelessWidget {
  const CustomStaticLayer({super.key});

  @override
  Widget build(BuildContext context) {
    return SizedBox.expand();
    // and/or
    return Align();
  }
}
```

{% endtab %}
{% endtabs %}

## 2. Hooking Into Inherited State

Then, there are three possible methods that could be used to retrieve separate 'aspects' of the state of the map.

Calling these inside a `build` method will also cause the layer to rebuild automatically when the depended-on aspects change.

```dart
final camera = MapCamera.of(context);
final controller = MapController.of(context);
final options = MapOptions.of(context);
```

{% hint style="warning" %}
Using these methods will restrict this widget to only being usable inside the context of a `FlutterMap`.
{% endhint %}


# Caching Providers

[Caching](/layers/tile-layer/caching) is extensible.

To create a new caching provider which is compatible with all tile providers which are compatible with built-in caching, create a class which implements `MapCachingProvider` and its required interface.

```dart
class CustomCachingProvider implements MapCachingProvider {
  @override
  bool get isSupported => throw UnimplementedError();

  @override
  Future<({Uint8List bytes, CachedMapTileMetadata metadata})?> getTile(
    String url,
  ) {
    throw UnimplementedError();
  }

  @override
  Future<void> putTile({
    required String url,
    required CachedMapTileMetadata metadata,
    Uint8List? bytes,
  }) {
    throw UnimplementedError();
  }
}
```

Compatible tile providers must check `isSupported` before using `getTile` or `putTile`.

Check in-code documentation for more detail on requirements and expectations.

***

Many providers may only work on certain platforms. In this case, implementations can mix-in `DisabledMapCachingProvider` on unsupported platforms:

```dart
class CustomCachingProvider
    with DisabledMapCachingProvider
    implements MapCachingProvider {}
```

***

If a provider cannot read a tile from the cache, but the tile is present, the provider should:

* throw `CachedMapTileReadFailure` with as much information as possible from `readTile`
* repair or replace the tile with a fresh & valid one
* ensure other mechanisms are resilient to corruption

This could occur due to corruption, for example a power cut, a sudden storage issue, or an intentional modification that did not comply with the expected specification.

It is not the provider's responsibility to check that stored tile bytes are valid. Providers may return invalid or undecodable bytes to tile providers, which they should handle gracefully by falling back to a non-caching alternative to retrieve a tile, and safely updating the invalid stored tile.


# Supporters

{% embed url="<https://github.com/sponsors/fleaflet>" %}

Thanks for considering donating to us: we're extremely grateful for all donations of any size!

We'll donate 15% of what we receive to the OpenStreetMap Foundation, as a thanks for their excellent work. The remainder goes directly to improving flutter\_map, whether that's through supporting external contributors through bounties, covering any unforeseen and future costs, or supporting the maintainers.

Donations allow us to keep flutter\_map up-to-date and full of features that save you from expensive tile server fees and that make us Flutter's №1 non-commercially aimed map client.

## Past Supporters

Check out our public past supporters on our GitHub Sponsors page. A huge thanks to all of them, they keep this project going!

And a huge thanks to all these people and organisations that supported us previously. In no particular order, thanks to:

* androidseb
* Roundtrip
* corepuncher
* Maxi
* V3ntus
* OCELL
* ishcherbina
* ... and everyone else who donated anonymously


# Credits & Contributing

## Credits

Huge thanks to everyone who uses, supports, and/or contributes to flutter\_map in any way, you've helped make the most popular non-commercially aimed mapping solution for Flutter!

In particular, thanks go to:

* All the current maintainers:
  * @ibrierley
  * @JaffaKetchup
  * @mootw (previously @MooNag)
  * @TesteurManiak
  * @josxha
* All the previous maintainers:
  * @johnpryan - the original founder of this project
  * @kengu
  * @maRci002
* The authors of this documentation:
  * @JaffaKetchup
* Anyone who has contributed to making flutter\_map: [Contributor List](https://github.com/fleaflet/flutter_map/graphs/contributors)
* Anyone who has made plugins for flutter\_map: [Plugins List](/plugins/list)
* Anyone who has donated to flutter\_map: [Supporters](/thanks/supporters#past-supporters)

## Contributing

{% embed url="<https://github.com/fleaflet/flutter_map/blob/master/CONTRIBUTING.md>" %}

### Apply To Be A Maintainer

{% embed url="<https://forms.gle/jEeygGJZtBWUdLFf6>" %}


# flutter\_map

A versatile mapping package for Flutter. Simple and easy to learn, yet completely customizable and configurable, it's the best choice for mapping in your Flutter app.

[![pub.dev](https://camo.githubusercontent.com/dcdb87d5d32ce7d0a83302ccccd6e5c70c55894e7c7920c6417b13761c8c773c/68747470733a2f2f696d672e736869656c64732e696f2f7075622f762f666c75747465725f6d61702e7376673f6c6162656c3d4c61746573742b56657273696f6e)](https://pub.dev/packages/flutter_map) [![stars](https://camo.githubusercontent.com/e3069fba0ddc64303cf9a1a60be83b6f789cfee3b3b39c2c062e63cece4d26f7/68747470733a2f2f62616467656e2e6e65742f6769746875622f73746172732f666c6561666c65742f666c75747465725f6d61703f6c6162656c3d737461727326636f6c6f723d677265656e2669636f6e3d676974687562)](https://github.com/fleaflet/flutter_map/stargazers) [![likes](https://camo.githubusercontent.com/102d04f1c16aad975caa6d413d84ab27b815a53910e72b41b81146b03b71f75a/68747470733a2f2f696d672e736869656c64732e696f2f7075622f6c696b65732f666c75747465725f6d61703f6c6f676f3d666c7574746572)](https://pub.dev/packages/flutter_map/score)        [![codecov](https://camo.githubusercontent.com/2e49ab046582d625b4559b04b726e596f6f0492d9ed6aef49d7cea3a3c3036d7/68747470733a2f2f636f6465636f762e696f2f67682f666c6561666c65742f666c75747465725f6d61702f67726170682f62616467652e7376673f746f6b656e3d4c6759675a5130596a6a)](https://codecov.io/gh/fleaflet/flutter_map) [![Open Issues](https://camo.githubusercontent.com/2237656d711e52f75b8c088f51236d5e2c910bd19d38131dc47aefe07a68c5af/68747470733a2f2f62616467656e2e6e65742f6769746875622f6f70656e2d6973737565732f666c6561666c65742f666c75747465725f6d61703f6c6162656c3d4f70656e2b49737375657326636f6c6f723d677265656e)](https://github.com/fleaflet/flutter_map/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) [![Open PRs](https://camo.githubusercontent.com/30fb50b9f4b92e66c01fd6fce5fe3fb7dec5419de1492953772c635bb1ef2886/68747470733a2f2f62616467656e2e6e65742f6769746875622f6f70656e2d7072732f666c6561666c65742f666c75747465725f6d61703f6c6162656c3d4f70656e2b50527326636f6c6f723d677265656e)](https://github.com/fleaflet/flutter_map/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aopen)

## Why Choose flutter\_map?

{% hint style="success" %}
See why you should use flutter\_map for your project:
{% endhint %}

{% content-ref url="/pages/eqVurD9aPws4irN9ZUCf" %}
[Why Choose flutter\_map?](/v7/choose)
{% endcontent-ref %}

## Code Demo

Setting up an interactive and compliant[^1] map is simpler than making your lunch-time coffee! It can be accomplished in just under 30 lines and a minute or two to install.

This code snippet demonstrates **everything** you need for a simple map (in under 30 lines!), but of course, FM is capable of much more than just this, and you could find yourself lost in the many options available and possibilities opened!

{% code lineNumbers="true" fullWidth="true" %}

```dart
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';

@override
Widget build(BuildContext context) {
  return FlutterMap(
    options: MapOptions(
      initialCenter: LatLng(51.509364, -0.128928), // Center the map over London
      initialZoom: 9.2,
    ),
    children: [
      TileLayer( // Display map tiles from any source
        urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', // OSMF's Tile Server
        userAgentPackageName: 'com.example.app',
        // And many more recommended properties!
      ),
      RichAttributionWidget( // Include a stylish prebuilt attribution widget that meets all requirments
        attributions: [
          TextSourceAttribution(
            'OpenStreetMap contributors',
            onTap: () => launchUrl(Uri.parse('https://openstreetmap.org/copyright')), // (external)
          ),
          // Also add images...
        ],
      ),
    ],
  );
}
```

{% endcode %}

## Get Help

Not quite sure about something? No worries, we're here to help!

* Check the [#faqs](#faqs "mention") below, and double check the documentation
* Then, for bug reports & feature requests: check for previous issues, then ask in [GitHub Issues](https://github.com/fleaflet/flutter_map/issues)
* Then, for support & everything else: ask in [flutter\_map Discord server](https://discord.gg/BwpEsjqMAH) or [GitHub Discussions](https://github.com/fleaflet/flutter_map/discussions)

{% hint style="info" %}
We're a community maintained project and the maintainers would greatly appriciate any help in implementing features and fixing bugs! Feel free to jump in: <https://github.com/fleaflet/flutter_map/blob/master/CONTRIBUTING.md>.

Please remember that we are volunteers and cannot gurantee support. The standard Code of Conduct is here to keep our community a safe and friendly place for everyone: <https://github.com/fleaflet/flutter_map/blob/master/CODE_OF_CONDUCT.md>.
{% endhint %}

### FAQs

We get quite a lot of similar questions, so please check if your question is here before you ask!

{% content-ref url="/pages/lL0cVxkdhQ8OmMlS1zdD" %}
[How Does It Work?](/v7/getting-started/explanation)
{% endcontent-ref %}

<details>

<summary>How can I use a custom map style?<br>How can I prevent POI/labels rotating when the map rotates?<br>How can I remove certain POI/labels from the map?</summary>

Unfortunately, this library cannot provide this functionality. It has no control over the tiles displayed in the `TileLayer`. This is a limitation of the technology, not this library.

This is because raster tiles are just images generated by a 3rd party tile server (dictated by your URL template), and therefore cannot be changed by the library that displays the tiles. Filters can be applied to the entire tile, such as an emulated dark mode, but these effects do not look great.

However, tilesets can be styled. This is the most effective way of using custom styles. These methods may help you with this:&#x20;

* You may wish to use a commercial service like Mapbox Studio, which allows you to style multiple tilesets. See [Using Mapbox](/v7/tile-servers/using-mapbox).
* Alternatively, you can experiment with vector tiles. These are not pre-rendered, and so allow any style you desire to be applied on the fly. See [Raster vs Vector Tiles](/v7/getting-started/explanation/raster-vs-vector-tiles#vector-tiles).
* Your last option is to serve tiles yourself. See [Other Options](/v7/tile-servers/other-options).

</details>

<details>

<summary>How can I route a user between two locations?<br>Why does the <code>Polyline</code> only go in a straight line between two points?</summary>

See [Polyline Layer](/v7/layers/polyline-layer#routing-navigation).

</details>

<details>

<summary>How can I add a <code>Marker</code> where the user's location is?<br>How can I center the map on the user's location?</summary>

This is beyond the scope of flutter\_map. However, you can use the [community maintained plugin 'flutter\_map\_location\_marker'](https://github.com/tlserver/flutter_map_location_marker) to do this.

Alternatively, use the 'location' and 'compass' packages to generate a stream of the user's location and heading, and feed that to a `Marker` using a `StreamBuilder`.

</details>

<details>

<summary>Why don't any map tiles appear?</summary>

If no tiles are appearing (if tiles are appearing on some zoom levels but not others, see below), try performing the following debugging steps:

1. Is the `templateUrl` or WMS configuration correct (to the best of your knowledge)?
2. Have you followed the platform specific setup ([Installation](/v7/getting-started/installation#additional-setup)) instructions (if applicable for your platform)?
3. Check the Network tab either in Flutter DevTools or the browser DevTools to see why/if the tile requests are failing.
4. If none of those solved the issue, check if there are any widgets covering the map, or any errors in the console (particularly in release mode)?

</details>

<details>

<summary>Why does the map disappear/go grey when I zoom in far?<br>Why does the map stop zooming in even though I know there are more zoom levels?</summary>

If tiles are disappearing when you zoom in, the default grey background of the `FlutterMap` widget will shine through. This usually means that the tile server doesn't support these higher zoom levels.

If you know that there are more tiles available further zoomed in, but flutter\_map isn't showing them and scaling a particular zoom level instead, it's likely because the `TileLayer.maxNativeZoom` property is set too low (it defaults to 19).

To set/change the zoom level at which FM starts scaling tiles, change the `TileLayer.maxNativeZoom` property. To set/change the max zoom level that can actually be zoomed to (hard limit), use `MapOptions.maxZoom`.

</details>

<details>

<summary>How can I make the map 3D, or view it as a globe?</summary>

Unfortunately, this isn't supported, partially due to lack of time on the maintainer's part to implement this feature, partially due to technical limitations. PRs are welcome!

</details>

[^1]: (includes necessary attribution)


# Why Choose flutter\_map?

We think flutter\_map hits the spot for many Flutter apps. Find out why!

## Feature Highlights

<table data-card-size="large" data-view="cards" data-full-width="true"><thead><tr><th align="center"></th><th align="center"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td align="center">🗺️ <strong>No more vendor lock-in: better flexibility, quality, and price</strong></td><td align="center"><p>We natively support any static <a data-footnote-ref href="#user-content-fn-1">raster*</a> tiles! <em>Bring your own tiles</em> from your own server, the user's device, a tile container, or another externally-operated tile server. Use any service, but always be free to change to get the best fit, quality, and price.</p><p></p><p>Still want to use those familiar maps? Consider great quality and better priced alternatives, or use the more mainstream Google Maps or Mapbox Maps with flutter_map<a data-footnote-ref href="#user-content-fn-2">*</a>.</p></td><td></td></tr><tr><td align="center">💪 <strong>Stress free setup &#x26; easy to use</strong></td><td align="center"><p>Migrating from a commercial library (such as Google Maps) has never been easier! No more complex platform-specific setup, no more difficult API keys: just add a widget, bring some tiles, and you're done. Check out our <a data-mention href="/pages/YICQhcdQtAOWmU2QJcuB#code-demo">/pages/YICQhcdQtAOWmU2QJcuB#code-demo</a> to see just how simple it is.</p><p></p><p>Our great documentation and community support makes it easy to setup and customize fast and keeps you moving if you need some more detailed help.</p></td><td></td></tr><tr><td align="center">🧩 <strong>Wide ecosystem of plugins</strong></td><td align="center">A huge community of developers maintain an ecosystem of plugins for you to supercharge flutter_map with.</td><td></td></tr><tr><td align="center">➕ <strong>Customize and expand endlessly</strong></td><td align="center">Add interactive and highly customizable polygons, polylines, and markers (which support widget children) to your map easily and quickly. And because we're 100% Flutter, it's easy to add your own stuff on top without messing around in platform views.</td><td></td></tr></tbody></table>

{% hint style="warning" %}
If you're looking for [vector tiles](/v7/getting-started/explanation/raster-vs-vector-tiles#raster-tiles), we don't currently support them natively. We only support raster tiles at the moment.

However, [options are available](/v7/getting-started/explanation/raster-vs-vector-tiles#using-vector-tiles), and the community and us are looking into improving support in the future!
{% endhint %}

## Independent Works

{% hint style="success" %}
Don't just take it from us - see how others view flutter\_map!

These show our many strengths, ~~and some things you may want to stick with the standard commercial SDKs for (vector tiles, essentially)~~ :smile:
{% endhint %}

{% embed url="<https://archive.org/details/incorporating-maps-into-flutter-a-study-of-mapping-sdks-and-their-integration-in>" fullWidth="false" %}
Original: <https://www.theseus.fi/bitstream/handle/10024/820026/Ushakov_Sergey.pdf>\
Archive: <https://archive.org/details/incorporating-maps-into-flutter-a-study-of-mapping-sdks-and-their-integration-in>
{% endembed %}

{% embed url="<https://player.vimeo.com/video/995398561?autopause=0&autoplay=0&byline=0&color=00adef&portrait=0&title=0>" fullWidth="false" %}
A talk at Fluttercon Europe 2024 comparing solutions to displays markers on maps between different SDKs, with a look towards flutter\_map's future at the end\
Original: <https://www.droidcon.com/2024/09/03/the-quest-to-display-widget-markers-on-a-map/>
{% endembed %}

## Project Showcase

{% hint style="success" %}
These great projects all make use of flutter\_map!

But there's more: check out the [dependents list on GitHub](https://github.com/fleaflet/flutter_map/network/dependents), and the #showcase channel for more projects on our Discord server.
{% endhint %}

### Non-OSS Sponsors

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Map Marker</strong></td><td>Your places organizer</td><td><a href="/files/l8EEhkIz3wMTGGIVeV2j">/files/l8EEhkIz3wMTGGIVeV2j</a></td><td><a href="https://www.mapmarker.app/">https://www.mapmarker.app/</a></td></tr><tr><td><strong>Your advert here!</strong></td><td><p>Want to advertise your project here? For more information, and to apply, please see:</p><ul><li><a href="https://docs.google.com/forms/d/e/1FAIpQLSeXALT0XVnWdl8vTcYQUz9l3mC7j63Et1MIkkEnnn7BgToRtw/viewform?usp=sf_link">The application form for non-OSS projects</a></li><li><a href="https://docs.google.com/forms/d/e/1FAIpQLSeM3RgRc-QG7diODXd29DzuQWAWlutUQC3uR_b0cSwbaOkOjg/viewform?usp=sf_link">The application form for OSS and/or non-profit projects</a> (it's free!)</li></ul></td><td><a href="/files/SXX6mr1Eq9i7kTxfiNmF">/files/SXX6mr1Eq9i7kTxfiNmF</a></td><td></td></tr></tbody></table>

### OSS and/or Non-Profit Projects

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th><select multiple><option value="288089d75dba4cc2930ef1e879241692" label="Open Source" color="blue"></option><option value="602c8c9d2d4b4c9bbdc1b704dbed0fe5" label="100% Non-Profit" color="blue"></option></select></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><strong>Every Door</strong></td><td><span data-option="288089d75dba4cc2930ef1e879241692">Open Source, </span><span data-option="602c8c9d2d4b4c9bbdc1b704dbed0fe5">100% Non-Profit</span></td><td>The most efficient OpenStreetMap editor for surveying shops and benches</td><td><a href="https://every-door.app/">https://every-door.app/</a></td><td><a href="/files/gLjBq3BCAhLGPaTuLrFe">/files/gLjBq3BCAhLGPaTuLrFe</a></td></tr><tr><td><strong>Ente Photos</strong></td><td><span data-option="288089d75dba4cc2930ef1e879241692">Open Source</span></td><td>End-to-end encrypted alternative to Google Photos </td><td><a href="https://ente.io/">https://ente.io/</a></td><td><a href="/files/muAERaZeGhotv3DmmbyK">/files/muAERaZeGhotv3DmmbyK</a></td></tr><tr><td>🇳🇱 <strong>Stichting Zeilvaart Warmond (Track &#x26; Trace)</strong></td><td><span data-option="602c8c9d2d4b4c9bbdc1b704dbed0fe5">100% Non-Profit</span></td><td>Follow participating ships during the race, and use the replay function after the race to analyse the performance</td><td><a href="https://tt.zeilvaartwarmond.nl/">https://tt.zeilvaartwarmond.nl/</a></td><td><a href="/files/SCR0oO1a99zssPP4PPjI">/files/SCR0oO1a99zssPP4PPjI</a></td></tr><tr><td><strong>Your advert here!</strong></td><td></td><td><p>Want to advertise your project here? For more information, and to apply, please see:</p><ul><li><a href="https://docs.google.com/forms/d/e/1FAIpQLSeXALT0XVnWdl8vTcYQUz9l3mC7j63Et1MIkkEnnn7BgToRtw/viewform?usp=sf_link">The application form for non-OSS projects</a></li><li><a href="https://docs.google.com/forms/d/e/1FAIpQLSeM3RgRc-QG7diODXd29DzuQWAWlutUQC3uR_b0cSwbaOkOjg/viewform?usp=sf_link">The application form for OSS and/or non-profit projects</a> (it's free!)</li></ul></td><td></td><td><a href="/files/SXX6mr1Eq9i7kTxfiNmF">/files/SXX6mr1Eq9i7kTxfiNmF</a></td></tr></tbody></table>

[^1]: See below for information about vector tile support.

[^2]: It may cost more to use services which provide their own SDKs through flutter\_map, but there's a reason they do that ;)


# Supporters

## Support Us

Please consider donating to keep this project going! We're extremely grateful all donations of any size. They keep us going and will allow us to cover any unforeseen or future costs, and potentially open up more doors and opportunities in future!

We'll donate 15% of what we receive to the OpenStreetMap Foundation, as a thanks for their excellent work. The remainder goes directly to improving flutter\_map.

Enter a nickname to be listed below in [#past-supporters](#past-supporters "mention"), and enter your Discord username to be granted the "Supporter" role on our server and gain access to a special supporter-only chat channel. These benefits last indefinitely (but may take 48 hours to grant, as this is done manually).

***(Donation links appear below)***

{% hint style="info" %}
We'd love it if you could help keep our payment processing fees low! Stripe has a complex payment fee structure, but by avoiding more mainstream donation collection platforms, we can keep most of your donation.

Of course, please choose whatever is most convienient and safe for you - we're really thankful for anything you can spare. Card and Google/Apple Pay options are always available, and are quick and easy!

If you live in the UK, please use the GBP link and pay via Bacs Direct Debit if that isn't a problem.

If you live in the EU/Eurozone:

* If you live in the Netherlands and have an iDEAL compatible bank account, please select the EUR link and use iDEAL to pay if that suits you
* If you don't mind using SEPA Direct Debit, please use the EUR link
* Otherwise, please consider paying in GBP

If you live in the US, please consider using ACH/bank account methods via the USD link, otherwise, please consider paying in GBP.

If you live elsewhere, please consider paying in GBP.

Note that paying in a currency different to your own may incur conversion fees and international transaction fees set by your banking provider.

If you would like to donate a larger amount, offer a bounty, or setup recurring donations, please get in touch! Also check out [Why Choose flutter\_map?](/v7/choose) if you'd like to get some advertising space for your project.
{% endhint %}

{% hint style="info" %}
As any donated money is not being exchanged for goods, and we are not a registered business, refunds are not given (although they may be given in exceptional circumstances at our discretion).
{% endhint %}

{% hint style="info" %}
We use Stripe for payments, analytics, and other business services.  Stripe may collect personal data including via cookies and similar technologies.  The personal data Stripe collects may include transactional data and identifying information about devices that connect to its services. Stripe uses this information to operate and improve the services it provides to us, including for fraud detection, loss prevention, authentication, and analytics related to the performance of its services.  You can learn more about Stripe and read its privacy policy at [https://stripe.com/privacy](https://stripe.com/gb/privacy).
{% endhint %}

* [**Donate with GBP**](https://donate.stripe.com/14kg1s1CB0dSdk4cMM) (Bacs Direct Debit, + standard options)
* [**Donate with EUR**](https://donate.stripe.com/cN202ugxvbWA93O146) (iDEAL, SEPA Direct Debit, + standard options)
* [**Donate with USD**](https://donate.stripe.com/6oE9D46WVe4I2FqcMN) (ACH/Bank Transfer, + standard options)

## Past Supporters

Huge thanks to all our past supporters, you help keep this project going. In no particular order, thanks to:

* androidseb
* Roundtrip
* corepuncher
* Maxi
* V3ntus
* OCELL
* ishcherbina
* ... and everyone else who donated anonymously


# Credits & Contributing

## Credits

Huge thanks to everyone who uses, supports, and/or contributes to flutter\_map in any way, you've helped make the most popular non-commercially aimed mapping solution for Flutter!

In particular, thanks go to:

* All the current maintainers:
  * @ibrierley
  * @JaffaKetchup
  * @mootw (previously @MooNag)
  * @TesteurManiak
  * @josxha
* All the previous maintainers:
  * John P Ryan - the original founder of this project, over at AppTree Software
  * @kengu
  * @maRci002
* The authors of this documentation:
  * @JaffaKetchup
* Anyone who has contributed to making flutter\_map: [Contributor List](https://github.com/fleaflet/flutter_map/graphs/contributors)
* Anyone who has made plugins for flutter\_map: [Plugins List](/v7/plugins/list)
* Anyone who has donated to flutter\_map: [Supporters](/v7/supporters#past-supporters)

## Contributing

{% embed url="<https://github.com/fleaflet/flutter_map/blob/master/CONTRIBUTING.md>" %}

### Apply To Be A Maintainer

{% embed url="<https://forms.gle/jEeygGJZtBWUdLFf6>" %}


# How Does It Work?

{% hint style="info" %}
If you don't know about standard map things, such as the latitude/longitude system and projections, you should probably read about these first!

*If you want a truly British insight into this, look no further than:* [*https://youtu.be/3mHC-Pf8-dU*](https://youtu.be/3mHC-Pf8-dU) *&* [*https://youtu.be/jtBV3GgQLg8*](https://youtu.be/jtBV3GgQLg8)*.*
{% endhint %}

## Layers

Interactive maps are often[^1] formed from multiple layers of data, which can be panned (moved), rotated, ~~and sometimes tilted/pitched~~, based on the user's gesture input, or another programmatic control.

## Tile Basics

One type of layer included on every map is known as a tile layer, which displays tiles, square segments of a map.

When multiple tiles, which are each the same dimensions, are laid out around each other, they give the illusion of one continuous map.

Tiles can be referenced/identified in a few different ways, such as:

* Slippy Map Convention (the most popular/common)
* [TMS](https://wiki.openstreetmap.org/wiki/TMS) (very similar to the Slippy Map Convention)
* [WMS](https://wiki.openstreetmap.org/wiki/WMS)&#x20;
* [WMTS](https://en.wikipedia.org/wiki/Web_Map_Tile_Service)

Tiles themselves can be of two types:

* Raster\
  Each tile is a normal pre-rendered standard image, such as JPG or PNG
* Vector\
  Each tile is a special format containing the data for the tile, and is then rendered by the end library

This library/documentation focuses on maps accessible via the Slippy Map Convention, although all are supported.

This library only supports raster tiles. See [Raster vs Vector Tiles](/v7/getting-started/explanation/raster-vs-vector-tiles) for more information.

### Slippy Map Convention

{% hint style="info" %}
For more information about the Slippy Map Convention, visit [the OpenStreetMap Wiki](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames).
{% endhint %}

Slippy map tiles are accessed by 3 coordinates, x/y/z.

X & Y coordinates correspond to all the latitudes and longitudes contained within that tile, however they are not actual longitude and latitude. For example, geographic coordinate (61.127, -0.123) [might be](#user-content-fn-2)[^2] in the tile (128983, 430239).

The Z value represents the current zoom level, where one tile ([0/0/0](https://tile.openstreetmap.org/0/0/0.png)) covers the entire planet with extremely low detail at level 0, to level 20 (although some tile servers will support even higher zoom levels) where over 1 trillion tiles are required to cover the entire surface of the Earth.

## Sourcing Tiles

Tiles, especially raster tiles, take a lot of computing power and time to generate, because of the massive scale of all the input and output data. Therefore, most tiles are sourced externally, from an online tile server (either publicly or by users holding an API key), or sometimes from the local filesystem or asset store of the app.

{% hint style="success" %}
Unlike commercial SDKs you may be more familiar with, we don't have an opinion on which tiles you should use, and you'll need to bring your own instead :)
{% endhint %}

{% content-ref url="/pages/4rzSoAS2RWublrqODkHc" %}
[Other Options](/v7/tile-servers/other-options)
{% endcontent-ref %}

## Tile Providers

A tile provider (within flutter\_map) is responsible for:

* Constructing the path/URL to a tile, when given its coordinates (x/y/z): [#slippy-map-convention](#slippy-map-convention "mention")
* Using an `ImageProvider` or other mechanism to fetch that tile: [#sourcing-tiles](#sourcing-tiles "mention")
* Performing any other processing steps, such as caching

But don't worry! flutter\_map (or a plugin) creates a provider for you, so for most use cases and tile sources, you shouldn't need to handle this yourself!

{% content-ref url="/pages/x2LzdwedaPxt1VzqBAz4" %}
[Tile Providers](/v7/layers/tile-layer/tile-providers)
{% endcontent-ref %}

{% hint style="info" %}
This can be quite confusing for newcomers!

Within this library, 'tile providers' use 'tile servers' to retrieve tiles from the Internet. On the other hand, 'tile servers' and external sites usually use 'tile providers' to mean 'tile servers'!
{% endhint %}

[^1]: Most mapping libraries operate in this way

[^2]: This is not a real example of this relationship.


# Raster vs Vector Tiles

{% hint style="info" %}
It is important to note that 'flutter\_map' only supports raster tiles natively. Vector tiles can be used with a community maintained plugin.

This is described in more detail at the bottom of this page.
{% endhint %}

There are 2 main types of tiles a server can serve: raster and vector; each has their own advantages and drawbacks. This page is designed to help you choose a type for your app, and help you use vector tiles if you choose to.

## Raster Tiles

Raster tiles are the 'older' type of tile, and are raster images (usually .png or .jpg). These tiles are good because they can render quickly and easily, can be viewed without special software, and are readily available from most mapping services. As such, this makes them the popular choice for beginners.

However, raster tiles cannot be easily themed: a theme needs a whole new set of map tiles. This makes apps using light and dark themes have mismatching maps. As well as this, raster tiles usually have larger file sizes meaning slower download times, and they can become blurred/pixelated when viewed at a larger scale: a problem for users when zooming between zoom levels. Another issue is that shapes/text inside tiles cannot be rotated, hence the name 'static tiles': therefore, rotating the map will not rotate the name of a road, for example.

## Vector Tiles

Vector tiles can be considered the 'newer' standard. These images might contain a specialised format (such as .pbf) dictating the mathematics and coordinates used to draw lines and shapes. Because these tiles are drawn at render time instead of at request/server time, theming can be used to make the map fit in better with an app's theme. The math-based image means that the images/tiles can be scaled without any loss of clarity.

However it does add complexity to the rendering process as each element needs to be parsed and painted individually, meaning an impact to performance. Text elements and certain shapes can also be rotated (unlike raster tiles) to match the user's orientation, not the orientation of the map; but calculating this rotation needs to be done every frame, meaning an even larger impact on performance.

### Using Vector Tiles

Due to the complications mentioned above, 'flutter\_map' does not natively support vector tiles. However, vector tiles can be used with a [community maintained plugin (`vector_map_tiles`)](https://github.com/greensopinion/flutter-vector-map-tiles) to do this.

{% hint style="warning" %}
Worried about vector tiles performance?

Using vector tiles may significantly cut FPS and introduce jank, and that's because of the amount of UI work that must be performed on the main thread.

The community and FM maintainers are looking to improve the situation!

Keep up to date and subscribe to the issue: <https://github.com/greensopinion/flutter-vector-map-tiles/issues/120>.
{% endhint %}


# Installation

## Install

{% hint style="success" %}
All users should also [install 'latlong2'](https://pub.dev/packages/latlong2/install) to work with coordinates in 'flutter\_map'.

In the event that the `LatLng` object provided by that library conflicts with another, for example the one provided by Google Maps, you may need to [use the 'as' suffix](https://dart.dev/guides/packages#importing-libraries-from-packages).
{% endhint %}

### From [pub.dev](https://pub.dev/packages/flutter_map)

Just import the package as you would normally, from the command line:

<pre class="language-bash"><code class="lang-bash">flutter pub add flutter_map latlong2
flutter pub add <a data-footnote-ref href="#user-content-fn-1">flutter_map_cancellable_tile_provider</a> # OPTIONAL
</code></pre>

### From [github.com](https://github.com/fleaflet/flutter_map)

{% hint style="warning" %}
Unreleased commits from Git (GitHub) may not be stable.
{% endhint %}

If you urgently need the latest version, a specific branch, or a specific fork, you can use this method.

First, use [#from-pub.dev](#from-pub.dev "mention"), then add the following lines to your pubspec.yaml file, as a root object:

{% code title="pubspec.yaml" %}

```yaml
dependency_overrides:
    flutter_map:
        git:
            url: https://github.com/fleaflet/flutter_map.git
            # ref: master (or commit hash, branch, or tag)
```

{% endcode %}

## Additional Setup

### Web

#### Wasm/Renderer

{% hint style="success" %}
We support Wasm! [Build your app as normal](https://docs.flutter.dev/platform-integration/web/wasm) and benefit from potentially improved performance when the browser can handle Wasm.
{% endhint %}

#### CORS

On the web platform, [CORS ](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)restrictions designed to protect resources on websites and control where they can be loaded from. Some tile servers may not be intended for external consumption, or may be incorrectly configured, which could prevent tiles from loading. If tiles load correctly on platforms other than the web, then this is likely the cause.

See the [Flutter documentation](https://docs.flutter.dev/platform-integration/web/web-images#cross-origin-resource-sharing-cors) for more details. We load images using a standard `Image` widget.

### Android

flutter\_map needs to access the Internet to load tiles, in most cases. On Android, apps must include the INTERNET permission in their manifest. Add the following line to all manifests:

{% code title="AndroidManifest.xml" %}

```xml
<uses-permission android:name="android.permission.INTERNET"/>
```

{% endcode %}

### MacOS

flutter\_map needs to access the Internet to load tiles, in most cases. On MacOS, apps must include a dedicated entitlement. Add the following lines to 'macos/Runner/DebugProfile.entitlements' and 'macos/Runner/Release.entitlement&#x73;**':**

{% code title="\*.entitlements" %}

```xml
<key>com.apple.security.network.client</key>
<true/>
```

{% endcode %}

## Import

After installing the package, import it into the necessary files in your project:

```dart
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
```

{% hint style="warning" %}
You must comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

*The OpenStreetMap Tile Server (as used in this documentation) ToS can be* [*found here*](https://operations.osmfoundation.org/policies/tiles)*. Other servers may have different terms.*
{% endhint %}

[^1]: [Tile Providers](/v7/layers/tile-layer/tile-providers#cancellablenetworktileprovider)


# Examples

flutter\_map provides an example application showcasing much of its functionality. In some cases, the example app contains undocumented functionality, so it's definitely worth checking out!

## Live Web Demo

{% hint style="info" %}
Note that the web demo is built automatically from the ['master' branch](https://github.com/fleaflet/flutter_map), so may not reflect the the latest release on pub.dev.
{% endhint %}

{% hint style="warning" %}
Please don't abuse the web demo! It runs on limited bandwidth and won't hold up to thousands of loads.

If you're going to be straining the application, please see [#prebuilt-artifacts](#prebuilt-artifacts "mention"), and serve the application yourself.
{% endhint %}

{% embed url="<https://demo.fleaflet.dev>" %}

## Prebuilt Artifacts

If you can't build from source for your platform, our GitHub Actions CI system compiles the example app to GitHub Artifacts for Windows, Web, and Android.

The Windows and Android artifacts just require unzipping and installing the .exe or .apk found inside.

The Web artifact requires unzipping and serving, as it contains more than one unbundled file. You may be able to use [dhttpd](https://pub.dev/packages/dhttpd) for this purpose.

{% hint style="info" %}
Note that these artifacts are built automatically from the ['master' branch](https://github.com/fleaflet/flutter_map), so may not reflect the the latest release on pub.dev.
{% endhint %}

{% embed url="<https://nightly.link/fleaflet/flutter_map/workflows/master/master>" %}
Latest Build Artifacts (thanks [nightly](https://nightly.link/))
{% endembed %}

## Build From Source

If you need to use the example app on another platform, you can build from source, using the 'example' directory of the repository.

{% @github-files/github-code-block url="<https://github.com/fleaflet/flutter_map/tree/master/example>" %}


# v7 Information

{% hint style="success" %}
**v7 should be a pain free upgrade!** Although some breaking changes have been made, most applications shouldn't see any effects or need to make any changes in their code.

For this reason, we have not included migration instructions. If you run into a specific issue, please get in touch on the Discord server, where we'll be happy to help you migrate!
{% endhint %}

{% hint style="success" %}
This update has resolved our oldest open issue: [#385](https://github.com/fleaflet/flutter_map/issues/385)! We now support hit detection/interactivity on the `PolygonLayer`, `PolylineLayer`, and `CircleLayer`. For more information, check out [Layer Interactivity](/v7/layers/layer-interactivity).

Additionally, we've been focusing hard on performance improvements, and we've added (in collaboration with the excellent open source community) two stress testing pages to the example application, so you can really strain FM to see how it handles huge datasets. Check out the performance sections of the [Polygon Layer](/v7/layers/polygon-layer) and [Polyline Layer](/v7/layers/polyline-layer) for more information.

The last major change is the introduction of `StrokePattern`: we now support solid, dashed, and dotted lines in all sorts of different arrangements and configurations - again thanks to our community! See [Polyline Layer](/v7/layers/polyline-layer#pattern) for more details.

For a curated list of changes, check out our changelog. Alternatively, for a full breakdown, check out the releases and commits on our GitHub repository.
{% endhint %}

{% hint style="warning" %}
v7 supports Flutter 3.22. However, if performing the upgrade to v7 is prohibitive for whatever reason (such as waiting for dependencies to upgrade), but you would like to use Flutter 3.22, we've also released v6.2.1.

v6.2.1 only contains the necessary changes on top of v6.1.0 to support Flutter 3.22, and so we recommend upgrading to v7 to experience all the new functionality and fixes!

*Version 6.2.0 has been retracted from pub.dev. For more information, please see* [*https://github.com/fleaflet/flutter\_map/pull/1891#issuecomment-2134069848*](https://github.com/fleaflet/flutter_map/pull/1891#issuecomment-2134069848)*.*
{% endhint %}

{% embed url="<https://github.com/fleaflet/flutter_map/blob/master/CHANGELOG.md>" %}
Full Changelog
{% endembed %}


# Base Widget

```dart
FlutterMap(
    mapController: MapController(),
    options: MapOptions(),
    children: [],
);
```

Start by adding some [Layers](/v7/layers/tile-layer) to `children`, then configure the map in [Options](/v7/usage/options). Additionally, if required, add a `MapController`: [Controllers & Cameras](/v7/usage/programmatic-interaction/controllers-and-cameras).

## Placement Recommendations

It is recommended to make the map as large as possible, to allow it to display a lot of useful information easily.

As such, we recommend using a depth-based layout (eg. using `Stack`s) instead of a flat-based layout (eg. using `Column`s). The following 3rd party packages might help with creating a modern design:

* <https://pub.dev/packages/backdrop>
* <https://pub.dev/packages/sliding_up_panel>
* <https://pub.dev/packages/material_floating_search_bar_2>

If you must restrict the widget's size, you won't find a `height` or `width` property. Instead, use a `SizedBox` or `Column`/`Row` & `Expanded`.

{% hint style="info" %}
The map widget will expand as much as possible.

To avoid errors about infinite/unspecified dimensions, ensure the map is contained within a constrained widget.
{% endhint %}

### Keep Alive

If the map is displayed lazily in something like a `PageView`, changing the page and unloading the map will cause it to reset to its [initial positioning](/v7/usage/options#initial-positioning).

To prevent this, set `MapOptions.keepAlive` `true`, which will activate an internal `AutomaticKeepAliveClientMixin`. This will retain the internal state container in memory, even when it would otherwise be disposed.


# Options

To dictate & restrict what the map can and should do, regardless of its contents, it needs some guidance!

It provides options that can be categorized into three main parts:

* [Initial positioning](#initial-positioning)\
  Defines the location of the map when it is first loaded
* [Permanent rules](#permanent-rules)\
  Defines restrictions that last throughout the map's lifetime
* [Event handling](/v7/usage/programmatic-interaction/listen-to-events)\
  Defines methods that are called on specific map events

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/MapOptions-class.html>" %}

## Initial Positioning

{% hint style="info" %}
Changing these properties after the map has already been built for the first time will have no effect: they only apply on initialisation.

To control the map programatically, use a `MapController`: [Controllers & Cameras](/v7/usage/programmatic-interaction/controllers-and-cameras).
{% endhint %}

One part of `MapOptions` responsibilities is to define how the map should be positioned when first loaded. There's two ways to do this (that are incompatible):

* `initialCenter` (`LatLng`) & `initialZoom`
* `initialCameraFit`
  * by bounds (circumscribed[^1]): `CameraFit.bounds`
  * by bounds (inscribed[^2]): `CameraFit.insideBounds`
  * by coordinates (circumscribed[^3]): `CameraFit.coordinates`

It is possible to also set the map's `initialRotation` in degrees, if you don't want it North (0°) facing initially.

If rotation is enabled/allowed, if using `initialCameraFit`, prefer defining it by coordinates for a more intended/tight fit.

## Permanent Rules

One part of `MapOptions` responsibilities is to define the restrictions and limitations of the map and what users can/cannot do with it.

Some of the options are described elsewhere in this documentation, in context. In addition, the API docs show all the available options, and below is a partial list of options:

* `cameraConstraint`
  * camera bounds inside bounds: `CameraConstraint.bounds`
  * camera center inside bounds: `CameraConstraint.center`
  * *unconstrained (default): `CameraConstraint.unconstrained`*
* `maxZoom` and `minZoom`\
  Sets a hard limit on the maximum and minimum amounts that the map can be zoomed
* [`interactionOptions`](/v7/usage/options/interaction-options)\
  Configures the gestures that the user can use to interact with the map - for example, disable rotation or configure cursor/keyboard rotation

{% hint style="success" %}
Instead of `maxZoom` (or in addition to), consider setting `maxNativeZoom` per `TileLayer` instead, to allow tiles to scale (and lose quality) on the final zoom level, instead of setting a hard limit.
{% endhint %}

## Custom CRS

FM does have some support for using alternative CRSs.&#x20;

{% content-ref url="/pages/nTZ2CIbH2eECsJUZS0im" %}
[Custom CRSs](/v7/usage/options/custom-crss)
{% endcontent-ref %}

[^1]: Bounds inside camera

[^2]: Camera inside bounds

[^3]: Coordinates inside camera, as tightly as possible


# Interaction Options

The `InteractionOptions` object passed to `MapOptions.interactiveOptions` configures the gestures that the user can use to interact with the map. For example, disable rotation or configure cursor/keyboard rotation.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/InteractionOptions-class.html>" %}

## Flags

`flags` is a [bitfield](https://en.wikipedia.org/wiki/Bit_field) that enables and disables the vast majority of gestures. Although technically the type is of `int`, it is usually set with a combination of `InteractiveFlag`s.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/InteractiveFlag-class.html>" %}

{% hint style="warning" %}
Note that some gestures must be configured by other means, either instead of using flags, or in addition to.
{% endhint %}

By default, `all` gestures are enabled, but a non-interactive map can be created using `none` (and other options in addition).

{% hint style="info" %}
The recommended way to create an entirely non-interactive map is to wrap the `FlutterMap` widget in an `IgnorePointer` widget.
{% endhint %}

Otherwise, to set flags, there's two methods:

* Add flags, with the bitwise 'OR' (`|`) operator in-between\
  For example, `InteractiveFlag.drag | InteractiveFlag.rotate`
* Remove flags from `all`, using the `&` and `~` operators in-between\
  For example, `InteractiveFlag.all & ~InteractiveFlag.rotate`

## Cursor/Keyboard Rotation

Cursor/keyboard rotation is designed for desktop platforms, and allows the cursor to be used to set the rotation of the map whilst a (customizable) keyboard key (by default, any of the 'Control' keys) is held down.

The `CursorKeyboardRotationOptions` object passed to the property with the corresponding name configures this behaviour. The `CursorKeyboardRotationOptions.disabled()` constructor can be used to disable cursor/keyboard rotation.

There's many customization options, see the API docs for more information:

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/CursorKeyboardRotationOptions-class.html>" %}

## "Win" Gestures

{% hint style="warning" %}
This is advanced behaviour that affects how gestures 'win' in the gesture arena, and does not usually need changing.
{% endhint %}


# Custom CRSs

## Projection

To define a `Proj4Crs` (custom CRS) you have to register a projection of `proj4.Projection`. For that you must import `proj4dart` library as follows:

```dart
import 'package:proj4dart/proj4dart.dart' as proj4;
```

You can create and register your custom projection in multiple ways, but the recommended is to use a **Proj4 definition string** from [epsg.io](https://epsg.io). For example for `EPSG:3413` (*WGS 84 / NSIDC Sea Ice Polar Stereographic North*) you can find it [here](https://epsg.io/3413.proj4). This is how a Proj4 definition string looks like:

```dart
+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84
+
units
=
m
+
no_defs
```

With this **Proj4 definition string** and a **string identifier** register your `proj4.Projection` like this:

```dart

var customProjection = proj4.Projection.add('EPSG:3413',
    '+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
```

For more possible ways to register `proj4.Projection` see [proj4dart documentation](https://github.com/maRci002/proj4dart).

## Coordinate Reference System (CRS)

You can use your previously registered `proj4.Projection` to create a custom CRS of type `Proj4Crs`. You can use the following parameters:

* `<String>` `code` (required): string identifier for the selected CRS, e.g. `EPSG:3413`
* `<proj4.Projection>` `proj4Projection` (required): the `proj4.Projection` object you wish to use
* `<Bounds>` `bounds`: bounds of the CRS in projected coordinates
* `<List<double>>` `resolutions`: an array of zoom factors (projection units per pixel, eg. meters/pixel)
* `<List<double>>` `scales`: scale factors (pixels per projection unit); specify either scales or resolutions, but not both!
* `<List<Point>>` `origins`: tile origin in projected coordinates (for TileLayer). Why is it needed? GeoServer by default can define different origins (top left coordinates) for each zoom levels. In case of origin mismatch the tile will be drawn on the wrong place: the map will jump at such zoom levels. If your origins vary with zoom levels the number of origins must match the number of resolutions. You can get the desired origins from a `GetCapabilities` WMTS call from geoserver e.g. `http://[ip:port]/geoserver/gwc/service/wmts?request=GetCapabilities`. This results an XML, and you have to look up for the `TopLeftCorner`s for each TileMatrix of your TileMatrixSet.<br>
* `<Transformation>` `transformation`: the transformation to use when transforming projected coordinates into pixel coordinates

An example:

```dart

var epsg3413 = proj4.Projection.add('EPSG:3413',
    '+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');

final resolutions = <double>[
  32768,
  16384,
  8192,
  4096,
  2048,
  1024,
  512,
  256,
  128,
];

final epsg3413Bounds = Bounds<double>(
  Point<double>(-4511619.0, -4511336.0),
  Point<double>(4510883.0, 4510996.0),
);

var maxZoom = (resolutions.length - 1).toDouble();

var epsg3413CRS = Proj4Crs.fromFactory(
  code: 'EPSG:3413',
  proj4Projection: epsg3413,
  resolutions: resolutions,
  bounds: epsg3413Bounds,
  origins: [Point(0, 0)],
  scales: null,
  transformation: null,
);
```

## Usage

Proj4Crs has multiple uses:

* Set `FlutterMap`'s default CRS:

  ```dart
    FlutterMap(
      options: MapOptions(
        // Set the default CRS
        crs: epsg3413CRS,
        center: LatLng(65.05166470332148, -19.171744826394896),
        zoom: 3.0,
        maxZoom: maxZoom,
      ),
      layers: [],
    );
  ```
* Set a WMS layer's CRS

  ```dart
    TileLayerOptions(
      opacity: 1.0,
      backgroundColor: Colors.transparent,
      wmsOptions: WMSTileLayerOptions(
        // Set the WMS layer's CRS
        crs: epsg3413CRS,
        transparent: true,
        format: 'image/jpeg',
        baseUrl:
            'https://www.gebco.net/data_and_products/gebco_web_services/north_polar_view_wms/mapserv?',
        layers: ['gebco_north_polar_view'],
      ),
    );
  ```

For complete code (with point transformation from one projection to another) see the page source code. This is how it looks like:


# Layers

To display anything on the map, you'll need to include at least one layer. This is usually a [`TileLayer`](/v7/layers/tile-layer), which displays the map tiles themselves: without it, the map isn't really a very good map!

<div align="center" data-full-width="false"><figure><img src="/files/4TSKf8wAVxm2hyeUMMom" alt="Example FlutterMap widget, containing multiple feature layers, atop a TileLayer" width="563"><figcaption><p>Example <code>FlutterMap</code> widget, containing multiple feature layers, atop a <code>TileLayer</code></p></figcaption></figure></div>

To insert a layer, add it to the `children` property. Other layers (sometimes referred to as 'feature layers', as they are map features) can then be stacked on top, where the last widget in the `children` list is topmost. For example, you might display a [`MarkerLayer`](/v7/layers/marker-layer), or any widget as your own custom layer ([Creating New Layers](/v7/plugins/making-a-plugin/creating-new-layers))!

{% hint style="info" %}
It is possible to add more than one `TileLayer`! Transparency in one layer will reveal the layers underneath.
{% endhint %}

{% hint style="info" %}
To display a widget in a sized and positioned box, similar to [Overlay Image Layer](/v7/layers/overlay-image-layer), try the community maintained [flutter\_map\_polywidget plugin](https://github.com/TimBaumgart/flutter_map_polywidget)!
{% endhint %}

Each layer is isolated from the other layers, and so handles its own independent logic and handling. However, they can access and modify the internal state of the map, as well as respond to changes.

## Mobile vs Static Layers

Most layers are 'mobile', such as the `TileLayer`. These use a `MobileLayerTransformer` widget internally, which enables the layer to properly move and rotate with the map's current camera.

However, some layers are 'static', such as the [`AttributionLayer`](/v7/layers/attribution-layer)s. These aren't designed to move nor rotate with the map, and usually make use of a widget like `Align` and/or `SizedBox.expand` to achieve this.

Both of these layer types are defined in the same `children` list. Most of the time, static layers go atop mobile layers, so should be at the end of the list.

## Layers With Elements

Some layers - such as `PolygonLayer` - take 'elements' - such as `Polygon`s - as an argument, which are then displayed by the layer. They are usually displayed bottom-to-top in the order of the list (like a `Stack`).

{% hint style="info" %}
Since v7, it has not been possible to add elements to layers in an imperative style.

Flutter is a [declarative UI](https://docs.flutter.dev/data-and-backend/state-mgmt/declarative) framework: the UI is a function of the state. It is not necessarily the state's job to change the UI, the state just requests the UI rebuild itself using the new state. Since v7, FM also now mostly follows this convention (although with the exception of the `MapController`, which is a special exception to this rule).

This means that code such as `MarkerLayer.children.add(newElement)` is invalid.

Instead, in this case, a list of the coordinates (and any extra information required to build each `Marker`) should be maintained, then this list used to build a list of `Markers` at build time, for example, by using `List.map` directly in the `MarkerLayer.children` argument.
{% endhint %}

### Hit Testing & Interactivity

Some layers that use elements also support interactivity via hit testing. This is described in more detail on another page:

{% content-ref url="/pages/eErmULMNQoTVxxGRc11H" %}
[Layer Interactivity](/v7/layers/layer-interactivity)
{% endcontent-ref %}


# Programmatic Interaction

There's two ways to interact with the map - that is to control it, as well as receive data from it - and it's current viewport, aka. 'camera'.

## via User Gestures

The first way is through user interaction, where they perform gestures (such as drags/pans), and the map reacts automatically to those gestures to change the camera view of the map.

These are usually restricted by [Options](/v7/usage/options). It is possible to disable all input, either by disabling all gestures, or by wrapping the map with something like `IgnorePointer`.

## via Programmatic Interation

However, the map camera can also be controlled by calling methods on a controller, and its state read by getting values from an exposed camera.

{% hint style="warning" %}
Changing the state of `MapOptions.initial*` will not update the map camera. It may only be updated through a `MapController`.
{% endhint %}

For more information, see:

{% content-ref url="/pages/UW2gppPcXFfE46FRhWT6" %}
[Controllers & Cameras](/v7/usage/programmatic-interaction/controllers-and-cameras)
{% endcontent-ref %}


# Controllers & Cameras

flutter\_map makes use of `InheritedModel` to share 3 'aspects' with its built children:

* `MapController`: use to programatically control the map camera & access some helper functionality - **control camera**
* `MapCamera`: use to read the current state/position of the map camera & access some helper functionality that depends on the camera (such as `latlngToPoint`) - **read camera**

{% hint style="info" %}
`MapOptions` is also an aspect, which reflects the `MapOptions` defined on the `FlutterMap.options` parameter.

However, it is mostly irrelevant, except for when [Creating New Layers](/v7/plugins/making-a-plugin/creating-new-layers).
{% endhint %}

## Accessing Aspects Within Descendants

All 3 aspects can be retrieved from within the context of a `FlutterMap`, which all *built* descendants should have access to. This usually means from within a layer: anywhere where there is at least one 'visible' builder method between the `FlutterMap` and the invocation.

Use the static `of` (or null-safe `maybeOf`) method to access the inherited aspect. For example, to access the `MapCamera`:

```dart
final inheritedCamera = MapCamera.of(context);
```

This will attach the widget to the state of the map, causing it to rebuild whenever the depended-on aspects change. See [/pages/PHCyJci0lnfcS8LgyvNw#id-2.-hooking-into-inherited-state](https://docs.fleaflet.dev/v7/usage/programmatic-interaction/pages/PHCyJci0lnfcS8LgyvNw#id-2.-hooking-into-inherited-state "mention") for more information.

{% hint style="warning" %}
Using this method directly in the `children` list (not inside another widget), and in any `MapOptions` callback, is not possible: there is no\* builder method between the `FlutterMap` and the `children` or callback.

Instead, follow [#accessing-aspects-elsewhere](#accessing-aspects-elsewhere "mention"), or, wrap the necessary layers with a `Builder` widget.\
For example, the code snippet below hides a `TileLayer` when above zoom level 13:

```dart
children: [
    Builder(
        builder: (context) {
            if (MapCamera.of(context).zoom < 13) return SizedBox.shrink();
            return TileLayer();
        },
    ),
],
```

{% endhint %}

## Accessing Aspects Elsewhere

### `MapCamera`

To access the `MapCamera` outside of a `FlutterMap` descendant, first [setup an external `MapController`, as guided below](#mapcontroller).

Then use the `camera` getter on the `MapController` instance.

{% hint style="warning" %}
Avoid using this method to access the camera when `MapCamera.of()` is available.
{% endhint %}

### `MapController`

For more information about correctly setting up an external(ly accessible) `MapController`, see:

{% content-ref url="/pages/CY2EJB6LTkA4x2nqWUKM" %}
[External Custom Controllers](/v7/usage/programmatic-interaction/external-custom-controllers)
{% endcontent-ref %}

### `MapOptions`

{% hint style="info" %}
It is not possible to access the `MapOptions` in this way outside of `FlutterMap` descendants.

This is because it is not changed by `FlutterMap`, and so that would be unnecessary.
{% endhint %}


# External Custom Controllers

For more information about what a `MapController` is, and when it is necessary to set one up in this way, see:

{% content-ref url="/pages/UW2gppPcXFfE46FRhWT6" %}
[Controllers & Cameras](/v7/usage/programmatic-interaction/controllers-and-cameras)
{% endcontent-ref %}

***

## Basic Setup

The `FlutterMap.controller` parameter takes an externally intialised `MapController` instance, and attaches it to the map.

```dart
// Within a widget
final mapController = MapController();

@override
Widget build(BuildContext context) =>
    FlutterMap(
        mapController: mapController,
        // ...
    );
```

{% hint style="success" %}
An externally attached controller will be accurately reflected when depending on the `MapController` aspect.
{% endhint %}

{% hint style="warning" %}
It is not safe to assume that the `MapController` is ready to use as soon as an instance has been initialised, for example within `initState`.

See below for more information.
{% endhint %}

## Usage Before Attachment (eg. Within `initState`)

It is not safe to assume that the `MapController` is ready to use as soon as an instance has been initialised, for example within `initState`.

It must first be attached to the `FlutterMap`, which could take up to multiple frames to occur (similar to the way a `ScrollController` is attached to a scrollable view). It helps to avoid errors by thinking of the controller in this way.

{% hint style="warning" %}
Use of the `MapController` before it has been attached to a `FlutterMap` will result in an error being thrown, usually a `LateInitialisationError`.
{% endhint %}

{% hint style="success" %}
It is usually safe to use a controller from within a callback manually initiated by the user without further complications.
{% endhint %}

For example, it is sometimes necessary to use a controller in the `initState()` method (for example to [attach an event listener](/v7/usage/programmatic-interaction/listen-to-events)). However, because this method executes before the widget has been built, a controller defined here will not be ready for use.&#x20;

Instead, use the `MapOptions.onMapReady` callback. At this point, it is guaranteed that the controller will have been attached. You could also use this method to complete a `Completer` (and `await` its `Future` elsewhere) if you need to use it elsewhere.

```dart
final mapController = MapController();

@override
void initState() {
    // Cannot use `mapController` safely here
}

@override
Widget build(BuildContext context) {
    return FlutterMap(
        mapController: mapController,
        options: MapOptions(
            onMapReady: () {
                mapController.mapEventStream.listen((evt) {}); // for example
                // Any* other `MapController` dependent methods
            },
        ),
    );
}
```

{% hint style="warning" %}
`MapController` methods that change the position of the map should not be used directly (not as a result of another callback) in `onMapReady` - see [issue #1507](https://github.com/fleaflet/flutter_map/issues/1507). This is an unsupported and usually unnecessary usecase.
{% endhint %}

## Usage Within A State System/Model

{% hint style="danger" %}
Don't define/intialise the a `MapController` within a class or widget that doesn't also contain the `FlutterMap`, such as a state model (eg. `ChangeNotifier`), then try to use it by querying the state in the `FlutterMap.controller` parameter.
{% endhint %}

Instead, some extra care should be taken, which may feel a little backwards at first. The state model should be used AFTER the normal setup.

1. Setup the controller as in [#basic-setup](#basic-setup "mention"), where the `MapController` is defined & initialised directly adjacent to the `FlutterMap`
2. In your state model, create a nullable (and initially uninitialised) `MapController` containing field
3. Follow [#usage-before-attachment-eg.-within-initstate](#usage-before-attachment-eg.-within-initstate "mention") to setup an `onMapReady` callback. Then within this callback, set the state model field.

It may then be beneficial to unset the state model field when the controller is disposed: it should be disposed when the `FlutterMap` is disposed, which should occur just before the widget building the `FlutterMap` is disposed. Therefore, you can override the `dispose` method.

## Animation

Whilst animated movements through `MapController`s aren't built-in, the [community maintained plugin `flutter_map_animations`](https://github.com/TesteurManiak/flutter_map_animations) provides this, and much more!


# Listen To Events

{% hint style="info" %}
To cause a widget inside `FlutterMap`'s context to rebuild when an [aspect](/v7/usage/programmatic-interaction/controllers-and-cameras) changes, see [/pages/PHCyJci0lnfcS8LgyvNw#id-2.-hooking-into-inherited-state](https://docs.fleaflet.dev/v7/usage/programmatic-interaction/pages/PHCyJci0lnfcS8LgyvNw#id-2.-hooking-into-inherited-state "mention").
{% endhint %}

When the state of a `MapCamera` changes, because of an update to its position or zoom, for example, a `MapEvent`, which can be handled by you.

## Catching All Events

There's two methods to catch all emitted `MapEvent`s. These methods expose the raw `MapEvent`, and is recommended in cases where multiple events need to be caught, or there's no more specific callback method available in `MapOptions` (see [#catching-specific-events](#catching-specific-events "mention")).

* Listening to a [`MapController`](/v7/usage/programmatic-interaction/controllers-and-cameras)'s `mapEventStream`, which exposes events via a `Stream`
* Specifying a callback method in `MapOptions.onMapEvent`

## Catching Specific Events

If only a couple of events need to be caught, such as just an `onTap` handler, it is possible to avoid handling the raw `Stream` of `MapEvent`s. Instead, `MapOptions` has callbacks available for the following events:

* `onTap`
* `onLongPress`
* `onPositionChanged`
* `onPointerDown`/`onPointerUp`/`onPointerHover`/`onPointerCancel`
* `onMapReady`\
  Primarily used for advanced `MapController` [Controllers & Cameras](/v7/usage/programmatic-interaction/controllers-and-cameras#usage-inside-initstate)

{% hint style="info" %}
The `MapEventTap` event may be emitted (or the `onTap` callback called) 250ms after the actual tap occurred, as this is the acceptable delay between the two taps in a double tap zoom gesture.

If this causes noticeable jank or a bad experience (for example, on desktop platforms), disable [`InteractiveFlag`](/v7/usage/options/interaction-options#flags)`.doubleTapZoom`:

```dart
options: MapOptions(
    interactiveFlags: ~InteractiveFlag.doubleTapZoom,
),
```

This disables the double tap handler, so the `MapEventTap` is emitted 'instantly' on tap.
{% endhint %}


# Tile Layer

{% hint style="warning" %}
You must comply with the appropriate restrictions and terms of service set by your tile server. Always read the ToS before using a tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

*The OpenStreetMap Tile Server (as used below) ToS can be* [*found here*](https://operations.osmfoundation.org/policies/tiles)*. Other servers may have different terms.*
{% endhint %}

The basis of any map is a `TileLayer`, which displays square raster images in a continuous grid, sourced from the Internet or a local file system.

flutter\_map supports [WMS Usage](/v7/layers/tile-layer/wms-usage), but most map tiles are accessed through Slippy Map/CARTO/XYZ URLs, as described here.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/TileLayer-class.html>" %}

```dart
TileLayer(
  urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
  userAgentPackageName: 'dev.fleaflet.flutter_map.example',
  // Plenty of other options available!
),
```

## Recommended Setup

{% hint style="success" %}
Although setting up a basic tile layer couldn't be simpler, it helps to spend a little bit more time fine-tuning it! We recommend covering this list at least, for every tile layer.
{% endhint %}

* [#url-template](#url-template "mention") (required, except when using WMS)\
  Choose a suitable tile server for your app
* [#useragentpackagename](#useragentpackagename "mention")\
  Always set `userAgentPackageName`, even though it is technically optional
* [#retina-mode](#retina-mode "mention")\
  If your tile server supports retina tiles natively, set up the `retinaMode` property
* [Tile Providers](/v7/layers/tile-layer/tile-providers#cancellablenetworktileprovider)\
  Especially on web, consider using this more advanced `TileProvider` to improve performance
* [`maxNativeZoom`](https://pub.dev/documentation/flutter_map/latest/flutter_map/TileLayer/maxNativeZoom.html)\
  Set the maximum zoom level that the tile server supports to prevent flutter\_map from trying to exceed this (especially when not set appropriately in `MapOptions.maxZoom`)

If you need to squeeze out as much performance as possible, or you're noticing the tile loading seems a little slow:

* Make sure the `FlutterMap` is rebuilt as few times as possible
* Construct the `TileProvider` yourself, outside of the `build` method if possible, so it is reconstructed as few times as possible\
  Some tile providers may perform more expensive logic when they are constructed, and if the provider is frequently reconstructed, this can add up.
* If the `TileProvider` supports it (as `NetworkTileProvider` does), construct a single HTTP `Client`/`HttpClient` outside the `build` method and pass it to the tile provider - especially if you're unable to do the tip above\
  Using a single HTTP client allows the underlying socket connection to the tile server to remain open, even when tiles aren't loading. When tiles are loaded again, it's much faster to communicate over an open socket than opening a new one. In some cases, this can take hundreds of milliseconds off tile loading!
* Reduce [`panBuffer`](https://pub.dev/documentation/flutter_map/latest/flutter_map/TileLayer/panBuffer.html) to 0\
  This reduces the number of network requests made, which may make those requests that are made for more important tiles faster.

## Main Parameters

### URL Template

{% hint style="success" %}
This parameter must be specified unless [`wmsOptions`](/v7/layers/tile-layer/wms-usage) is specified.
{% endhint %}

The URL template is a string that contains placeholders, which, when filled in, create a URL/URI to a specific tile.

Specifically, flutter\_map supports the Slippy Map format, sometimes referred to as CARTO or Raster XYZ. Tiles are referred to by their zoom level, and position on the X & Y axis. For more information, read [How Does It Work?](/v7/getting-started/explanation).

These templates are usually documented by your tile server, and will always include the following placeholders:

* `{x}`: x axis coordinate
* `{y}`: y axis coordinate
* `{z}`: zoom level

Sometimes, they also include:

* `{s}`: [#subdomains](#subdomains "mention")
* `{r}`: [#retina-mode](#retina-mode "mention")
* `{d}`: [#tilesize](#tilesize "mention")

Additional placeholders can also be added freely to the template, and are filled in with the specified values in `additionalOptions`. This can be used to easier add switchable styles or access tokens, for example.

#### Subdomains

Some tile servers provide mirrors/redirects of the main tile server on/via subdomains, such as 'a', 'b', 'c'.

These were necessary to bypass browsers' limitations on simultaneous HTTP connections, thus increasing the number of tiles that can load at once.

To use subdomains, add the `{s}` placeholder, and specify the available subdomains in `TileLayer.subdomains`. flutter\_map will then fill the placeholder with one of these values based on internal logic.

{% hint style="warning" %}
Subdomains are now usually [considered redundant](https://github.com/openstreetmap/operations/issues/737) due to the usage of HTTP/2 & HTTP/3 which don't have the same restrictions.

Usage of subdomains will also hinder Flutter's ability to cache tiles, potentially leading to increased tile requests and costs.

If the server supports HTTP/2 or HTTP/3 ([how to check](https://stackoverflow.com/a/71288871/11846040)), avoid using subdomains.
{% endhint %}

#### Retina Mode

Retina mode improves the resolution of map tiles, an effect particularly visible on high density (aka. retina) displays.

Raster map tiles can look especially pixelated on retina displays, so some servers support [high-resolution "@2x" tiles](https://wiki.openstreetmap.org/wiki/High-resolution_tiles), which are tiles at twice the resolution of normal tiles.

Where the display is high density, and the server supports retina tiles - usually indicated by an `{r}` placeholder in the URL template - it is recommended to enable retina mode.

{% hint style="success" %}
Therefore, where `{r}` is available, it is recommended to call the method `RetinaMode.isHighDensity` with the current `BuildContext`, and pass the result to `TileLayer.retinaMode`. This will enable retina mode on retina displays by filling the `{r}` placeholder with "@2x".
{% endhint %}

Note that where tiles are larger than the standard x256px (such as x512px), retina mode can help make them appear very similar to x256px tiles, but still retain the other benefits of larger tiles. In this case, consider fixing `retinaMode` to `true`, depending on your own tests. See [#tilesize](#tilesize "mention") for more information.

{% hint style="warning" %}
It is also possible to emulate retina mode, even when the server does not natively support it. If `retinaMode` is `true`, and no `{r}` placeholder is present, flutter\_map will emulate it by requesting four tiles at a larger zoom level and combining them together in place of one.

Emulating retina mode has multiple negative effects:

* it increases tile requests
* it likely causes text/labels and POI markers embedded in the tiles to become smaller and unreadable
* it decreases the effective maximum zoom by 1

Therefore, carefully consider whether emulating retina mode is appropriate for your application, and disable it if necessary. Always prefer native retina tiles if they are available.
{% endhint %}

#### Fallback URL Template

It's also possible to specify a `fallbackUrl` template, used if fetching a tile from the primary `urlTemplate` fails (which has the same format as this).

{% hint style="warning" %}
Specifying a `fallbackUrl` does have negative effects on performance and efficiency. Avoid specifying `fallbackUrl` unless necessary.

See in-code documentation and [Tile Providers](/v7/layers/tile-layer/tile-providers) for more information.
{% endhint %}

{% hint style="warning" %}
Some `TileProvider`s may not support/provide any functionality for `fallbackUrl` template.
{% endhint %}

### `userAgentPackageName`

{% hint style="success" %}
Although it is programatically optional, always specify the `userAgentPackageName` argument to avoid being blocked by your tile server.
{% endhint %}

This parameter should be passed the application's package name, such as 'com.example.app'. This is important to avoid blocking by tile servers due to high-levels of unidentified traffic. If no value is passed, it defaults to 'unknown'.

This is then formatted into a 'User-Agent' header, and appended to the `TileProvider`'s `headers` map, if it is not already present.

This is ignored on the web, where the 'User-Agent' header cannot be changed due to a limitation of Dart/browsers.

### Tile Providers

{% hint style="success" %}
If a large proportion of your users use the web platform, it is preferable to use `CancellableNetworkTileProvider`, instead of the default `NetworkTileProvider`. It may also be beneficial to use this tile provider on other platforms as well.

See [Tile Providers](/v7/layers/tile-layer/tile-providers#cancellablenetworktileprovider) for more information.
{% endhint %}

Need more control over how the URL template is interpreted and/or tiles are fetched? You'll need to change the `TileProvider`.

{% content-ref url="/pages/x2LzdwedaPxt1VzqBAz4" %}
[Tile Providers](/v7/layers/tile-layer/tile-providers)
{% endcontent-ref %}

### `tileSize`

Some tile servers will use 512x512px tiles instead of 256x256px, such as Mapbox. Using these larger tiles can help reduce tile requests, and when combined with [Retina Mode](#retina-mode), it can give the same resolution.

To use these tiles, set `tileSize` to the actual dimensions of the tiles (otherwise they will appear to small), such as `512`. Also set `zoomOffset` to the result of `-((d/256) - 1)` - ie. `-1` for x512px tiles (otherwise they will appear at the wrong geographical locations).

The `{d}` placeholder/parameter may also be used in the URL to pass through the value of `tileSize`.

### `panBuffer`

To make a more seamless experience, tiles outside the current viewable area can be 'preloaded', with the aim of minimizing the amount of non-tile space a user sees.

`panBuffer` sets the number of surrounding rows and columns around the viewable tiles that should be loaded, and defaults to 1.

{% hint style="warning" %}
Specifying a `panBuffer` too high may result in slower tile requests for all tiles (including those that are visible), and a higher load on the tile sever. The effect is amplified on larger map dimensions/screen sizes.
{% endhint %}

### Tile Update Transformers

{% hint style="info" %}
`TileUpdateTransformer`(`s`) is a power-user feature. Most applications won't require it.
{% endhint %}

A `TileUpdateTransformer` restricts and limits `TileUpdateEvent`s (which are emitted 'by' `MapEvent`s), which cause tiles to update.

For example, a transformer can delay (throttle or debounce) updates through one of the built-in transformers, or pause updates during an animation, or force updates even when a `MapEvent` wasn't emitted.

For more information, see:

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/TileUpdateTransformer.html>" %}


# Tile Providers

The `tileProvider` parameter in `TileLayer` takes a `TileProvider` object specifying a [tile provider](/v7/getting-started/explanation#tile-providers) to use for that layer.

This has a default of `NetworkTileProvider` which gets tiles from the internet through a dedicated image provider.

There's two situations in which you'll need to change the tile provider:

* Sourcing tiles from the filesystem or asset store: [#local-tile-providers](#local-tile-providers "mention")
* Using a [plugin](/v7/plugins/list) that instructs you to do so ([Creating New Tile Providers](/v7/plugins/making-a-plugin/creating-new-tile-providers))

## Network Tile Providers

These tile providers use the `urlTemplate` to get the appropriate tile from the a network, usually the World Wide Web.

The underlying custom `ImageProvider`s will cache tiles in memory, so that they do not require another request to the tile server if they are pruned then re-loaded. This should result in them being loaded quicker, as well as enabling already loaded tiles to appear even without Internet connection (at least in the same session).

{% hint style="warning" %}
Specifying any `fallbackUrl` (even if it is not used) in the `TileLayer` will prevent loaded tiles from being cached in memory.

This is to avoid issues where the `urlTemplate` is flaky (sometimes works, sometimes doesn't), to prevent potentially different tilesets being displayed at the same time.
{% endhint %}

### `NetworkTileProvider`

This is the default tile provider, and does nothing particularly special. It takes two arguments, but you'll usually never need to specify them:

* `httpClient`: `BaseClient`\
  By default, a `RetryClient` backed by a standard `Client` is used
* `headers`: `Map<String, String>`\
  By default, only headers sent by the platform are included with each request, plus an overridden (where possible) 'User-Agent' header based on the [Tile Layer](/v7/layers/tile-layer#useragentpackagename) property

### [`CancellableNetworkTileProvider`](https://github.com/fleaflet/flutter_map_cancellable_tile_provider)

{% hint style="info" %}
This requires the '[flutter\_map\_cancellable\_tile\_provider](https://github.com/fleaflet/flutter_map_cancellable_tile_provider)' plugin to be installed.

This plugin is part of the official 'flutter\_map' organisation, and maintained by the same maintainers.
{% endhint %}

Tiles that are removed/pruned before they are fully loaded do not need to complete (down)loading, and therefore do not need to complete the HTTP interaction. Cancelling these unnecessary tile requests early could:

* Reduce tile loading durations (particularly on the web)
* Reduce users' (cellular) data and cache space consumption
* Reduce costly tile requests to tile servers\*
* Improve performance by reducing CPU and IO work

This provider uses '[dio](https://pub.dev/packages/dio)', which supports aborting unnecessary HTTP requests in-flight, after they have already been sent.

Although HTTP request abortion is supported on all platforms, it is especially useful on the web - and therefore recommended for web apps. This is because the web platform has a limited number of simulatous HTTP requests, and so closing the requests allows new requests to be made for new tiles.\
On other platforms, the other benefits may still occur, but may not be as visible as on the web.

Once HTTP request abortion is [added to Dart's 'native' 'http' package (which already has a PR opened)](https://github.com/dart-lang/http/issues/424), `NetworkTileProvider` will be updated to take advantage of it, replacing and deprecating this provider. This tile provider is currently a separate package and not the default due to the reliance on the additional Dio dependency.

## Local Tile Providers

These tile providers use the `urlTemplate` to get the appropriate tile from the asset store of the application, or from a file on the users device, respectively.

{% hint style="warning" %}
Specifying any `fallbackUrl` (even if it is not used) in the `TileLayer` will reduce the performance of these providers.

It will cause [23% slower asset tile requests](https://github.com/fleaflet/flutter_map/issues/1436#issuecomment-1569663004) with `AssetTileProvider`,  and will cause main thread blocking when requesting tiles from `FileTileProvider`.
{% endhint %}

### `AssetTileProvider`

This tile providers uses the `templateUrl` to get the appropriate tile from the asset store of the application.

{% hint style="info" %}
Asset management in Flutter leaves a lot to be desired! Unfortunately, every single sub-directory (to the level of tiles) must be listed.
{% endhint %}

### `FileTileProvider`

This tile providers uses the `templateUrl` to get the appropriate tile from the a path/directory/file on the user's device - either internal application storage or external storage.

{% hint style="warning" %}
On the web, `FileTileProvider()` will throw an `UnsupportedError` when a tile request is attempted, due to the lack of the web platform's access to the local filesystem.

If you know you are running on the web platform, use a [`NetworkTileProvider`](#network-tile-provider) or a custom tile provider.
{% endhint %}

## Offline Mapping

{% content-ref url="/pages/bpcomOuvTGL5xNNpSAYp" %}
[Offline Mapping](/v7/tile-servers/offline-mapping)
{% endcontent-ref %}


# WMS Usage

flutter\_map supports WMS tile servers through `WMSTileLayerOptions` - `wmsOptions` in `TileLayer`s.

For usage, please refer to the Full API Reference, and the examples in the example app.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/WMSTileLayerOptions-class.html>" %}

{% hint style="success" %}
Omit `urlTemplate` if using WMS tiles. The template is now specified in the `baseUrl` property of `WMSTileLayerOptions`.
{% endhint %}


# Marker Layer

You can add single point features - such as pins, labels, or markers - to maps using `MarkerLayer` and `Marker`s.

{% hint style="success" %}
No more image only markers! [Unlike *other* ](https://github.com/flutter/flutter/issues/24213)😉[^1][ popular mapping libraries](https://github.com/flutter/flutter/issues/24213), we allow usage of any widget as the marker.
{% endhint %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/MarkerLayer-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/Marker-class.html>" %}

<figure><img src="/files/Hx2ndEAmKB4I1lbZNgTj" alt=""><figcaption><p>An example <code>Marker</code>, using <code>FlutterLogo</code> as the child</p></figcaption></figure>

```dart
MarkerLayer(
  markers: [
    Marker(
      point: LatLng(30, 40),
      width: 80,
      height: 80,
      child: FlutterLogo(),
    ),
  ],
),
```

{% hint style="warning" %}
Excessive use of markers may create performance issues.

Consider using a clustering plugin to merge nearby markers together, reducing the work that needs to be done when rendering: [Plugins List](/v7/plugins/list#marker-clustering).
{% endhint %}

## Alignment

The marker widget will be centered over the geographic coordinate by default. However, this can be changed with the `alignment` argument, which aligns the widget relative to the point.

The center of rotation when `rotate` is `true` will be the point.

The default alignment for all `Marker`s within a `MarkerLayer` can be set by changing the same property on the `MarkerLayer`.

## Rotation

It is possible to enable the `Marker` to automatically counter-rotate to the camera's rotation, to ensure it remains facing upwards, via the `rotate` argument.

The default alignment for all `Marker`s within a `MarkerLayer` can be set by changing the same property on the `MarkerLayer`.

{% hint style="info" %}
There is no built-in support to rotate markers to a specific degree. However, this is easy to implement through a rotating widget, such as `Transform.rotate`.
{% endhint %}

## Handling Gestures

There is no built-in support to handle gestures on `Marker`s, such as taps. However, this is easy to implement using a standard `GestureDetector`.

[^1]: [Google Maps \*wink \*wink](https://github.com/flutter/flutter/issues/24213)


# Polygon Layer

You can add areas/shapes to maps by making them out of individual coordinates using `PolygonLayer` and `Polygon`s.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/PolygonLayer-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/Polygon-class.html>" %}

<figure><img src="/files/swqEnSThjwRht8pHS3iB" alt=""><figcaption><p>An example <code>Polygon</code></p></figcaption></figure>

```dart
PolygonLayer(
  polygons: [
    Polygon(
      points: [LatLng(30, 40), LatLng(20, 50), LatLng(25, 45)],
      color: Colors.blue,
      isFilled: true,
    ),
  ],
),
```

## Interactivity

`PolygonLayer`s and `Polygons`s support hit detection and interactivity.

{% content-ref url="/pages/eErmULMNQoTVxxGRc11H" %}
[Layer Interactivity](/v7/layers/layer-interactivity)
{% endcontent-ref %}

## Performance Optimizations

flutter\_map includes many performance optimizations built in, especially as of v7. Some are enabled by default, but may be only 'weakly' applied, and others must be enabled manually. There are also some other actions that can be taken externally to improve performance

The following list is ordered from least 'intrusive'/extreme, to most intrusive. Remember to consider the potential risks of enabling an optimization before doing so.

{% hint style="success" %}
The example application includes a stress test which loads multiple `Polygon`s from a GeoJson file, with a total of 138,000 points.
{% endhint %}

***

<details>

<summary>Culling <em>(enabled by default)</em></summary>

To improve performance, polygons that are entirely offscreen are effectively removed - they are not processed or painted/rendered. This is enabled by default, and may be disabled using the `polygonCulling` parameter, although this is not recommended.

</details>

***

<details>

<summary>Batching <em>(enabled by default, but improvable with effort)</em></summary>

To improve performance, polygons that are similar in appearance, and borders that are similar in appearance, are drawn to the underlying canvas in batches, to reduce the number of draw calls. This cannot be disabled.

To further improve performance, consider defining all `Polygon` `points` in a clockwise order, and place similar appearance `Polygon`s adjacent to each other in the `polygons` list (where elevation does not matter).

</details>

{% hint style="warning" %}
Overlapping colors that are not completely opaque will not recieve the 'darkening'/layering effect - the overlapping area will just be the single colour.&#x20;
{% endhint %}

***

<details>

<summary>Simplification <em>(enabled by default, adjustable)</em></summary>

To improve performance, polygon outlines (`points`) are 'simplified' before the polygons are culled and painted/rendered. The well-known [Ramer–Douglas–Peucker algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm) is used to perform this, and is enabled by default.

To adjust the quality and performance of the simplification, the maximum distance between removable points can be adjusted through the `simplificationTolerance` parameter. Increasing this value (from its default of 0.5) results in a more jagged, less accurate (lower quality) simplification, with improved performance; and vice versa. Many applications use a value in the range 1 - 1.5. To disable simplification, set `simplificationTolerance` to 0.&#x20;

***

Simplification algorithms reduce the number of points in each line by removing unnecessary points that are 'too close' to other points which create tiny line segements invisible to the eye. This reduces the number of draw calls and strain on the raster/render thread. This should have minimal negative visual impact (high quality), but should drastically improve performance.

For this reason, polygons can be more simplified at lower zoom levels (more zoomed out) and less simplified at higher zoom levels (more zoomed in), where the effect of culling on performance improves and trades-off. This is done by scaling the `simplificationTolerance` parameter (see below) automatically internally based on the zoom level.

</details>

{% hint style="warning" %}
On layers with (many) only small polygons (those with few points), disabling simplification may yield better performance.
{% endhint %}

{% hint style="warning" %}
Polygons may overlap after simplification when they did not before, and vice versa.
{% endhint %}

***

<details>

<summary>Performant Rendering, with <code>drawVertices</code> <em>(disabled by default)</em></summary>

Polygons (and similar other features) are usually drawn directly onto a `Canvas`, using built-in methods such as `drawPolygon` and `drawLine`. However, these can be relatively slow, and will slow the raster thread when used at a large scale.

Therefore, to improve performance, it's possible to optionally set the `useAltRendering` flag on the `PolygonLayer`. This will use an alternative, specialised, rendering pathway, which *may* lead to an overall performance improvement, particularly at a large scale.

***

There's two main steps to this alternative rendering algorithm:

1. Cut each `Polygon` into multiple triangles through a process known as [triangulation](https://en.wikipedia.org/wiki/Polygon_triangulation). flutter\_map uses an earcutting algorithm through [dart\_earcut](https://pub.dev/packages/dart_earcut) (a port of an algorithm initially developed at Mapbox intended for super-large scale triangulation).
2. Draw each triangle onto the canvas via the lower-level, faster [`drawVertices`](https://api.flutter.dev/flutter/dart-ui/Canvas/drawVertices.html) method. Borders are then drawn as normal.

</details>

{% hint style="warning" %}
Self-intersecting (complex) `Polygon`s are not supported by the triangulation algorithm, and could cause errors.

The Shamos-Hoey algorithm could be used to automatically detect self-intersections, and set the feature-level flag correspondingly. If doing this, remember that the simplification step (which runs prior to this) could either add or remove a self-intersection.

Holes are supported.
{% endhint %}

{% hint style="warning" %}
This pathway may be slower than the standard pathway, especially when used on a large scale but with simplification disabled, or used on an especially small scale.

It is intended for use when prior profiling indicates more performance is required after other methods are already in use.
{% endhint %}

{% hint style="warning" %}
Rarely, some visible artefacts may be introduced by the triangulation algorithm.
{% endhint %}

***

<details>

<summary>Use No/Thin Borders or Cheaper <code>StrokeCap</code>s/<code>StrokeJoin</code>s <em>(external)</em></summary>

To further improve performance, consider using no border, or a hairline 1px border (remembering to consider the difference between device and logical pixels). Alternatively, consider using `StrokeCap.butt`/`StrokeCap.square` & `StrokeJoin.miter`/`StrokeJoin.bevel`.\
These are much cheaper for the rendering engine (particularly Skia), as it does not have to perform as many calculations.&#x20;

</details>

## Polygon Manipulation

'flutter\_map' doesn't provide any public methods to manipulate polygons, as these would be deemed out of scope.

However, some useful methods can be found in libraries such as 'latlong2' and ['poly\_bool\_dart'](https://github.com/mohammedX6/poly_bool_dart). These can be applied to the input of `Polygon`'s `points` argument, and the map will do it's best to try to render them. However, more complex polygons - such as those with holes - may be painted inaccurately, and may therefore require manual adjustment (of `holePointsList`, for example).


# Polyline Layer

You can add lines to maps by making them out of individual coordinates using `PolylineLayer` and `Polyline`s.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/PolylineLayer-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/Polyline-class.html>" %}

<figure><img src="/files/PGjlcKD2STmgBPGvKJek" alt=""><figcaption><p>An example <code>Polyline</code></p></figcaption></figure>

```dart
PolylineLayer(
  polylines: [
    Polyline(
      points: [LatLng(30, 40), LatLng(20, 50), LatLng(25, 45)],
      color: Colors.blue,
    ),
  ],
),
```

## Pattern

`Polyline`s support a `solid`, `dotted`, and `dashed` style, through a `StrokePattern`passed as an argument to`Polyline.pattern`. These are flexible, and spacing and sizing may be customized.

`dotted` and `dashed` patterns should 'fit' the `Polyline` they are applied to, otherwise the final point in that line may not be visually clear. The fit can be adjusted when defining the pattern through the `PatternFit` enum.

<figure><img src="/files/x41nSAvTRrIa0PxRqonG" alt="" width="375"><figcaption><p>Illustration of the 5 types of <code>PatternFit</code> applied to a <code>dashed</code> <code>Polyline</code><br>From left to right: <code>none</code>, <code>appendDot</code>, <code>extendFinalDash</code>, <code>scaleUp</code> (default), <code>scaleDown</code></p></figcaption></figure>

## Interactivity

`PolylineLayer`s and `Polyline`s support hit detection and interactivity.

{% content-ref url="/pages/eErmULMNQoTVxxGRc11H" %}
[Layer Interactivity](/v7/layers/layer-interactivity)
{% endcontent-ref %}

{% hint style="info" %}
If any polylines are very thin, it is recommended for accessibility reasons to increase the size of the 'hitbox' (the area where a hit is detected on a polyline) to larger than the line itself in order to make it easier to tap/interact with the polyline.

The `minimumHitbox` argument adjusts the minimum size of the hitbox - the size of the hitbox will usually be the entire visual area/thickness of the polyline (and border), but will be no less than this value. It defaults to 10.
{% endhint %}

## Performance Optimizations

{% hint style="success" %}
The example application includes a stress test which generates a `Polyline` with 200,000 points.
{% endhint %}

### Culling

To improve performance, line segments that are entirely offscreen are effectively removed - they are not processed or painted/rendered. This is enabled by default and disabling it is not recommended.

{% hint style="warning" %}
Polylines that are particularly wide (due to their `strokeWidth`/`borderStrokeWidth` may be improperly culled if using the default configuration. This is because culling decisions are made on the 'infinitely thin line' joining the `points`, not the visible line, for performance reasons.

Therefore, the `cullingMargin` parameter is provided, which effectively increases the distance a segement needs to be from the viewport edges before it can be culled. Increase this value from its default if line segements are visibly disappearing unexpectedly.
{% endhint %}

{% hint style="warning" %}
Culling cannot be applied to polylines with a gradient fill, as this would cause inconsistent gradients.

These will be automatically internally excluded from culling: it is not necessary to disable it layer-wide - unless all polylines have gradient fills, in which case that may yield better performance.

Avoid using these if performance is of importance. Instead, try using multiple polylines with coarser color differences.
{% endhint %}

### Simplification

To improve performance, polylines are 'simplified' before being culled and painted/rendered. The well-known [Ramer–Douglas–Peucker algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm) is used to perform this, and is enabled by default.

> Simplification algorithms reduce the number of points in each line by removing unnecessary points that are 'too close' to other points which create tiny line segements invisible to the eye. This reduces the number of draw calls and strain on the raster/render thread. This should have minimal negative visual impact (high quality), but should drastically improve performance.
>
> For this reason, polylines can be more simplified at lower zoom levels (more zoomed out) and less simplified at higher zoom levels (more zoomed in), where the effect of culling on performance improves and trades-off. This is done by scaling the `simplificationTolerance` parameter (see below) automatically internally based on the zoom level.

To adjust the quality and performance of the simplification, the maximum distance between removable points can be adjusted through the `simplificationTolerance` parameter. Increasing this value (from its default of 0.4) results in a more jagged, less accurate (lower quality) simplification, with improved performance; and vice versa. Many applications use a value in the range 0 - 1.

To disable simplification, set `simplificationTolerance` to 0.&#x20;

{% hint style="warning" %}
The simplification step must run before culling, to avoid the polyline appearing to change when interacting with the map (due to the first and last points of the polyline changing, influencing the rest of the simplified points).

Therefore, reducing/disabling simplification will yield better performance on complex polylines that are out of the camera view (and therefore culled without requiring the potentially expensive simplification). However, using simplification will likely improve performance overall - it does this by reducing the load on the raster thread and slightly increasing the load on the UI/build/widget thread.
{% endhint %}

{% hint style="warning" %}
On layers with (many) only 'short' polylines (those with few points), disabling simplification may yield better performance.
{% endhint %}

## Routing/Navigation

Routing is out of scope for 'flutter\_map'. However, if you can get a list of coordinates from a 3rd party, then you can use polylines to show them!

Good open source options that can be self-hosted include [OSRM](http://project-osrm.org/) (which includes a public demo server) and [Valhalla](https://github.com/valhalla/valhalla). You can also use a commercial solution such as Mapbox or Google Maps - these can often give more accurate/preferable results because they can use their traffic data when routing.

### Converting Formats

You may have a polyline with 'Google Polyline Encoding' (which is a lossy compression algorithm to convert coordinates into a string and back). These are often returned from routing engines, for example. In this case, you'll need to decode the polyline to the correct format first, before you can use it in a `Polyline`'s `points` argument.

One way to accomplish this is to use another Flutter library called ['google\_polyline\_algorithm'](https://pub.dev/packages/google_polyline_algorithm), together with a custom method. You can use the code snippet below, which can just be pasted into a file and imported whenever needed:

{% code title="unpack\_polyline.dart" %}

```dart
import 'package:latlong2/latlong.dart';
export 'package:google_polyline_algorithm/google_polyline_algorithm.dart'
    show decodePolyline;

extension PolylineExt on List<List<num>> {
  List<LatLng> unpackPolyline() =>
      map((p) => LatLng(p[0].toDouble(), p[1].toDouble())).toList();
}
```

{% endcode %}

You can then use the package and the above snippet by doing:

```dart
import 'unpack_polyline.dart';

decodePolyline('<encoded-polyline>').unpackPolyline(); // Returns `List<LatLng>` for a map polyline
```


# Circle Layer

You can add circle areas to maps by making them out of a center coordinate and radius using `CircleLayer` and `CircleMarker`s.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/CircleLayer-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/CircleMarker-class.html>" %}

<figure><img src="/files/3qRSt3ShwF2m6AZhvS4o" alt=""><figcaption><p>An example <code>CircleMarker</code></p></figcaption></figure>

```dart
CircleLayer(
  circles: [
    CircleMarker(
      point: LatLng(51.50739215592943, -0.127709825533512),
      radius: 10000,
      useRadiusInMeter: true,
    ),
  ],
),
```

{% hint style="warning" %}
Excessive use of circles may create performance issues.
{% endhint %}

## Interactivity

`CircleLayer`s and `CircleMarker`s support hit detection and interactivity.

{% content-ref url="/pages/eErmULMNQoTVxxGRc11H" %}
[Layer Interactivity](/v7/layers/layer-interactivity)
{% endcontent-ref %}


# Overlay Image Layer

You can overlay images on the map (for example, town or floor plans) using `OverlayImageLayer` and `OverlayImage`s or `RotatedOverlayImage`s.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/OverlayImageLayer-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/BaseOverlayImage-class.html>" %}

<figure><img src="/files/1efO215vqz7Ug1w8BCO3" alt=""><figcaption><p>Example <code>RotatedOverlayImage</code></p></figcaption></figure>

```dart
OverlayImageLayer(
  overlayImages: [
    OverlayImage( // Unrotated
      bounds: LatLngBounds(
        LatLng(45.3367881884556, 14.159452282322459),
        LatLng(45.264129635422826, 14.252585831779033),
      ),
      imageProvider: NetworkImage(),
    ),
  ],
),
```

{% hint style="warning" %}
There have been issues in the past where these images failed to appear properly, sometimes not showing up at all, sometimes showing up malformed or corrupted.

If this issue occurs to you, and you're using Impeller, try disabling Impeller at launch/build time to see if the issue rectifies itself. If it does, this is an Impeller issue, and should be reported to the Flutter team.
{% endhint %}

## Rotation & Skewing

`RotatedOverlayImage` supports rotation and parallelogram skewing, by accepting 3 points instead of 2.

To calculate a rotation without skewing, given a center and a 3rd corner, see <https://stackoverflow.com/a/78064659/11846040>.


# Attribution Layer

Before publishing your app to users, you should credit any sources you use, according to their Terms of Service.

There are two built in methods to provide attribution, `RichAttributionWidget` and `SimpleAttributionWidget`, but you can also build your own using a simple `Align` widget.

{% hint style="warning" %}
You must comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

*The OpenStreetMap Tile Server (as used above) ToS can be* [*found here*](https://operations.osmfoundation.org/policies/tiles)*. Other servers may have different terms.*
{% endhint %}

{% hint style="success" %}
Please consider crediting flutter\_map. It helps us to gain more awareness, which helps make this project better for everyone!
{% endhint %}

## `RichAttributionWidget`

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/RichAttributionWidget-class.html>" %}

An animated, interactive attribution layer that supports both logos/images (displayed permanently) and text (displayed in a popup controlled by an icon button adjacent to the logos).

It is heavily customizable (in both animation and contents), and designed to easily meet the needs of most ToSs out of the box.

<div align="center"><figure><img src="/files/WtJ0CmBqZ0RXTgX3te72" alt="An icon and a button displayed over a map, in the bottom right corner"><figcaption><p>Closed <code>RichAttributionWidget</code></p></figcaption></figure> <figure><img src="/files/vtAA1uBs2HJBKlRZfShE" alt="A white box with attribution text displayed over a map"><figcaption><p>Opened <code>RichAttributionWidget</code>, as in the example app</p></figcaption></figure></div>

```dart
children: [
  RichAttributionWidget(
    animationConfig: const ScaleRAWA(), // Or `FadeRAWA` as is default
    attributions: [
      TextSourceAttribution(
        'OpenStreetMap contributors',
        onTap: () => launchUrl(Uri.parse('https://openstreetmap.org/copyright')),
      ),
    ],
  ),
],
```

For more information about configuration and all the many options this supports, see the in-code API documentation.

## `SimpleAttributionWidget`

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/SimpleAttributionWidget-class.html>" %}

We also provide a more 'classic' styled box, similar to those found on many web maps. These are less customizable, but might be preferred over `RichAttributionWidget` for maps with limited interactivity.

<figure><img src="/files/6BnEAEMCqKasgrKKXMyB" alt=""><figcaption><p><code>SimpleAttributionWidget</code>, as in the example app</p></figcaption></figure>

```dart
children: [
  SimpleAttributionWidget(
    source: Text('OpenStreetMap contributors'),
  ),
],
```


# Layer Interactivity

{% hint style="info" %}
Layer interactivity is different to map interactivity. See [Interaction Options](/v7/usage/options/interaction-options) to control map interactivity.
{% endhint %}

{% hint style="info" %}
For information about how hit testing behaves in flutter\_map, see [Hit Testing Behaviour](/v7/layers/layer-interactivity/hit-testing-behaviour).

It is important to note that hit testing != interactivity, and hit testing is always executed on interactable layers by default.
{% endhint %}

The following layers are interactable - they have specialised `hitTest`ers and support external hit detection:

* [Polyline Layer](/v7/layers/polyline-layer)
* [Polygon Layer](/v7/layers/polygon-layer)
* [Circle Layer](/v7/layers/circle-layer)

These all follow roughly the same pattern to setup hit detection/interactivity, and there's three or four easy steps to setting it up.&#x20;

## 1. Attach A Hit Notifier

{% hint style="info" %}
Direct callbacks, such as `onTap,`aren't provided on layers or individual elements, to maximize flexibility.
{% endhint %}

Pass a `LayerHitNotifier` to the `hitNotifier` parameter of the layer. The `LayerHitNotifier` should be created as a `ValueNotifier` defaulting to `null`, but strongly typed to `LayerHitNotifier`.

This notifier will be notified whenever a hit test occurs on the layer, with a  `LayerHitResult` when an element (such as a `Polyline` or `Polygon`) within the layer is hit, and with `null` when an element is not hit (but the layer is).

<pre class="language-dart"><code class="lang-dart">final LayerHitNotifier hitNotifier = ValueNotifier(null);

// Inside the map build...
PolylineLayer( // Or any other supported layer
<strong>  hitNotifier: hitNotifier,
</strong>  polylines: [], // Or any other supported elements
);
</code></pre>

It is possible to listen to the notifier directly with `addListener`, if you want to handle all hit events (including, for example, hover events).\
However, most use cases just need to handle particular gestures (such as taps). This can be done with a wrapper widget to 'filter' the events appropriately: [#id-3.-gesture-detection](#id-3.-gesture-detection "mention").

## 2. Add `hitValue` To Elements

To identify which particular element was hit (which will be useful when handling the hit events in later steps), supported elements have a `hitValue` property.

This can be set to any object, but if one layer contains all the same type, type casting can be avoided (if the type is also specified in the `LayerHitNotifier`'s type argument).

{% hint style="warning" %}
The equality of the element depends on the equality of the `hitValue`.

Therefore, any object passed to the `hitValue` should have a valid and useful equality method.\
Objects such as [records](https://dart.dev/language/records) do this behind the scenes, and can be a good choice to store small amounts of uncomplicated data alongside the element.
{% endhint %}

## 3. Gesture Detection

To only handle certain hits based on the type of gesture the user performed (such as a tap), wrap the layer with a gesture/hit responsive widget, such as `GestureDetector` or `MouseRegion`.&#x20;

These widgets are smart enough to delegate whether they detect a hit (and therefore whether they can detect a gesture) to the child - although `HitTestBehavior.deferToChild` may be required for some widgets to enable this functionality.

This means the layer can report whether it had any form of hit, and the handler widget can detect whether the gesture performed on it actually triggered a hit on the layer below.

```dart
// Inside the map build...
MouseRegion(
  hitTestBehavior: HitTestBehavior.deferToChild,
  cursor: SystemMouseCursors.click, // Use a special cursor to indicate interactivity
  child: GestureDetector(
    onTap: () {
      // Handle the hit, which in this case is a tap
      // For example, see the example in Hit Handling (below)
    },
    // And/or any other gesture callback
    child: PolylineLayer(
      hitNotifier: hitNotifier,
      // ...
    ),
  ),
),
```

## 4. Hit Handling

Once a `LayerHitResult` object is obtained, through the hit notifier, you can retrieve:

* `hitValues`: the `hitValue`s of all elements that were hit, ordered by their corresponding element, first-to-last, visually top-to-bottom
* `coordinate`: the geographic coordinate of the hit location (which may not lie on any element)
* `point`: the screen point of the hit location

{% hint style="success" %}
If all the `hitValue`s in a layer are of the same type, and the created hit notifier specifies that type in the type argument, typing is preserved all the way to retrieval.
{% endhint %}

Because the `HitNotifier` is a special type of `ValueNotifier`, it can be both listened to (like a `Stream`), and its value instantly retrieved (like a normal variable).\
Therefore, there are two ways to retrieve a `LayerHitResult` (or `null`) from the notifier:

* Using `.value` to instantly retrieve the value\
  This is usually done within a gesture handler, such as `GestureDetector.onTap`, as demonstrated below.
* Adding a listener (`.addListener`) to retrieve all hit results\
  This is useful where you want to apply some custom/advanced filtering to the values, and is not a typical usecase.

<pre class="language-dart" data-overflow="wrap"><code class="lang-dart">// Inside a gesture detector/handler

<strong>final LayerHitResult? hitResult = hitNotifier.value;
</strong>if (hitResult == null) return;

// If running frequently (such as on a hover handler), and heavy work or state changes are performed here, store each result so it can be compared to the newest result, then avoid work if they are equal 

for (final hitValue in hitResult.hitValues) {}
</code></pre>


# Hit Testing Behaviour

The behaviour of hit testing can be confusing at first. These rules define how hit testing usually behaves:

1. Gesture callbacks in `MapOptions` are always invoked, no matter what is within the layers or the result of `hitTest`s in those layers, with the exception of custom defined hit test behaviours (not those layers that support interactivity, see [Layer Interactivity](/v7/layers/layer-interactivity)), such as applying `GestureDetector`s around `Marker.child`ren

{% hint style="warning" %}
`GestureDetector`s absorb hit tests, and so corresponding callbacks in `MapOptions` will not be invoked if they are defined/invoked in the `GestureDetector`.

Workarounds to resolve this are discussed below.
{% endhint %}

2. Hit testing is always\* performed on the interactable layers (see [Layer Interactivity](/v7/layers/layer-interactivity)) even if they have not been set-up for interactivity: hit testing != interactivity
3. Non-interactable layers (such as [Overlay Image Layer](/v7/layers/overlay-image-layer)) have no defined `hitTest`, and behaviour is situation dependent
4. A successful hit test (`true`) from an interactable layer will prevent hit testing on layers below it in the `children` stack

To change this behviour, make use of these three widgets, wrapping them around layers when and as necessary:

* [`IgnorePointer`](https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html)
* [`AbsorbPointer`](https://api.flutter.dev/flutter/widgets/AbsorbPointer-class.html)
* `TranslucentPointer`: a general purpose 'widget' included with flutter\_map that allows the child to hit test as normal, but also allows widgets beneath it to hit test as normal, both seperately

***

For example, a marker with a `GestureDetector` child that detects taps beneath a `Polyline` will not detect a tap, no matter if the `PolylineLayer` has a defined `hitNotifier` or the `Polyline` has a defined `hitValue`. A defined `onTap` callback in `MapOptions` would be called however. If the `Marker` were no longer obscured by the `Polyline`, it's `onTap` callback would be fired instead of the one defined in `MapOptions`.

However, this behaviour could be changed by wrapping the `PolylineLayer` with a `TranslucentPointer`. This would allow interacitivity to function as normal, but also allow the `Marker` beneath to have it's `onTap` callback fired. Further wrapping another `TransclucentPointer` around the `MarkerLayer` would allow all 3 detections to function.


# Using Google Maps

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Google Maps

Google Maps' home page: <https://developers.google.com/maps>\
Google Maps' pricing page: <https://mapsplatform.google.com/pricing/>\
Google Maps' documentation page: <https://developers.google.com/maps/documentation/tile/2d-tiles-overview>
{% endhint %}

{% hint style="success" %}
Raster 2D tiles from Google Maps is a relatively new offering, which makes Google Maps directly compatible with flutter\_map, whilst abiding by the Google Maps' ToS (which the previous method did not).
{% endhint %}

{% hint style="warning" %}
Tile providers that also provide their own SDK solution to display tiles will often charge a higher price to use 3rd party libraries like flutter\_map to display their tiles. Just another reason to switch to an alternative provider.
{% endhint %}

To display map tiles from Google Maps, a little more effort is needed, as they require a complex session token system.

Therefore, we haven't yet constructed a full guide, so please read the Google Developers documentation for more info:

{% embed url="<https://developers.google.com/maps/documentation/tile/2d-tiles-overview>" %}


# Using Mapbox

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Mapbox.

Mapbox's Maps home page: [mapbox.com/maps](https://docs.fleaflet.dev/v7/tile-servers/www.mapbox.com/maps)\
Mapbox's Maps pricing page: [mapbox.com/pricing#maps](https://docs.fleaflet.dev/v7/tile-servers/www.mapbox.com/pricing#maps)\
Mapbox's Maps documentation: [docs.mapbox.com/api/maps/static-tiles](https://docs.mapbox.com/api/maps/static-tiles)
{% endhint %}

{% hint style="warning" %}
Tile providers that also provide their own SDK solution to display tiles will often charge a higher price to use 3rd party libraries like flutter\_map to display their tiles. Just another reason to switch to an alternative provider.
{% endhint %}

To display their map tiles, Mapbox usually provides a 'Style URL' for map styles. However, to integrate with 3rd-party APIs, they also provide a 'CARTO Integration URL', and tiles requested through this endpoint consume the 'Static Tiles API' quota. This URL needs no extra configuration to integrate with flutter\_map.

## Integration

### Custom Styles

Mapbox supports creating and using custom styled maps through Studio.

{% hint style="warning" %}
Before attempting to use your style, ensure it actually has layers, and not just a Basemap, which will not appear in the tiles. The image below shows a style which will not work. If you only see blank tiles, but no errors, this is likely the cause.

To create a new style based on the Standard style, choose a template when creating the new style.

<img src="/files/g91QyG01z4FN826JPqvT" alt="" data-size="original"> &#x20;
{% endhint %}

1. Create a custom style using the editor
2. Click "Share...", or the share icon
3. Choose between Draft or Production
4. Scroll to the bottom of the dialog, and choose Third Party
5. Select "CARTO" from the dropdown menu
6. Click the copy button to copy the template URL, then paste it into your `TileLayer`

<figure><img src="/files/UWza3L1nDeO25zgCP6nS" alt="" width="563"><figcaption></figcaption></figure>

#### Configuring [Retina Mode](/v7/layers/tile-layer#retina-mode)

The URL includes an '@2x' string, which forces usage of high-definition tiles on all displays, without extra setup.

Should you need to let flutter\_map interfere, and only use retina tiles on retina/high-density displays, replace it with the '{r}' placeholder, then see [Tile Layer](/v7/layers/tile-layer#retina-mode) for more information.

#### Other configurations

The maximum zoom level that Mapbox supports is 22, so it is recommended to set `maxNativeZoom` or `maxZoom` as such.

#### Adding Attribution

{% hint style="warning" %}
Attribution is required, and quite extensive compared to some alternatives: see [docs.mapbox.com/help/getting-started/attribution](https://docs.mapbox.com/help/getting-started/attribution/).

Consider using the [Attribution Layer](/v7/layers/attribution-layer#richattributionwidget), which meets the requirements by supporting both logo and text attribution.
{% endhint %}

### Prebuilt Styles

Mapbox offers a variety of ready-made map styles that don't require customization. An example URL can be found in [the example here](https://docs.mapbox.com/api/maps/static-tiles/#example-request-retrieve-raster-tiles-from-styles).

This URL should be used as above, although you may need to insert the placeholders manually.


# Using Thunderforest

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Thunderforest (or Gravitystorm Limited).

Thunderstorm's home page: [thunderforest.com](https://www.thunderforest.com)\
Thunderstorm's pricing page: [thunderforest.com/pricing](https://www.thunderforest.com/pricing)\
Thunderstorm's documentation page: [thunderforest.com/docs/map-tiles-api](https://www.thunderforest.com/docs/map-tiles-api)
{% endhint %}

Thunderforest is a popular tiered-payment (with free tier) tile provider solution, especially for generic mapping applications. Note that using 'flutter\_map' uses up your 'Map Tiles API' requests.

{% embed url="<https://www.thunderforest.com/tutorials/flutter/>" %}
Visit our documentation over on Thunderforest's site (may be outdated)
{% endembed %}


# Using Tracestrack

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Tracestrack.

Tracestrack's home page: [tracestrack.com](https://tracestrack.com/)
{% endhint %}

Tracestrack has a wide variety of raster styles, including topographic and standard bases, in multiple different color schemes (light & dark). It also supports vector tiles, which makes it super flexible - use raster tiles now, and be ready to switch to vector tiles!

1. Use <https://console.tracestrack.com/explorer> to find your ideal style
2. Create an account
3. Copy the URL template from the result box at the bottom and paste it into your `TileLayer`


# Using Stadia Maps

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Stadia Maps.

Stadia Maps' home page: [stadiamaps.com](https://stadiamaps.com/)\
Stadia Maps' pricing page: [stadiamaps.com/pricing](https://stadiamaps.com/pricing/)\
Stadia Maps' documentation page: [docs.stadiamaps.com](https://docs.stadiamaps.com/)
{% endhint %}

To display their map tiles, Stadia Maps usually provides a 'Static Maps Base URL' for map styles. However, to integrate with 3rd-party APIs, they also provide a 'Raster XYZ PNGs URL' , and tiles requested through this endpoint consume 'Styled Raster Map Tiles' credits. This URL needs no extra configuration to integrate with flutter\_map.

Retina tiles (high-DPI) tiles are available. Use the URLs containing '@2x' instead of '{r}'. The maximum zoom level that Stadia Maps supports is 20, so it is recommended to set `maxNativeZoom` or `maxZoom` as such.

{% hint style="warning" %}
Attribution is required, see [docs.stadiamaps.com/#legal-details-required-attribution](https://docs.stadiamaps.com/#legal-details-required-attribution).

Consider using the [Attribution Layer](/v7/layers/attribution-layer#richattributionwidget) or [Attribution Layer](/v7/layers/attribution-layer#simpleattributionwidget)s, which meet the requirements.
{% endhint %}

## Styles

Stadia Maps offers a variety of ready-made map styles that don't require customization. URLs are found with the style: see the available [map styles](https://docs.stadiamaps.com/themes). The URL should be used as above.

## Vector Usage

Stadia Maps' also provides vector tiles. For more information about using vector tiles, please see [Raster vs Vector Tiles](/v7/getting-started/explanation/raster-vs-vector-tiles#using-vector-tiles).&#x20;

However, please note that this method of integration is still experimental. Many of the Stadia Maps styles utilize advanced features of the Mapbox GL JSON style language which are not yet well-supported.


# Using Lima Labs

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Lima Labs.

Lima Labs' home page: [maps.lima-labs.com](https://maps.lima-labs.com/)\
Lima Labs' pricing page: [maps.lima-labs.com/index.php/pricing](https://maps.lima-labs.com/index.php/pricing/)
{% endhint %}

If you're looking for *extremely* low cost & often free (even for commercial usage), but still reliable, and low frills service, Lima Labs provides high quality raster tiles with one style based off the standard CARTO style.

1. Sign up for an account - Lima Labs operates manual verification, so you may have to wait a few days
2. Use the common URL ([https://cdn.lima-labs.com/0/0/0.png?api=](https://cdn.lima-labs.com/0/0/0.png?api=bear)\_\_\_) with your API key


# Using Bing Maps

{% hint style="info" %}
'flutter\_map' is in no way associated or related with Bing Maps.

Bing Maps' home page: [microsoft.com/maps](https://www.microsoft.com/maps/)
{% endhint %}

To display map tiles from Bing Maps, a little more effort is needed, as they use a RESTful API with quadkeys, rather than the standard slippy map system.

Luckily, we've constructed all the code you should need below! Feel free to copy and paste into your projects.

{% hint style="info" %}
Thanks to [Luka Glušica](https://github.com/luka-glusica) for discovering the [basic initial implementation](https://github.com/fleaflet/flutter_map/issues/1197#issuecomment-1478763824).
{% endhint %}

{% hint style="warning" %}
Attribution is not demonstrated here, but may be required. Ensure you comply with Bing Maps' ToS.
{% endhint %}

{% code title="bing\_maps.dart" overflow="wrap" lineNumbers="true" %}

```dart
// All compatible imagery sets
enum BingMapsImagerySet {
  road('RoadOnDemand', zoomBounds: (min: 0, max: 21)),
  aerial('Aerial', zoomBounds: (min: 0, max: 20)),
  aerialLabels('AerialWithLabelsOnDemand', zoomBounds: (min: 0, max: 20)),
  canvasDark('CanvasDark', zoomBounds: (min: 0, max: 21)),
  canvasLight('CanvasLight', zoomBounds: (min: 0, max: 21)),
  canvasGray('CanvasGray', zoomBounds: (min: 0, max: 21)),
  ordnanceSurvey('OrdnanceSurvey', zoomBounds: (min: 12, max: 17));

  final String urlValue;
  final ({int min, int max}) zoomBounds;

  const BingMapsImagerySet(this.urlValue, {required this.zoomBounds});
}

// Custom tile provider that contains the quadkeys logic
// Note that you can also extend from the CancellableNetworkTileProvider
class BingMapsTileProvider extends NetworkTileProvider {
  BingMapsTileProvider({super.headers});

  String _getQuadKey(int x, int y, int z) {
    final quadKey = StringBuffer();
    for (int i = z; i > 0; i--) {
      int digit = 0;
      final int mask = 1 << (i - 1);
      if ((x & mask) != 0) digit++;
      if ((y & mask) != 0) digit += 2;
      quadKey.write(digit);
    }
    return quadKey.toString();
  }

  @override
  Map<String, String> generateReplacementMap(
    String urlTemplate,
    TileCoordinates coordinates,
    TileLayer options,
  ) =>
      super.generateReplacementMap(urlTemplate, coordinates, options)
        ..addAll(
          {
            'culture': 'en-GB', // Or your culture value of choice
            'subdomain': options.subdomains[
                (coordinates.x + coordinates.y) % options.subdomains.length],
            'quadkey': _getQuadKey(coordinates.x, coordinates.y, coordinates.z),
          },
        );
}

// Custom `TileLayer` wrapper that can be inserted into a `FlutterMap`
class BingMapsTileLayer extends StatelessWidget {
  const BingMapsTileLayer({
    super.key,
    required this.apiKey,
    required this.imagerySet,
  });

  final String apiKey;
  final BingMapsImagerySet imagerySet;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: http.get(
        Uri.parse(
          'http://dev.virtualearth.net/REST/V1/Imagery/Metadata/${imagerySet.urlValue}?output=json&include=ImageryProviders&key=$apiKey',
        ),
      ),
      builder: (context, response) {
        if (response.data == null) return const Placeholder();

        return TileLayer(
          urlTemplate: (((((jsonDecode(response.data!.body)
                          as Map<String, dynamic>)['resourceSets']
                      as List<dynamic>)[0] as Map<String, dynamic>)['resources']
                  as List<dynamic>)[0] as Map<String, dynamic>)['imageUrl']
              as String,
          tileProvider: BingMapsTileProvider(),
          subdomains: const ['t0', 't1', 't2', 't3'],
          minNativeZoom: imagerySet.zoomBounds.min,
          maxNativeZoom: imagerySet.zoomBounds.max,
        );
      },
    );
  }
}
```

{% endcode %}


# Offline Mapping

Using maps without an Internet connection is common requirement. Luckily, there are a few options available to you to implement offline mapping in your app.

* [Caching](#caching)\
  Automatically store tiles as the user loads them through interacting with the map, usually on a temporary basis
* [Bulk downloading](#bulk-downloading)\
  Download an entire area/region of tiles in one shot, ready for a known no-Internet situation
* [Bundling](#bundled-map-tiles)\
  Provide a set of pre-determined tiles to all users through app assets or the filesystem

## Caching

{% hint style="info" %}
flutter\_map only provides caching in-memory. All cached tiles will be cleared after the app session is ended.

You must comply with the appropriate restrictions and terms of service set by your tile server. Always read the ToS before using a tile server. Failure to do so may lead to any punishment, at the tile server's discretion. Some tile servers may require longer-term caching to be implemented.
{% endhint %}

Caching is used usually to improve user experience by reducing network waiting times, not necessarily to prepare for no-Internet situations. Caching can be more temporary (eg. in-memory/session-only, where the cache is cleared after the app is closed), or more long-term (eg. app cache, where the OS takes responsibility for clearing the app cache when necessary/when requested).

There's 3 methods that basic caching can be implemented in your app, two of which rely on community maintained plugins:

1. [flutter\_map\_cache](https://github.com/josxha/flutter_map_cache) (lightweight and MIT licensed)
2. [flutter\_map\_tile\_caching](https://github.com/JaffaKetchup/flutter_map_tile_caching) (also includes [#bulk-downloading](#bulk-downloading "mention"), but GPL licensed)
3. Custom implementation, via a [custom `TileProvider`](/v7/plugins/making-a-plugin/creating-new-tile-providers) and `ImageProvider` (either custom or via a package such as [cached\_network\_image](https://pub.dev/packages/cached_network_image))

## Bulk Downloading

{% hint style="warning" %}
You must comply with the appropriate restrictions and terms of service set by your tile server. Always read the ToS before using a tile server. Failure to do so may lead to any punishment, at the tile server's discretion. Many tile servers will forbid or restrict bulk downloading, as it places additional strain on their servers.
{% endhint %}

Bulk downloading is used to prepare for known no-Internet situations by downloading map tiles, then serving these from local storage.

Bulk downloading is more complex than [#caching](#caching "mention"), especially for regions that are a non-rectangular shape. Implementing this can be very time consuming and prone to issues.

The [community maintained plugin 'flutter\_map\_tile\_caching'](https://github.com/JaffaKetchup/flutter_map_tile_caching) includes advanced bulk downloading functionality, of multiple different region shapes, and other functionality. It is however GPL licensed. To help choose whether FMTC or DIY is more appropriate for your use case, please see:

{% embed url="<https://fmtc.jaffaketchup.dev/is-fmtc-right-for-me>" %}

## Bundled Map Tiles

If you have a set of custom raster tiles that you need to provide to all your users, you may want to consider bundling them together, to make a them easier to deploy to your users. Bundles can be provided in two formats.

### Contained

Container formats, such as the traditional MBTiles, or the more recent PMTiles, store tiles usually in a database or binary internal format.

These require a special parser to read on demand, usually provided as a `TileProvider` by a plugin. The following community-maintained plugins are available to read these formats:

* [MBTiles](https://wiki.openstreetmap.org/wiki/MBTiles): [flutter\_map\_mbtiles](https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_mbtiles) ([vector\_map\_tiles\_mbtiles ](https://github.com/josxha/flutter_map_plugins/tree/main/vector_map_tiles_mbtiles)when using vector tiles)
* [PMTiles](https://github.com/protomaps/PMTiles): [flutter\_map\_pmtiles](https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_pmtiles) ([vector\_map\_tiles\_pmtiles](https://github.com/josxha/flutter_map_plugins/tree/main/vector_map_tiles_pmtiles) when using vector tiles, also works in online contexts)

### Uncontained

When uncontained, tiles are usually in a tree structure formed by directories, usually 'zoom/x/y.png'. These don't require special parsing, and can be provided directly to the `TileLayer` using one of the built-in local `TileProvider`s.

#### `AssetTileProvider`

You can ship an entire tile tree as part of your application bundle, and register it as assets in your app's pubspec.yaml.

This means that they will be downloaded together with your application, keeping setup simple, but at the expense of a larger application bundle size.

{% hint style="warning" %}
If using `AssetTileProvider`, every sub-directory of the tree must be listed seperately. See the example application's 'pubspec.yaml' for an example.
{% endhint %}

#### `FileTileProvider`

This allows for more flexibility: you could store a tile tree on a remote server, then download the entire tree later to the device's filesystem, perhaps after intial setup, or just an area that the user has selected.

This means that the setup may be more complicated for users, and it introduces a potentially long-running blocking action, but the application's bundle size will be much smaller.


# Other Options

## Other Servers

There are plenty of other tile servers you can choose from, free or paid. Most provide a static tile service/API, usually called Static Tiles or just Tile Requests (if no vector tiles are supported).

If you're responsible for a tile server, and want to have your tile server and setup instructions listed in this documentation, please get in touch!

A good catalogue of servers (usually called Providers elsewhere) can be found at the websites below:

{% embed url="<https://wiki.openstreetmap.org/wiki/Raster_tile_providers>" %}

{% embed url="<https://switch2osm.org/providers/>" %}

## Serving Your Own Tiles

Switch2OSM also provides detailed instructions on how to serve your own tiles: this can be surprisingly economical and enjoyable if you don't mind a few hours in a Linux console.

However, this will require a very high-spec computer, especially for larger areas, and hosting this might be more complicated than it's worth. It's very difficult to fully understand the technologies involved.

{% embed url="<https://switch2osm.org/serving-tiles/>" %}


# Plugins List

There are many independently maintained 'plugins' created by the 'flutter\_map' community that give extra, prebuilt functionality, saving you even more time and potentially money.

Some pages in this documentation provide direct links to these plugins to make it easier for you to find a suitable plugin.

However, if you're just browsing, a full list is provided below (in no particular order), containing many of the available plugins. You can click on any of the tiles to visit its GitHub repo or pub.dev package.

{% hint style="danger" %}
Although these plugins have been checked by 'flutter\_map' maintainers, we do not accept responsibility for any issues or threats posed by independently maintained plugins.

Use plugins at your own risk.
{% endhint %}

{% hint style="warning" %}
There is no guarantee that any of these plugins will support the latest version of flutter\_map. Please remain patient with the plugin authors/owners.
{% endhint %}

{% hint style="info" %}
Many plugins provide multiple methods to achieve similar goals. It is recommended to read the documentation of each potential plugin before using it in your project, as they might have slightly different feature sets or stability.
{% endhint %}

## Tools

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_animations (TesteurManiak)</strong></td><td>Replacement <code>MapController</code> which provides animated movement alternatives</td><td><a href="https://github.com/TesteurManiak/flutter_map_animations">https://github.com/TesteurManiak/flutter_map_animations</a></td></tr><tr><td><strong>flutter_map_geojson (jozes)</strong></td><td>Parse GeoJson data and transform into map features</td><td><a href="https://github.com/jozes/flutter_map_geojson">https://github.com/jozes/flutter_map_geojson</a></td></tr><tr><td><a data-footnote-ref href="#user-content-fn-1"><em>BETA</em></a> <strong>flutter_osrm (JaffaKetchup)</strong></td><td>Query an OSRM-based server to provide routing and other related functionality</td><td><a href="https://github.com/JaffaKetchup/flutter_osrm">https://github.com/JaffaKetchup/flutter_osrm</a></td></tr><tr><td><a data-footnote-ref href="#user-content-fn-1"><em>BETA</em></a> <strong>flutter_map_query_osm_features (JaffaKetchup)</strong></td><td>Query OpenStreetMap features within a radius of a point, using the Overpass and OSM APIs</td><td><a href="https://github.com/JaffaKetchup/flutter_map_query_osm_features">https://github.com/JaffaKetchup/flutter_map_query_osm_features</a></td></tr></tbody></table>

## Additional Layers

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>vector_map_tiles (greensopinion)</strong></td><td>Suite of tools and layers for working with vector maps and associated style files</td><td><a href="https://github.com/greensopinion/flutter-vector-map-tiles">https://github.com/greensopinion/flutter-vector-map-tiles</a></td></tr><tr><td><strong>flutter_map_polywidget (TimBaumgart)</strong></td><td>Layer that allows any widget to be displayed inside a positioned box, similar to <a data-mention href="/pages/IvTwjpFpt0mYepO5ir9v">/pages/IvTwjpFpt0mYepO5ir9v</a></td><td><a href="https://pub.dev/packages/flutter_map_polywidget">https://pub.dev/packages/flutter_map_polywidget</a></td></tr><tr><td><strong>fluttermap_heatmap (tprebs)</strong></td><td>Layer that represents multiple data points in a density-to-color relationship</td><td><a href="https://github.com/tprebs/fluttermap_heatmap">https://github.com/tprebs/fluttermap_heatmap</a></td></tr><tr><td><strong>lat_lon_grid_plugin (matthiasdittmer)</strong></td><td>Layer that shows a grid of latitude longitude lines</td><td><a href="https://github.com/matthiasdittmer/lat_lon_grid_plugin">https://github.com/matthiasdittmer/lat_lon_grid_plugin</a></td></tr><tr><td><strong>flutter_map_compass (josxha)</strong></td><td>Simple compass that illustrates the direction of the map rotation (not the user's real heading)</td><td><a href="https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_compass">https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_compass</a></td></tr></tbody></table>

## Offline Mapping

To help choose between one of these plugins or a DIY solution, see:

{% content-ref url="/pages/bpcomOuvTGL5xNNpSAYp" %}
[Offline Mapping](/v7/tile-servers/offline-mapping)
{% endcontent-ref %}

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_tile_caching (JaffaKetchup)</strong></td><td>Advanced, performant, highly configurable caching &#x26; bulk downloading (under a GPL license)</td><td><a href="https://github.com/JaffaKetchup/flutter_map_tile_caching">https://github.com/JaffaKetchup/flutter_map_tile_caching</a></td></tr><tr><td><strong>flutter_map_cache (josxha)</strong></td><td>Lightweight mid-term tile caching with support for most storage backends and request cancellation.</td><td><a href="https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_cache">https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_cache</a></td></tr></tbody></table>

## Tile Container Parsers/Providers

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_mbtiles (josxha)</strong></td><td>Offline and online MBTiles parser and provider (vector variant also available)</td><td><a href="https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_mbtiles">https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_mbtiles</a></td></tr><tr><td><strong>flutter_map_pmtiles (josxha)</strong></td><td>Offline and online PMTiles parser and provider (vector variant also available)</td><td><a href="https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_pmtiles">https://github.com/josxha/flutter_map_plugins/tree/main/flutter_map_pmtiles</a></td></tr></tbody></table>

## Better `Marker`s

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_location_marker (tlserver)</strong></td><td>Provides a prebuilt solution to display the user's location and heading/direction</td><td><a href="https://github.com/tlserver/flutter_map_location_marker">https://github.com/tlserver/flutter_map_location_marker</a></td></tr><tr><td><strong>flutter_map_marker_popup (rorystephenson)</strong></td><td>Provides a prebuilt solution to display a popup above a marker when tapped</td><td><a href="https://github.com/rorystephenson/flutter_map_marker_popup">https://github.com/rorystephenson/flutter_map_marker_popup</a></td></tr><tr><td><strong>flutter_map_floating_marker_titles (androidseb)</strong></td><td>Enables the display of 'floating' titles over markers</td><td><a href="https://github.com/androidseb/flutter_map_floating_marker_titles">https://github.com/androidseb/flutter_map_floating_marker_titles</a></td></tr><tr><td><a data-footnote-ref href="#user-content-fn-1"><em>BETA</em></a> <strong>flutter_map_fast_markers (KanarekApp)</strong></td><td>Improves performance of markers by painting directly onto canvas</td><td><a href="https://github.com/KanarekApp/flutter_map_fast_markers/tree/canary">https://github.com/KanarekApp/flutter_map_fast_markers/tree/canary</a></td></tr></tbody></table>

### Marker Clustering

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_supercluster (rorystephenson)</strong></td><td>Superfast™ marker clustering solution, without animations</td><td><a href="https://github.com/rorystephenson/flutter_map_supercluster">https://github.com/rorystephenson/flutter_map_supercluster</a></td></tr><tr><td><strong>flutter_map_marker_cluster (lpongetti)</strong></td><td>Beautiful and animated marker clustering solution</td><td><a href="https://github.com/lpongetti/flutter_map_marker_cluster">https://github.com/lpongetti/flutter_map_marker_cluster</a></td></tr><tr><td><strong>flutter_map_radius_cluster (rorystephenson)</strong></td><td>Marker clustering solution with support for <code>async</code> marker searching within a radius</td><td><a href="https://github.com/rorystephenson/flutter_map_radius_cluster">https://github.com/rorystephenson/flutter_map_radius_cluster</a></td></tr></tbody></table>

## Better `Polyline`s & `Polygon`s

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>flutter_map_tappable_polyline (OwnWeb)</strong></td><td>Provides an <code>onTap</code> callback for <code>Polyline</code>s</td><td><a href="https://github.com/OwnWeb/flutter_map_tappable_polyline">https://github.com/OwnWeb/flutter_map_tappable_polyline</a></td></tr><tr><td><strong>flutter_map_line_editor (ibrierley)</strong></td><td>Enables better dynamic editing of <code>Polyline</code>s and <code>Polygon</code>s</td><td><a href="https://github.com/ibrierley/flutter_map_line_editor">https://github.com/ibrierley/flutter_map_line_editor</a></td></tr><tr><td><strong>line_animator (ibrierley)</strong></td><td>Interpolates along a set of points, allowing gradual drawing of lines and animating moving markers</td><td><a href="https://github.com/ibrierley/line_animator">https://github.com/ibrierley/line_animator</a></td></tr></tbody></table>

[^1]: This plugin is not ready for production use, and is liable to breaking changes without major version increments!


# Creating A Plugin

If one of the [independently maintained plugins](/v7/plugins/list) doesn't suit your needs, then you can create your own, to achieve maximum customizability.

Most plugins create either new [Tile Providers](/v7/layers/tile-layer/tile-providers), or new [Layers](/v7/usage/layers). Some plugins just provide additional useful tools.

{% content-ref url="/pages/Iv08n8fQ8A1XeZG65Oi7" %}
[Creating New Tile Providers](/v7/plugins/making-a-plugin/creating-new-tile-providers)
{% endcontent-ref %}

{% content-ref url="/pages/PHCyJci0lnfcS8LgyvNw" %}
[Creating New Layers](/v7/plugins/making-a-plugin/creating-new-layers)
{% endcontent-ref %}

## Submitting A New Plugin

If you've made your own plugin that you're willing to share, please let us know in the #plugins channel on the flutter\_map Discord server. We can then add it to the [Plugins List](/v7/plugins/list). We're always looking forward to see what you've made!

When submitting a plugin, please ensure the plugin:

* *preferably* includes 'flutter\_map\_' in the name, by convention
* is *preferably* available via a standard pub.dev package installation
* includes good documentation (at least to setup basic functionality), and a code example


# Creating New Tile Providers

One common requirement is a custom `TileProvider`, and potentially a custom `ImageProvider` inside. This will allow your plugin to intercept all tile requests made by a map, and take your own action(s), before finally returning a tile.

{% hint style="info" %}
Check the [Plugins List](/v7/plugins/list) for providers that already implement the behaviour you wish to replicate.
{% endhint %}

## 1. Extending `TileProvider`

To create your own usable `TileProvider`, the first step is making a class that `extends` the abstract class, and adding a constructor.

The constructor should accept an argument of `super.headers`, without a `const`ant default.

```dart
class CustomTileProvider extends TileProvider {
    CustomTileProvider({
        // Suitably initialise your own custom properties
        super.headers, // Accept a `Map` of custom HTTP headers
    })
}
```

{% hint style="info" %}
If using an object that needs closing/cancelling, such as an `HttpClient`, override the `dispose` method.
{% endhint %}

## 2. Setup Tile Retrieval

`TileProvider`s must implement a method to return an `ImageProvider` (the image of a tile), given its coordinates and the `TileLayer` it is used within.

{% hint style="success" %}
It is best to put as much logic as possible into a custom `ImageProvider`, to avoid blocking the main thread.
{% endhint %}

There's two methods that could be called by flutter\_map internals to retrieve a tile: `getImage` or `getImageWithCancelLoadingSupport`.

Prefer overriding `getImageWithCancelLoadingSupport` for `TileProvider`s that can cancel the loading of a tile in-flight, if the tile is pruned before it is fully loaded. An example of a provider that may be able to do this is one that makes HTTP requests, as HTTP requests can be aborted on the web (although Dart does not 'natively' support it yet, so a library such as Dio is necessary). Otherwise, `getImage` must be overridden.

{% tabs %}
{% tab title="With Cancel Loading Support" %}
In addition to the coordinates and `TileLayer`, the method also takes a `Future<void>` that is completed when the tile is pruned. It should be listened to for completion (for example, with `then`), then used to trigger the cancellation.

For an example of this, see [Tile Providers](/v7/layers/tile-layer/tile-providers#cancellablenetworktileprovider).

```dart
    @override
    bool get supportsCancelLoading => true;
    
    @override
    ImageProvider getImageWithCancelLoadingSupport(
        TileCoordinates coordinates,
        TileLayer options,
        Future<void> cancelLoading,
    ) =>
        CustomCancellableImageProvider(
            url: getTileUrl(coordinates, options),
            fallbackUrl: getTileFallbackUrl(coordinates, options),
            cancelLoading: cancelLoading,
            tileProvider: this,
        );
```

{% endtab %}

{% tab title="Without Cancel Loading Support" %}

```dart
    @override
    ImageProvider getImage(TileCoordinates coordinates, TileLayer options) =>
        CustomImageProvider(
            url: getTileUrl(coordinates, options),
            fallbackUrl: getTileFallbackUrl(coordinates, options),
            tileProvider: this,
        );
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
If developing a plugin, you may wish to adjust the 'User-Agent' header has been to further differentiate your plugin's traffic from vanilla 'flutter\_map' traffic.
{% endhint %}

## (Optionally) Override URL Generation

Some custom `TileProvider`s may want to change the way URLs are generated for tiles, given a coordinate.

It's possible to override:

* how the `urlTemplate`'s placeholders are populated: `populateTemplatePlaceholders`
* the values used to populate those placeholders: `generateReplacementMap`
* the generation method itself: `getTileUrl` and/or `getTileFallbackUrl`

{% hint style="warning" %}
Avoid overriding the generation method itself, as it is not usually necessary.
{% endhint %}


# Creating New Layers

Creating a new map layer is a great way to achieve a more custom, performant, map design. For example, it might be used to display a scale bar, or overlay a grid.

{% hint style="info" %}
Check the [Plugins List](/v7/plugins/list) for layers that already implement the behaviour you wish to replicate.
{% endhint %}

## 1. Creating A Layer Widget

It starts with a normal `StatelessWidget` or `StatefulWidget`, which then starts its widget tree with a widget dependent on whether the layer is designed to be either 'mobile' or 'static', depending on the purpose of the layer. For more information, see [Layers](/v7/usage/layers#mobile-vs-static-layers).

{% tabs %}
{% tab title="Mobile Layers" %}

```dart
class CustomMobileLayer extends StatelessWidget {
  const CustomMobileLayer({super.key});

  @override
  Widget build(BuildContext context) {    
    return MobileLayerTransformer(
      child: // your child here
    );
  }
}
```

{% endtab %}

{% tab title="Static Layers" %}

```dart
class CustomStaticLayer extends StatelessWidget {
  const CustomStaticLayer({super.key});

  @override
  Widget build(BuildContext context) {
    return SizedBox.expand();
    // and/or
    return Align();
  }
}
```

{% endtab %}
{% endtabs %}

## 2. Hooking Into Inherited State

Then, there are three possible methods that could be used to retrieve separate 'aspects' of the state of the map.

Calling these inside a `build` method will also cause the layer to rebuild automatically when the depended-on aspects change.

```dart
final camera = MapCamera.of(context);
final controller = MapController.of(context);
final options = MapOptions.of(context);
```

{% hint style="warning" %}
Using these methods will restrict this widget to only being usable inside the context of a `FlutterMap`.
{% endhint %}


# flutter\_map

A versatile mapping package for Flutter. Simple and easy to learn, yet completely customizable and configurable, it's the best choice for mapping in your Flutter app.

[![pub.dev](https://camo.githubusercontent.com/a796d19cced2795c62dee9f3b165665449dbfd0bd46bf39beceef3371f14ebee/68747470733a2f2f696d672e736869656c64732e696f2f7075622f762f666c75747465725f6d61702e7376673f6c6162656c3d4c61746573742b56657273696f6e)](https://pub.dev/packages/flutter_map) [![stars](https://camo.githubusercontent.com/7e6d80df311cbd5e68edf6994e404a97af85c84f7ec66614875dba12f055c246/68747470733a2f2f62616467656e2e6e65742f6769746875622f73746172732f666c6561666c65742f666c75747465725f6d61703f6c6162656c3d737461727326636f6c6f723d677265656e2669636f6e3d676974687562)](https://github.com/fleaflet/flutter_map/stargazers) [![likes](https://camo.githubusercontent.com/450afb6eb57ffb0e3cdae61f8a90d51541dbe09eaddcc69900cb09a91762363a/68747470733a2f2f696d672e736869656c64732e696f2f7075622f6c696b65732f666c75747465725f6d61703f6c6f676f3d666c7574746572)](https://pub.dev/packages/flutter_map/score)      [![Open Issues](https://camo.githubusercontent.com/3f89334e961516c8b3eef4879a287818a2b8e6523e5f9f8d3767e1d98c8a4f44/68747470733a2f2f62616467656e2e6e65742f6769746875622f6f70656e2d6973737565732f666c6561666c65742f666c75747465725f6d61703f6c6162656c3d4f70656e2b49737375657326636f6c6f723d677265656e)](https://github.com/fleaflet/flutter_map/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) [![Open PRs](https://camo.githubusercontent.com/2d48f34537361cf13f775e8c88c5884a7a7b280469b319453b2ccdabdee1f2db/68747470733a2f2f62616467656e2e6e65742f6769746875622f6f70656e2d7072732f666c6561666c65742f666c75747465725f6d61703f6c6162656c3d4f70656e2b50527326636f6c6f723d677265656e)](https://github.com/fleaflet/flutter_map/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc)

## Feature Highlights

<table data-card-size="large" data-view="cards" data-full-width="false"><thead><tr><th align="center"></th><th align="center"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td align="center">🗺️ <strong>Supports any* map style</strong></td><td align="center">We natively support any static raster tile server, including from a web server or even from the local file system or app asset store.<br>No more vendor lock-in!</td><td></td></tr><tr><td align="center">💪 <strong>Stress-free setup and use</strong></td><td align="center">Migrating from a commercial library such as Google Maps has never been easier! No more complex platform-specific setup, no more API keys: just add a widget and you're done.</td><td></td></tr><tr><td align="center">🧩 <strong>Wide ecosystem of plugins</strong></td><td align="center">In the unlikely event that flutter_map doesn't natively contain something you need, just check to see if there's a community maintained plugin that does what you need!</td><td></td></tr><tr><td align="center">➕ <strong>Add other map features easily</strong></td><td align="center">Add polygons, polylines, and markers/pins to your map easily and quickly. Markers support displaying any widget you might want.</td><td></td></tr></tbody></table>

<details>

<summary>How does flutter_map compare to other mapping libraries?</summary>

This usually refers to libraries such as 'mapbox\_gl' and 'google\_maps\_flutter'. In most ways, it is better, in some it is worse.

flutter\_map wins on:

* Less vendor lock-in (and potentially reduced costs)\
  You're not locked into a particular tile server with us - choose from hundreds of options, or build your own!
* Customizability & extensibility\
  Add all sorts of layers to display custom widgets and data on top of your map, and choose from flutter\_map's many community maintained plugins to add even more functionality!
* Ease of use/setup\
  We don't require any API keys or platform specific setup (other than enabling the Internet permission!), so you can get started quicker, and make changes without fear of breaking your release application.
* Support quality and frequency\
  Most questions are answered and resolved within 12-24 hours, thanks to our dedicated maintainers and community.&#x20;

However, alternatives may win on:

* Performance\*\
  flutter\_map's performance is very adequate for the vast majority of applications, and many big businesses use FM to provide maps in their Flutter app.\
  However, if you're using high-thousands of `Markers` or `Polygons` and such like, alternatives may win, purely because they use platform views and GL, and so can do calculations outside of Dart.
* ... and that's pretty much it 😉

</details>

## Demonstration

Setting up an interactive and compliant[^1] map is simpler than making your lunch-time coffee! It can be accomplished in just under 30 lines and a minute or two to install.

This code snippet demonstrates **everything** you need for a simple map (in just over 20 lines!), but of course, FM is capable of much more than just this, and you could find yourself lost in the many options available and possibilities opened!

<pre class="language-dart" data-line-numbers><code class="lang-dart">import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';

@override
Widget build(BuildContext context) {
  return <a data-footnote-ref href="#user-content-fn-2">FlutterMap</a>(
    <a data-footnote-ref href="#user-content-fn-3">options</a>: MapOptions(
      initialCenter: LatLng(51.509364, -0.128928),
      initialZoom: 9.2,
    ),
    <a data-footnote-ref href="#user-content-fn-4">children</a>: [
      TileLayer(
        <a data-footnote-ref href="#user-content-fn-5">urlTemplate</a>: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
        userAgentPackageName: 'com.example.app',
      ),
      <a data-footnote-ref href="#user-content-fn-6">RichAttributionWidget</a>(
        attributions: [
          TextSourceAttribution(
            'OpenStreetMap contributors',
            onTap: () => <a data-footnote-ref href="#user-content-fn-7">launchUrl</a>(Uri.parse('https://openstreetmap.org/copyright')),
          ),
        ],
      ),
    ],
  );
}
</code></pre>

## Get Help

Not quite sure about something? No problem. Please get in touch via any of these methods, and we'll be with you as soon as possible. Please remember that we are volunteers, so we cannot guarantee (fast) support.

* For bug reports & feature requests: check the [#faqs](#faqs "mention") then [GitHub Issues](https://github.com/fleaflet/flutter_map/issues)
* For support & everything else: check the [#faqs](#faqs "mention") then [flutter\_map Discord server](https://discord.gg/BwpEsjqMAH)

{% hint style="info" %}
Due to time shortages, wait times for feature request implementations are currently extremely long and may not happen at all.

We'd love to have your contributions to add your own or others' pull requests!
{% endhint %}

### FAQs

We get quite a lot of similar questions, so please check if your question is here before you ask!

{% content-ref url="/pages/lL0cVxkdhQ8OmMlS1zdD" %}
[How Does It Work?](/v6/getting-started/explanation)
{% endcontent-ref %}

<details>

<summary>How can I use a custom map style?<br>How can I prevent POI/labels rotating when the map rotates?<br>How can I remove certain POI/labels from the map?</summary>

Unfortunately, this library cannot provide this functionality. It has no control over the tiles displayed in the `TileLayer`. This is a limitation of the technology, not this library.

This is because raster tiles are just images generated by a 3rd party tile server (dictated by your URL template), and therefore cannot be changed by the library that displays the tiles. Filters can be applied to the entire tile, such as an emulated dark mode, but these effects do not look great.

However, tilesets can be styled. This is the most effective way of using custom styles. These methods may help you with this:&#x20;

* You may wish to use a commercial service like Mapbox Studio, which allows you to style multiple tilesets. See [Using Mapbox](/v6/tile-servers/using-mapbox).
* Alternatively, you can experiment with vector tiles. These are not pre-rendered, and so allow any style you desire to be applied on the fly. See [Raster vs Vector Tiles](/v6/getting-started/explanation/raster-vs-vector-tiles#vector-tiles).
* Your last option is to serve tiles yourself. See [Other Options](/v6/tile-servers/other-options).

</details>

<details>

<summary>How can I route a user between two locations?<br>Why does the <code>Polyline</code> only go in a straight line between two points?</summary>

See [Polyline Layer](/v6/layers/polyline-layer#routing-navigation).

</details>

<details>

<summary>How can I add a <code>Marker</code> where the user's location is?<br>How can I center the map on the user's location?</summary>

This is beyond the scope of flutter\_map. However, you can use the [community maintained plugin 'flutter\_map\_location\_marker'](https://github.com/tlserver/flutter_map_location_marker) to do this.

Alternatively, use the 'location' and 'compass' packages to generate a stream of the user's location and heading, and feed that to a `Marker` using a `StreamBuilder`.

</details>

<details>

<summary>Why does the map disappear/go grey when I zoom in far?<br>Why does the map stop zooming in even though I know there are more zoom levels?</summary>

If tiles are disappearing when you zoom in, the default grey background of the `FlutterMap` widget will shine through. This usually means that the tile server doesn't support these higher zoom levels.

If you know that there are more tiles available further zoomed in, but flutter\_map isn't showing them and scaling a particular zoom level instead, it's likely because the `TileLayer.maxNativeZoom` property is set too low (it defaults to 19).

To set/change the zoom level at which FM starts scaling tiles, change the `TileLayer.maxNativeZoom` property. To set/change the max zoom level that can actually be zoomed to (hard limit), use `MapOptions.maxZoom`.

</details>

<details>

<summary>How can I make the map 3D, or view it as a globe?</summary>

Unfortunately, this isn't supported, partially due to lack of time on the maintainer's part to implement this feature, partially due to technical limitations. PRs are welcome!

</details>

[^1]: (includes necessary attribution)

[^2]: As simple as just another widget...

[^3]: Plenty of customisable options available

[^4]: Choose from a variety of features to display on your map

[^5]: Connect to any\* map server/provider

[^6]: Stylish attribution required? No problem!

[^7]: *Requires url\_launcher to be installed separately*


# Showcase

A collection of cool projects that use flutter\_map

## Apply To Be Listed

Want to advertise your project here? For more information, and to apply, please see:

* [The application form for non-OSS projects](https://docs.google.com/forms/d/e/1FAIpQLSeXALT0XVnWdl8vTcYQUz9l3mC7j63Et1MIkkEnnn7BgToRtw/viewform?usp=sf_link)
* [The application form for OSS and/or non-profit projects](https://docs.google.com/forms/d/e/1FAIpQLSeM3RgRc-QG7diODXd29DzuQWAWlutUQC3uR_b0cSwbaOkOjg/viewform?usp=sf_link) (it's free!)

{% hint style="success" %}
We also appreciate donations, and we're extremely grateful for anything you can spare.

We'll donate 15% of what we receive to the OpenStreetMap Foundation, as a thanks for their excellent work.

For more information, details about benefits, and the link to donate, please see [Supporters](/v6/supporters#support-us).
{% endhint %}

## Non-OSS Sponsors

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Map Marker</strong></td><td>Your places organizer</td><td><a href="https://www.mapmarker.app/">https://www.mapmarker.app/</a></td><td><a href="/files/l8EEhkIz3wMTGGIVeV2j">/files/l8EEhkIz3wMTGGIVeV2j</a></td><td><a href="https://www.mapmarker.app/">https://www.mapmarker.app/</a></td></tr><tr><td><strong>Your advert here!</strong></td><td>Support us and get a top advert slot! More information in <a data-mention href="#apply-to-be-listed">#apply-to-be-listed</a>.</td><td></td><td></td><td></td></tr></tbody></table>

## OSS and/or Non-Profit Projects

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th><select multiple><option value="288089d75dba4cc2930ef1e879241692" label="Open Source" color="blue"></option><option value="602c8c9d2d4b4c9bbdc1b704dbed0fe5" label="100% Non-Profit" color="blue"></option></select></th><th></th><th data-type="content-ref"></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><strong>Every Door</strong></td><td><span data-option="288089d75dba4cc2930ef1e879241692">Open Source, </span><span data-option="602c8c9d2d4b4c9bbdc1b704dbed0fe5">100% Non-Profit</span></td><td>The most efficient OpenStreetMap editor for surveying shops and benches</td><td><a href="https://every-door.app/">https://every-door.app/</a></td><td><a href="https://every-door.app/">https://every-door.app/</a></td><td><a href="/files/gLjBq3BCAhLGPaTuLrFe">/files/gLjBq3BCAhLGPaTuLrFe</a></td></tr><tr><td><strong>Ente Photos</strong></td><td><span data-option="288089d75dba4cc2930ef1e879241692">Open Source</span></td><td>End-to-end encrypted alternative to Google Photos </td><td><a href="https://ente.io/">https://ente.io/</a></td><td><a href="https://ente.io/">https://ente.io/</a></td><td><a href="/files/muAERaZeGhotv3DmmbyK">/files/muAERaZeGhotv3DmmbyK</a></td></tr><tr><td><strong>Your advert here!</strong></td><td></td><td>More information in <a data-mention href="#apply-to-be-listed">#apply-to-be-listed</a>.</td><td></td><td></td><td></td></tr></tbody></table>

{% hint style="info" %}
There's many more that aren't listed here! See the [dependents list on GitHub](https://github.com/fleaflet/flutter_map/network/dependents), and the #showcase channel for more minor projects on our Discord server.
{% endhint %}

## Plugins

{% content-ref url="/pages/0hlEfILBhS5xj338Lp5i" %}
[Plugins List](/v6/plugins/list)
{% endcontent-ref %}


# Supporters

## Support Us

Please consider donating anything you can to us, we're extremely grateful for any and all donations. They keep us going and will allow us to cover any unforeseen or future costs, and potentially open up more doors and opportunities in future!

We'll donate 15% of what we receive to the OpenStreetMap Foundation, as a thanks for their excellent work. The remainder goes directly to improving flutter\_map.

Enter a nickname to be listed below in [#past-supporters](#past-supporters "mention"), and enter your Discord username to be granted the "Supporter" role on our server and gain access to a special supporter-only chat channel. These benefits last indefinitely (but may take 48 hours to grant, as this is done manually).

* [**Donate with GBP**](https://donate.stripe.com/14kg1s1CB0dSdk4cMM) & British specific payment methods
* [**Donate with EUR**](https://donate.stripe.com/cN202ugxvbWA93O146) & European specific payment methods

We support a variety of payment methods through Stripe, although some have lower fees for us. In general, cards have the highest fees, and the closer the method to the bank, the lower the fees. Donating in the GBP currency also reduces our fees, but we provide EUR checkout to add extra payment methods for those living in Europe. If paying in a currency other than your own, your bank's exchange rate/terms will apply. At this time, only one-time donations are accepted: please get in touch if you'd like to donate on a regular basis.

{% hint style="info" %}
We use Stripe for payments, analytics, and other business services.  Stripe may collect personal data including via cookies and similar technologies.  The personal data Stripe collects may include transactional data and identifying information about devices that connect to its services. Stripe uses this information to operate and improve the services it provides to us, including for fraud detection, loss prevention, authentication, and analytics related to the performance of its services.  You can learn more about Stripe and read its privacy policy at [https://stripe.com/privacy](https://stripe.com/gb/privacy).
{% endhint %}

## Past Supporters

Huge thanks to all our past supporters, you help keep this project going. In no particular order, thanks to:

* androidseb
* Roundtrip
* corepuncher
* Maxi
* ... and everyone else who donated anonymously


# Credits & Contributing

## Credits

Huge thanks to everyone who uses, supports, and/or contributes to flutter\_map in any way, you've helped make the most popular non-commercially aimed mapping solution for Flutter!

In particular, thanks go to:

* All the current maintainers:
  * @ibrierley
  * @JaffaKetchup
  * @mootw (previously @MooNag)
  * @TesteurManiak
  * @josxha
* All the previous maintainers:
  * John P Ryan - the original founder of this project, over at AppTree Software
  * @kengu
  * @maRci002
* The authors of this documentation:
  * @JaffaKetchup
* Anyone who has contributed to making flutter\_map: [Contributor List](https://github.com/fleaflet/flutter_map/graphs/contributors)
* Anyone who has made plugins for flutter\_map: [Plugins List](/v6/plugins/list)
* Anyone who has donated to flutter\_map: [Supporters](/v6/supporters#past-supporters)

## Contributing

We're always happy to receive improvements and fixes, so please submit them whenever you can! A few key points are listed below.

{% hint style="info" %}
Many feature additions are more suitable for plugins, instead of being added to the core.  This is aimed to reduce the future maintenance burden/cost on the maintainers.

If we deny your PR for this reason, please do consider publishing a plugin, and we'll be happy to add it to the [Plugins List](/v6/plugins/list)! See [Creating A Plugin](/v6/plugins/making-a-plugin) for more information.
{% endhint %}

* **If your PR will add a major or breaking change, please discuss it with us first, via the Issue Tracker**\
  We don't want to waste your time if we think it's more appropriate for a plugin, and it helps to make a clear plan before starting work
* **Create a draft PR as soon as work starts, and take it out of draft status when ready for review**\
  Keep everyone in the loop, so no-one tries working on the same thing as you
* **Don't change the package version, GitHub workflows, lints, or any other meta files without clarification**\
  We rely on a standardized process and procedure to ensure top-quality releases
* **Use a clear (preferably** [**Conventional**](https://www.conventionalcommits.org/)**) PR title**\
  This makes it easier for us to group commits for release and write correct CHANGELOGs

### Apply To Be A Maintainer

{% embed url="<https://forms.gle/jEeygGJZtBWUdLFf6>" %}


# How Does It Work?

{% hint style="info" %}
If you don't know about standard map things, such as the latitude/longitude system and projections, you should probably read about these first!

*If you want a truly British insight into this, look no further than:* [*https://youtu.be/3mHC-Pf8-dU*](https://youtu.be/3mHC-Pf8-dU) *&* [*https://youtu.be/jtBV3GgQLg8*](https://youtu.be/jtBV3GgQLg8)*.*
{% endhint %}

## Layers

Interactive maps are often[^1] formed from multiple layers of data, which can be panned (moved), rotated, ~~and sometimes tilted/pitched~~, based on the user's gesture input, or another programmatic control.

## Tile Basics

One type of layer included on every map is known as a tile layer, which displays tiles, square segments of a map.

When multiple tiles, which are each the same dimensions, are laid out around each other, they give the illusion of one continuous map.

Tiles can be referenced/identified in a few different ways, such as:

* Slippy Map Convention (the most popular/common)
* [TMS](https://wiki.openstreetmap.org/wiki/TMS) (very similar to the Slippy Map Convention)
* [WMS](https://wiki.openstreetmap.org/wiki/WMS)&#x20;
* [WMTS](https://en.wikipedia.org/wiki/Web_Map_Tile_Service)

Tiles themselves can be of two types:

* Raster\
  Each tile is a normal pre-rendered standard image, such as JPG or PNG
* Vector\
  Each tile is a special format containing the data for the tile, and is then rendered by the end library

This library/documentation focuses on maps accessible via the Slippy Map Convention, although all are supported.

This library only supports raster tiles. See [Raster vs Vector Tiles](/v6/getting-started/explanation/raster-vs-vector-tiles) for more information.

### Slippy Map Convention

{% hint style="info" %}
For more information about the Slippy Map Convention, visit [the OpenStreetMap Wiki](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames).
{% endhint %}

Slippy map tiles are accessed by 3 coordinates, x/y/z.

X & Y coordinates correspond to all the latitudes and longitudes contained within that tile, however they are not actual longitude and latitude. For example, geographic coordinate (61.127, -0.123) [might be](#user-content-fn-2)[^2] in the tile (128983, 430239).

The Z value represents the current zoom level, where one tile ([0/0/0](https://tile.openstreetmap.org/0/0/0.png)) covers the entire planet with extremely low detail at level 0, to level 20 (although some tile servers will support even higher zoom levels) where over 1 trillion tiles are required to cover the entire surface of the Earth.

## Sourcing Tiles

Tiles, especially raster tiles, take a lot of computing power and time to generate, because of the massive scale of all the input and output data. Therefore, most tiles are sourced externally, from an online tile server (either publicly or by users holding an API key), or sometimes from the local filesystem or asset store of the app.

{% content-ref url="/pages/4rzSoAS2RWublrqODkHc" %}
[Other Options](/v6/tile-servers/other-options)
{% endcontent-ref %}

## Tile Providers

A tile provider (within flutter\_map) is responsible for:

* Constructing the path/URL to a tile, when given its coordinates (x/y/z): [#slippy-map-convention](#slippy-map-convention "mention")
* Using an `ImageProvider` or other mechanism to fetch that tile: [#sourcing-tiles](#sourcing-tiles "mention")
* Performing any other processing steps, such as caching

But don't worry! flutter\_map (or a plugin) creates a provider for you, so for most use cases and tile sources, you shouldn't need to handle this yourself!

{% content-ref url="/pages/x2LzdwedaPxt1VzqBAz4" %}
[Tile Providers](/v6/layers/tile-layer/tile-providers)
{% endcontent-ref %}

{% hint style="info" %}
This can be quite confusing for newcomers!

Within this library, 'tile providers' use 'tile servers' to retrieve tiles from the Internet. On the other hand, 'tile servers' and external sites usually use 'tile providers' to mean 'tile servers'!
{% endhint %}

[^1]: Most mapping libraries operate in this way

[^2]: This is not a real example of this relationship.


# Raster vs Vector Tiles

{% hint style="info" %}
It is important to note that 'flutter\_map' only supports raster tiles natively. Vector tiles can be used with a community maintained plugin.

This is described in more detail at the bottom of this page.
{% endhint %}

There are 2 main types of tiles a server can serve: raster and vector; each has their own advantages and drawbacks. This page is designed to help you choose a type for your app, and help you use vector tiles if you choose to.

## Raster Tiles

Raster tiles are the 'older' type of tile, and are raster images (usually .png or .jpg). These tiles are good because they can render quickly and easily, can be viewed without special software, and are readily available from most mapping services. As such, this makes them the popular choice for beginners.

However, raster tiles cannot be easily themed: a theme needs a whole new set of map tiles. This makes apps using light and dark themes have mismatching maps. As well as this, raster tiles usually have larger file sizes meaning slower download times, and they can become blurred/pixelated when viewed at a larger scale: a problem for users when zooming between zoom levels. Another issue is that shapes/text inside tiles cannot be rotated, hence the name 'static tiles': therefore, rotating the map will not rotate the name of a road, for example.

## Vector Tiles

Vector tiles can be considered the 'newer' standard. These images might contain a specialised format (such as .pbf) dictating the mathematics and coordinates used to draw lines and shapes. Because these tiles are drawn at render time instead of at request/server time, theming can be used to make the map fit in better with an app's theme. The math-based image means that the images/tiles can be scaled without any loss of clarity.

However it does add complexity to the rendering process as each element needs to be parsed and painted individually, meaning an impact to performance. Text elements and certain shapes can also be rotated (unlike raster tiles) to match the user's orientation, not the orientation of the map; but calculating this rotation needs to be done every frame, meaning an even larger impact on performance.

### Using Vector Tiles

Due to the complications mentioned above, 'flutter\_map' does not natively support vector tiles. However, vector tiles can be used with a [community maintained plugin (`vector_map_tiles`)](https://github.com/greensopinion/flutter-vector-map-tiles) to do this.


# Installation

## Install

{% hint style="success" %}
All users should also [install 'latlong2'](https://pub.dev/packages/latlong2/install) to work with coordinates in 'flutter\_map'.

In the event that the `LatLng` object provided by that library conflicts with another, for example the one provided by Google Maps, you may need to [use the 'as' suffix](https://dart.dev/guides/packages#importing-libraries-from-packages).
{% endhint %}

### From [pub.dev](https://pub.dev/packages/flutter_map)

Just import the package as you would normally, from the command line:

<pre class="language-bash"><code class="lang-bash">flutter pub add flutter_map latlong2
flutter pub add <a data-footnote-ref href="#user-content-fn-1">flutter_map_cancellable_tile_provider</a> # OPTIONAL
</code></pre>

### From [github.com](https://github.com/fleaflet/flutter_map)

{% hint style="warning" %}
Commits available from Git (GitHub) may not be stable. Only use this method if you have no other choice.
{% endhint %}

If you urgently need the latest version, a specific branch, or a specific fork, you can use this method.

First, use [#from-pub.dev](#from-pub.dev "mention"), then add the following lines to your pubspec.yaml file, as a root object:

{% code title="pubspec.yaml" %}

```yaml
dependency_overrides:
    flutter_map:
        git:
            url: https://github.com/fleaflet/flutter_map.git
            # ref: main (custom branch/commit)
```

{% endcode %}

## Additional Setup

### Web

Always force usage of the CanvasKit renderer instead of the HTML renderer, even on mobile devices.

The HTML renderer causes performance issues, and may also cause other bugs. Although the CanvasKit renderer does require slightly more Javascript (and therefore a longer download time), it works much better with flutter\_map.

For more information about web renderers, see <https://docs.flutter.dev/platform-integration/web/renderers>.

### Android

flutter\_map needs to access the Internet to load tiles, in most cases. On Android, apps must include the INTERNET permission in their manifest. Add the following line to all manifests:

{% code title="AndroidManifest.xml" %}

```xml
<uses-permission android:name="android.permission.INTERNET"/>
```

{% endcode %}

### MacOS

flutter\_map needs to access the Internet to load tiles, in most cases. On MacOS, apps must include a dedicated entitlement. Add the following lines to 'macos/Runner/DebugProfile.entitlements' and 'macos/Runner/Release.entitlement&#x73;**':**

{% code title="\*.entitlements" %}

```xml
<key>com.apple.security.network.client</key>
<true/>
```

{% endcode %}

## Import

After installing the package, import it into the necessary files in your project:

```dart
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
```

{% hint style="warning" %}
You must comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

*The OpenStreetMap Tile Server (as used in this documentation) ToS can be* [*found here*](https://operations.osmfoundation.org/policies/tiles)*. Other servers may have different terms.*
{% endhint %}

[^1]: [Tile Providers](/v6/layers/tile-layer/tile-providers#cancellablenetworktileprovider)


# Examples

flutter\_map provides an example application showcasing much of its functionality. In some cases, the example app contains undocumented functionality, so it's definitely worth checking out!

## Live Web Demo

{% hint style="info" %}
Note that the web demo is built automatically from the ['master' branch](https://github.com/fleaflet/flutter_map), so may not reflect the the latest release on pub.dev.
{% endhint %}

{% hint style="warning" %}
Please don't abuse the web demo! It runs on limited bandwidth and won't hold up to thousands of loads.

If you're going to be straining the application, please see [#prebuilt-artifacts](#prebuilt-artifacts "mention"), and serve the application yourself.
{% endhint %}

{% embed url="<https://demo.fleaflet.dev>" %}

## Prebuilt Artifacts

If you can't build from source for your platform, our GitHub Actions CI system compiles the example app to GitHub Artifacts for Windows, Web, and Android.

The Windows and Android artifacts just require unzipping and installing the .exe or .apk found inside.

The Web artifact requires unzipping and serving, as it contains more than one unbundled file. You may be able to use [dhttpd](https://pub.dev/packages/dhttpd) for this purpose.

{% hint style="info" %}
Note that these artifacts are built automatically from the ['master' branch](https://github.com/fleaflet/flutter_map), so may not reflect the the latest release on pub.dev.
{% endhint %}

{% embed url="<https://nightly.link/fleaflet/flutter_map/workflows/master/master>" %}
Latest Build Artifacts (thanks [nightly](https://nightly.link/))
{% endembed %}

## Build From Source

If you need to use the example app on another platform, you can build from source, using the 'example' directory of the repository.

{% @github-files/github-code-block url="<https://github.com/fleaflet/flutter_map/tree/master/example>" %}


# Migrating To v6

{% hint style="info" %}
This update has renewed two of the oldest surviving sections of 'flutter\_map' (state/`MapController` and `TileProvider`s), fixed bugs, and added features!

This is significant progress in our aim to renew the project and bring it up to date. In the long run, this will bring it inline with up-to-date Flutter good practises and techniques, improve its performance and stability, and reduce the maintenance burden.
{% endhint %}

There are major breaking changes for all users, as well as some things users should check and possibly change.

Some changes have deprecations and messages, some do not. Please refer to the sections below for information on how to migrate your project, as well as in-code documentation and deprecation messages, if your migration is not listed below. Some changes are omitted if they are deemed unlikely to affect implementations.

## Changelog & Highlights

There's loads of changes in this release, which will improve performance and reduce costs! Check out these highlights, along with the full changelog:

{% embed url="<https://github.com/fleaflet/flutter_map/blob/master/CHANGELOG.md>" %}
Full Changelog
{% endembed %}

{% hint style="success" %}
We've added in-memory caching to the underlying custom `ImageProvider`. This means that they do not need to be re-requested if they are pruned then re-loaded, reducing tile loading times, and reduce tile requests and costs!

No action is needed to benefit from this.
{% endhint %}

{% hint style="success" %}
If you're developing an app for the web, there's an exciting new performance boost available. By aborting in-flight HTTP requests if tiles are pruned before they are fully-loaded, connections can be freed up, reducing tile loading times, and potentially saving you money!

There are also advantages for other platforms, although they may not be quite as visible.

Manual action is required to benefit from this. See [Tile Providers](/v6/layers/tile-layer/tile-providers#cancellablenetworktileprovider) for more information.
{% endhint %}

{% hint style="success" %}
Rotation is now supported on desktop! Simply use the CTRL (or equivalent) keyboard key (customizable in `MapOptions`) and mouse.
{% endhint %}

{% hint style="success" %}
We've added some warning & recommendation logs in-code, that will trigger under certain circumstances. If they trigger, make sure to listen to them to benefit from performance and efficiency improvements!
{% endhint %}

## Migration Instructions

### General/Misc

<details>

<summary><code>CustomPoint</code> has been replaced by extension methods on <code>Point</code></summary>

[Extension methods](https://dart.dev/language/extension-methods) are now used to add the required functionality to the standard 'dart:math' `Point` object.

To migrate, most cases should just need to replace all occurrences of `CustomPoint` with `Point`.

</details>

<details>

<summary>"Plugin API" import has been removed</summary>

This import path was getting increasingly useless and exposing increasingly less features compared to the standard import. It also covered the standard import in the auto-generated DartDoc documentation, as it exported it as well.

All features that need to be exposed are now exposed through the primary import, and the dedicated plugin import has been removed.

</details>

### State Management

<details>

<summary><code>FlutterMapState</code> has been removed</summary>

`FlutterMapState` previously represented all of the map's state. However, to improve the maintainability of this library's internals, and to improve performance, it has been removed and replaced with several 'aspects':

* `MapCamera.of`: for more information, see  [#some-of-mapcontrollers-responsibilities-have-been-moved-to-mapcamera](#some-of-mapcontrollers-responsibilities-have-been-moved-to-mapcamera "mention")
* `MapOptions.of`: use to access the ambient configured `MapOptions`
* (`MapController.of`): use to access the ambient `MapController`, even if one was not explicitly defined by the user

In most cases, migrating will entail replacing `FlutterMapState` with `MapCamera`, but another aspect may be required.

See [/pages/PHCyJci0lnfcS8LgyvNw#2.-hooking-into-inherited-state](https://docs.fleaflet.dev/v6/getting-started/pages/PHCyJci0lnfcS8LgyvNw#2.-hooking-into-inherited-state "mention") and [Programmatic Control](/v6/usage/programmatic-control) for more information.

</details>

<details>

<summary>Some of <code>MapController</code>'s responsibilities have been moved to <code>MapCamera</code></summary>

`MapController` now only controls the map's position/viewport/camera. The map's position is now described by `MapCamera`.

You should not read camera data directly from a `MapController`: these methods have been deprecated.

There are multiple possibilities for migration:

1. If inside the `FlutterMap` context, prefer using `MapCamera.of(context)`
2. Otherwise, use `MapController` in the same way, but use the `.camera` getter to retrieve the `MapCamera`.

See [Programmatic Control](/v6/usage/programmatic-control) for more information.

</details>

### Children/Layers

<details>

<summary><code>nonRotatedChildren</code> has been removed</summary>

The approach to 'mobile' and 'static' layers has been changed. Mobile layers now wrap themselves in a `MobileLayerTransformer` which uses the inherited state, instead of `FlutterMap` applying the affects directly to them. Static layers should now ensure they use `Align` and/or `SizedBox.expand`.

This has been done to simplify setup, and allow for placing static layers between mobile layers.

</details>

<details>

<summary>Custom layers need to define their behaviour</summary>

The way custom layers are defined has changed. Mobile/moving layers should now use `MobileLayerTransformer` at the top of their widget tree.

For more information, see [/pages/PHCyJci0lnfcS8LgyvNw#1.-creating-a-layer-widget](https://docs.fleaflet.dev/v6/getting-started/pages/PHCyJci0lnfcS8LgyvNw#1.-creating-a-layer-widget "mention").

</details>

#### Tile Layer

<details>

<summary><code>retinaMode</code> behaviour has changed</summary>

Previously, the `retinaMode` property enabled/disabled the simulation of retina mode. To request retina tiles from the server, either the `{r}`placeholder or "@2x" string could be included in the `urlTemplate`.\
This behaviour was unclear, did not conform to the norms of other mapping packages, and meant the `{r}` placeholder was actually redundant.

Now, `retinaMode` also affects whether the `{r}` placeholder is filled in. If `true`, and `{r}` is present, then that will now be filled in to request retina tiles. If the placeholder is not present, only then will flutter\_map simulate retina mode.

Additionally, it is now recommended to use the `RetinaMode.isHighDensity` method to check whether `retinaMode` should be enabled.

For more information, see [Tile Layer](/v6/layers/tile-layer#retina-mode).

</details>

<details>

<summary><code>backgroundColor</code> has been replaced by <code>MapOptions.backgroundColor</code></summary>

This will simplify the developer experience when using multiple overlaid `TileLayer`s, as `Colors.transparent` will no longer need to be specified. There is no reason that multiple `TileLayer`s would each need to have a different (non-transparent) background colors, as the layers beneath would be invisible and therefore pointless.

Therefore, `TileLayer`s now have transparent backgrounds, and the new `MapOptions.backgroundColor` property sets the background color of the entire map.

To migrate, move any background colour specified on the bottom-most `TileLayer` to `MapOptions`.

</details>

<details>

<summary><code>templateFunction</code> has been replaced by <code>TileProvider.populateTemplatePlaceholders</code></summary>

`TileProvider.templateFunction` has been deprecated. It is now preferrable to create a custom `TileProvider` extension, and override the `populateTemplatePlaceholders` method. This has been done to reduce the scope of `TileLayer`.

To migrate, see [Creating New Tile Providers](/v6/plugins/making-a-plugin/creating-new-tile-providers).

</details>

#### Marker Layer

<details>

<summary><code>anchor</code> and all related objects have been removed</summary>

In order to simplify `Marker`s, the `anchor` property and `AnchorPos`/`Anchor` objects have been removed without replacement.

Marker alignment is now performed with the standard `Alignment` object through the `alignment` argument.

Due to the previously named `anchor` being confusingly (and perhaps incorrectly) named, migration without behaviour change is possible just by taking the `Alignment` from inside any `AnchorPos` and passing it directly to `alignment`.

</details>

<details>

<summary><code>rotateOrigin</code> and <code>rotateAligment</code> have been removed</summary>

These properties on `Marker` have been removed as it is not apparent what any valid use-case could be, and removing them helped simplify the internals significantly.

If these are currently used, try changing `alignment`, and if that does not give the desired results, use a `Transform` widget yourself.

</details>

<details>

<summary><code>Marker.builder</code> has been replaced by <code>child</code></summary>

This has been done since using the builder pattern offered no significant advantage.

To migrate, simply change to using the `child` parameter, and use as you would any other widget. If a builder is required, for example to access the inherited states ([Controllers & Cameras](/v7/usage/programmatic-interaction/controllers-and-cameras#accessing-aspects-within-descendants)), use the `Builder` widget yourself.

</details>

### Map Options

<details>

<summary><code>center</code>, <code>bounds</code>, <code>zoom</code>, and <code>rotation</code> have been replaced with <code>initialCenter</code>, <code>initialCameraFit</code>, <code>initialZoom</code>, and <code>initialRotation</code></summary>

These have been renamed for clarity, as well as to better fit the change into using a documented 'camera' and increasing customizability.

To migrate, rename the properties, and also check the in-code documentation and new objects for information.

</details>

<details>

<summary><code>maxBounds</code> has been replaced with <code>cameraConstraint</code></summary>

This is part of to better fit the change into using a documented 'camera' and increasing customizability.

To migrate, rename the properties, and also check the in-code documentation and new objects for information.

</details>

<details>

<summary>Interactive options (such as <code>interactiveFlags</code>) have been moved into <code>InteractionOptions</code></summary>

This has been done to improve readability and seperation of responsibilities.

For more information, see [Interaction Options](/v6/usage/options/interaction-options).

</details>

### Tile Providers

<details>

<summary>Implementations should switch to extensions</summary>

It is not recommended to implement `TileProvider`, as there are now two methods of which only one should be implemented (`getImage` & `getImageWithCancelLoadingSupport`), as well as other members that should not usually be overridden.

To migrate, use `extends` instead of `implements`.

*Further panes will refer to implementations that use `extends` as 'extensions' for clarity, not to be confused with extension methods.*

</details>

<details>

<summary>Extensions should not provide a constant default value for <code>headers</code> in the constructor</summary>

`TileLayer` behaviour has been modified so that the 'User-Agent' header can be set without copying all user-specified `headers`. It is now inserted into the `Map`, so it must be immutable/non-constant.

Note that the `headers` property is also now `final`.

To migrate, remove the default value for `super.headers`: it is not necessary.

</details>

<details>

<summary>Extensions overriding <code>getTileUrl</code> should consider overriding other methods instead</summary>

The logic previously handled by `getTileUrl`, `invertY`, and `getSubdomain` has been refactored into `generateReplacementMap`, `populateTemplatePlaceholders`, and `getTileUrl`.

To migrate, consider overriding another of those methods, if it is more suitable. This will reduce the amount of code duplicated in your library from flutter\_map's implementation.

</details>

<details>

<summary>Extensions implementing <code>getImage</code> should consider overriding <code>getImageWithCancelLoadingSupport</code> instead </summary>

The framework necessary to support tile providers that can abort in-flight HTTP requests and other processing is now available. For more information about the advantages of cancelling unnecessary tile requests when they are pruned before being fully loaded, see [Tile Providers](/v6/layers/tile-layer/tile-providers#cancellablenetworktileprovider).

If it is not possible to cancel the loading of a tile, or there is no advantage gained by doing so, you can ignore this.

To migrate, override `supportsCancelLoading` to `true`, implement `getImageWithCancelLoadingSupport` as appropriate, and remove the implementation of `getImage`.

</details>


# Base Widget

```dart
FlutterMap(
    mapController: MapController(),
    options: MapOptions(),
    children: [],
);
```

Start by adding some [Layers](/v6/layers/tile-layer) to `children`, then configure the map in [Options](/v6/usage/options). Additionally, if required, add a `MapController`: [Control Camera](/v6/usage/programmatic-control/controller).

## Placement Recommendations

It is recommended to make the map as large as possible, to allow it to display a lot of useful information easily.

As such, we recommend using a depth-based layout (eg. using `Stack`s) instead of a flat-based layout (eg. using `Column`s). The following 3rd party packages might help with creating a modern design:

* <https://pub.dev/packages/backdrop>
* <https://pub.dev/packages/sliding_up_panel>
* <https://pub.dev/packages/material_floating_search_bar_2>

If you must restrict the widget's size, you won't find a `height` or `width` property. Instead, use a `SizedBox` or `Column`/`Row` & `Expanded`.

{% hint style="info" %}
The map widget will expand as much as possible.

To avoid errors about infinite/unspecified dimensions, ensure the map is contained within a constrained widget.
{% endhint %}

### Keep Alive

If the map is displayed lazily in something like a `PageView`, changing the page and unloading the map will cause it to reset to its [initial positioning](/v6/usage/options#initial-positioning).

To prevent this, set `MapOptions.keepAlive` `true`, which will activate an internal `AutomaticKeepAliveClientMixin`. This will retain the internal state container in memory, even when it would otherwise be disposed.


# Options

To dictate & restrict what the map can and should do, regardless of its contents, it needs some guidance!

It provides options that can be categorized into three main parts:

* [Initial positioning](#initial-positioning)\
  Defines the location of the map when it is first loaded
* [Permanent rules](#permanent-rules)\
  Defines restrictions that last throughout the map's lifetime
* [Event handling](/v6/usage/programmatic-control/listen-to-events)\
  Defines methods that are called on specific map events

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/MapOptions-class.html>" %}

## Initial Positioning

{% hint style="info" %}
Changing these properties after the map has already been built for the first time will have no effect: they only apply on initialisation.

To control the map programatically, use a `MapController`: [Control Camera](/v6/usage/programmatic-control/controller).
{% endhint %}

One part of `MapOptions` responsibilities is to define how the map should be positioned when first loaded. There's two ways to do this (that are incompatible):

* `initialCenter` (`LatLng`) & `initialZoom`
* `initialCameraFit`
  * by bounds (circumscribed[^1]): `CameraFit.bounds`
  * by bounds (inscribed[^2]): `CameraFit.insideBounds`
  * by coordinates (circumscribed[^3]): `CameraFit.coordinates`

It is possible to also set the map's `initialRotation` in degrees, if you don't want it North (0°) facing initially.

If rotation is enabled/allowed, if using `initialCameraFit`, prefer defining it by coordinates for a more intended/tight fit.

## Permanent Rules

One part of `MapOptions` responsibilities is to define the restrictions and limitations of the map and what users can/cannot do with it.

Some of the options are described elsewhere in this documentation, in context. In addition, the API docs show all the available options, and below is a partial list of options:

* `cameraConstraint`
  * camera bounds inside bounds: `CameraConstraint.bounds`
  * camera center inside bounds: `CameraConstraint.center`
  * *unconstrained (default): `CameraConstraint.unconstrained`*
* `maxZoom` and `minZoom`\
  Sets a hard limit on the maximum and minimum amounts that the map can be zoomed
* [`interactionOptions`](/v6/usage/options/interaction-options)\
  Configures the gestures that the user can use to interact with the map - for example, disable rotation or configure cursor/keyboard rotation

{% hint style="success" %}
Instead of `maxZoom` (or in addition to), consider setting `maxNativeZoom` per `TileLayer` instead, to allow tiles to scale (and lose quality) on the final zoom level, instead of setting a hard limit.
{% endhint %}

[^1]: Bounds inside camera

[^2]: Camera inside bounds

[^3]: Coordinates inside camera, as tightly as possible


# Interaction Options

The `InteractionOptions` object passed to `MapOptions.interactiveOptions` configures the gestures that the user can use to interact with the map. For example, disable rotation or configure cursor/keyboard rotation.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/InteractionOptions-class.html>" %}

## Flags

`flags` is a [bitfield](https://en.wikipedia.org/wiki/Bit_field) that enables and disables the vast majority of gestures. Although technically the type is of `int`, it is usually set with a combination of `InteractiveFlag`s.

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/InteractiveFlag-class.html>" %}

{% hint style="warning" %}
Note that some gestures must be configured by other means, either instead of using flags, or in addition to.
{% endhint %}

By default, `all` gestures are enabled, but a non-interactive map can be created using `none` (and other options in addition).

{% hint style="info" %}
The recommended way to create an entirely non-interactive map is to wrap the `FlutterMap` widget in an `IgnorePointer` widget.
{% endhint %}

Otherwise, to set flags, there's two methods:

* Add flags, with the bitwise 'OR' (`|`) operator in-between\
  For example, `InteractiveFlag.drag | InteractiveFlag.rotate`
* Remove flags from `all`, using the `&` and `~` operators in-between\
  For example, `InteractiveFlag.all & ~InteractiveFlag.rotate`

## Cursor/Keyboard Rotation

Cursor/keyboard rotation is designed for desktop platforms, and allows the cursor to be used to set the rotation of the map whilst a (customizable) keyboard key (by default, any of the 'Control' keys) is held down.

The `CursorKeyboardRotationOptions` object passed to the property with the corresponding name configures this behaviour. The `CursorKeyboardRotationOptions.disabled()` constructor can be used to disable cursor/keyboard rotation.

There's many customization options, see the API docs for more information:

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/CursorKeyboardRotationOptions-class.html>" %}

## "Win" Gestures

{% hint style="warning" %}
This is advanced behaviour that affects how gestures 'win' in the gesture arena, and does not usually need changing.
{% endhint %}


# Layers

To display anything on the map, you'll need to include at least one layer. This is usually a [`TileLayer`](/v6/layers/tile-layer), which displays the map tiles themselves: without it, the map isn't really a very good map!

<div align="center" data-full-width="false"><figure><img src="/files/4TSKf8wAVxm2hyeUMMom" alt="Example FlutterMap widget, containing multiple feature layers, atop a TileLayer" width="563"><figcaption><p>Example <code>FlutterMap</code> widget, containing multiple feature layers, atop a <code>TileLayer</code></p></figcaption></figure></div>

To insert a layer, add it to the `children` property. Other layers (sometimes referred to as 'feature layers', as they are map features) can then be stacked on top, where the last widget in the `children` list is topmost. For example, you might display a [`MarkerLayer`](/v6/layers/marker-layer), or any widget as your own custom layer ([Creating New Layers](/v6/plugins/making-a-plugin/creating-new-layers))!

{% hint style="info" %}
It is possible to add more than one `TileLayer`! Transparency in one layer will reveal the layers underneath.
{% endhint %}

{% hint style="info" %}
To display a widget in a sized and positioned box, similar to [Overlay Image Layer](/v6/layers/overlay-image-layer), try the community maintained [flutter\_map\_polywidget plugin](https://github.com/TimBaumgart/flutter_map_polywidget)!
{% endhint %}

Each layer is isolated from the other layers, and so handles its own independent logic and handling. However, they can access and modify the internal state of the map, as well as respond to changes.

## Per-Layer Gesture Handling

By default, each layer acts translucently to gestures, meaning they can handle gestures themselves, but they also allow gestures to bubble down to other layers beneath them.

This behaviour can be disabled in the [`MapOptions`](/v6/usage/options), by setting `.applyPointerTranslucencyToLayers` false. Optionally, then, the `TranslucentPointer` widget can be wrapped around individal layers to achieve the desired effect.

## Mobile vs Static Layers

Most layers are 'mobile', such as the `TileLayer`. These use a `MobileLayerTransformer` widget internally, which enables the layer to properly move and rotate with the map's current camera.

However, some layers are 'static', such as the [`AttributionLayer`](/v6/layers/attribution-layer)s. These aren't designed to move nor rotate with the map, and usually make use of a widget like `Align` and/or `SizedBox.expand` to achieve this.

Both of these layer types are defined in the same `children` list. Most of the time, static layers go atop mobile layers, so should be at the end of the list.


# Programmatic Control

There's two ways to interact with the map - that is to control it, as well as receive data from it - and it's current viewport, aka. 'camera'.

## via User Gestures

The first way is through user interaction, where they perform gestures (such as drags/pans), and the map reacts automatically to those gestures to change the camera view of the map.

These are usually restricted by [Options](/v6/usage/options). It is possible to disable all input, either by disabling all gestures, or by wrapping the map with something like `IgnorePointer`.

## via Programmatic Means

When using programmatic means, there's two methods to most things, dependent on whether the context is within a `FlutterMap` (ie. usually a layer) or not.

If within `FlutterMap`'s context, the methods usually cause automatic rebuilding. As well as the pages below, also see [/pages/PHCyJci0lnfcS8LgyvNw#2.-hooking-into-inherited-state](https://docs.fleaflet.dev/v6/usage/pages/PHCyJci0lnfcS8LgyvNw#2.-hooking-into-inherited-state "mention").

{% content-ref url="/pages/UW2gppPcXFfE46FRhWT6" %}
[Control Camera](/v6/usage/programmatic-control/controller)
{% endcontent-ref %}

{% content-ref url="/pages/1cm3wsG3oD7AXsU6fMJk" %}
[Get Camera](/v6/usage/programmatic-control/get-camera)
{% endcontent-ref %}

{% content-ref url="/pages/sl4eSFK5ZvCyp3fur4r8" %}
[Listen To Events](/v6/usage/programmatic-control/listen-to-events)
{% endcontent-ref %}


# Control Camera

To control the map (such as moving it to a new position and zoom level), you'll need a `MapController`. The controller does not provide access to the current viewport/camera: that is the responsibility of [`MapCamera`](/v6/usage/programmatic-control/get-camera).

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/MapController-class.html>" %}

## Usage Inside Of A `FlutterMap` Child

To control the map from within the context of a `FlutterMap` widget, use `MapController.of(context)`.

{% hint style="info" %}
Calling this method in a `build` method will cause the widget to automatically rebuild if the `MapController` changes. See [/pages/PHCyJci0lnfcS8LgyvNw#2.-hooking-into-inherited-state](https://docs.fleaflet.dev/v6/usage/programmatic-control/pages/PHCyJci0lnfcS8LgyvNw#2.-hooking-into-inherited-state "mention") for more information.
{% endhint %}

If this throws a `StateError`, try wrapping the concerned widget in a `Builder`, to ensure the `FlutterMap` widget is parenting the `BuildContext`. If this has no effect, use [#usage-outside-of-fluttermap](#usage-outside-of-fluttermap "mention") instead.

## Usage Outside Of `FlutterMap`

### Initialisation

To use a `MapController`, it must initialised like any other object and then passed to the `FlutterMap`. This attaches them until the map is disposed.

```dart
final mapController = MapController();

@override
Widget build(BuildContext context) =>
    FlutterMap(
        mapController: mapController,
        // ...
    );
```

### Usage Inside `initState()`

Sometimes, it is necessary `MapController` in `initState()` before the map has been built, for example to attach an event listener ([Listen To Events](/v6/usage/programmatic-control/listen-to-events)). This is not directly possible, as the map must be built for the controller to be attached.

Instead, use the `MapOptions.onMapReady` callback. The initialised `MapController` can be used freely within it.

```dart
final mapController = MapController();

@override
Widget build(BuildContext context) =>
    FlutterMap(
        mapController: mapController,
        options: MapOptions(
            onMapReady: () {
                mapController.mapEventStream.listen((evt) {});
                // And any other `MapController` dependent non-movement methods
            },
        ),
    );
```

{% hint style="warning" %}
`MapController` methods that change the position of the map should not be used instantly in `onMapReady` - see [issue #1507](https://github.com/fleaflet/flutter_map/issues/1507).

Using them as a reaction to a map event is still fine.
{% endhint %}

## Animated Movements

Whilst animated movements through `MapController`s aren't built-in, the [community maintained plugin `flutter_map_animations`](https://github.com/TesteurManiak/flutter_map_animations) provides this, and much more!

The example application also includes a page demonstrating a custom animated map movement without the plugin.


# Get Camera

The `MapCamera` object describes the map's current viewport. It does not provide methods to change it: that is the responsibility of a [`MapController`](/v6/usage/programmatic-control/controller).

{% embed url="<https://pub.dev/documentation/flutter_map/latest/flutter_map/MapCamera-class.html>" %}

{% hint style="info" %}
The `MapCamera` object also provides access to some other helpful methods that depend on it, such as `pointToLatLng` & `latLngToPoint`.
{% endhint %}

## Usage Inside Of A `FlutterMap` Child

To get the camera from within the context of a `FlutterMap` widget, use `MapCamera.of(context)`.

{% hint style="info" %}
Calling this method in a `build` method will cause the widget to automatically rebuild when the `MapCamera` changes. See [/pages/PHCyJci0lnfcS8LgyvNw#2.-hooking-into-inherited-state](https://docs.fleaflet.dev/v6/usage/programmatic-control/pages/PHCyJci0lnfcS8LgyvNw#2.-hooking-into-inherited-state "mention") for more information.

If this behaviour is unwanted, use [#single-time](#single-time "mention") instead.
{% endhint %}

If this throws a `StateError`, try wrapping the concerned widget in a `Builder`, to ensure the `FlutterMap` widget is parenting the `BuildContext`. If this has no effect, use [#usage-outside-of-fluttermap](#usage-outside-of-fluttermap "mention") instead.

## Usage Outside Of `FlutterMap`

### Single Time

To get the camera from outside the context of the `FlutterMap` widget, you'll need to setup a `MapController` first: see [Control Camera](/v6/usage/programmatic-control/controller) > [Control Camera](/v6/usage/programmatic-control/controller#usage-outside-of-fluttermap).

Then, use the `.camera` getter.

{% hint style="warning" %}
Avoid using `MapController.of(context).camera` from within the context of `FlutterMap`, as it is redundant and less performant than using `MapCamera.of(context)` directly.
{% endhint %}

### Listen To Changes

{% content-ref url="/pages/sl4eSFK5ZvCyp3fur4r8" %}
[Listen To Events](/v6/usage/programmatic-control/listen-to-events)
{% endcontent-ref %}




---

[Next Page](/llms-full.txt/1)

