arrow-left

Only this pageAll pages
gitbookPowered by GitBook
1 of 49

v8

Loading...

Why & How

Loading...

Loading...

Loading...

Loading...

Getting Started

Loading...

Loading...

Usage

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Layers

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Tile Servers

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Plugins

Loading...

Loading...

Loading...

Loading...

Loading...

Thanks

Loading...

Loading...

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

circle-check

hashtag
Welcome to v8 🎉

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

  • If you're upgrading, find out

  • If you're new here, welcome! Feel free to look around and experiment, and check out when you're ready.

  • If you're looking for documentation for an older version, use the version selector. documentation is still available.

hashtag
Why choose flutter_map?

circle-info

hashtag
Vector tile support

If you're looking for , we don't currently support them natively. We only support raster tiles at the moment.

However, , and the we and the community are actively exploring & developing future support!

hashtag
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!

circle-exclamation

This map uses the OpenStreetMap public tile servers, which are NOT free to use by everyone.

hashtag
Get Help

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

  • Check the below, and double check the documentation

  • Then, for bug reports & feature requests: check for previous issues, then ask in

  • Then, for support & everything else: ask in or

circle-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: .

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: .

hashtag
FAQs

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

chevron-rightHow can I use a custom map style? How can I prevent POI/labels rotating when the map rotates? How can I remove certain POI/labels from the map?hashtag

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:

chevron-rightHow can I route a user between two locations? Why does the Polyline only go in a straight line between two points?hashtag

See .

chevron-rightHow can I add a Marker where the user's location is? How can I center the map on the user's location?hashtag

This is beyond the scope of flutter_map. However, you can use the 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.

chevron-rightWhy don't any map tiles appear?hashtag

Check the debugging steps in .

chevron-rightWhy are the layers glitching on some platforms?hashtag

Check the debugging steps in .

chevron-rightWhy does the map disappear/go grey when I zoom in far? Why does the map stop zooming in even though I know there are more zoom levels?hashtag

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).

chevron-rightHow can I make the map 3D, or view it as a globe?hashtag

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!

Showcase & Case Studies

We think flutter_map hits the spot for many Flutter apps: 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!

hashtag
Community Studies

hashtag
Project Showcase

These great projects all make use of flutter_map!

But there's more: check out the , and the #showcase channel for more projects on our Discord server.

hashtag
Non-OSS Sponsors

hashtag
OSS and/or Non-Profit Projects

  • You may wish to use a commercial service like Mapbox Studio, which allows you to style multiple tilesets. See 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 .

  • Your last option is to serve tiles yourself. See Other Options.

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.

arrow-up-right arrow-up-right arrow-up-right arrow-up-right

🗺️ No more vendor lock-in: better flexibility, quality, and price

We natively support any static raster* tiles! Bring your own tiles 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.

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*.

🚀 Stress-free setup & easy-to-use

Migrating from a commercial library (such as Google Maps) has never been easier. No more complex platform-specific setup, no more buggy & limited platform views (we're 100% pure-Flutter), and no more lacking-documentation & 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. Check out our to see just how simple it is.

🧩 Customize and expand endlessly

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. A huge community of developers maintain an ecosystem of plugins for you to supercharge flutter_map with.

👋 But don't just take it from us...

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. Check out some independent reviews, comparisons, and studies, and see who's using flutter_map right now:

What's New In v8.2?
Installation
v7
🌟Showcase & Case Studieschevron-right
vector tiles
options are available
GitHub Issuesarrow-up-right
flutter_map Discord serverarrow-up-right
GitHub Discussionsarrow-up-right
https://github.com/fleaflet/flutter_map/blob/master/CONTRIBUTING.mdarrow-up-right
https://github.com/fleaflet/flutter_map/blob/master/CODE_OF_CONDUCT.mdarrow-up-right
❔How Does It Work?chevron-right
community maintained plugin 'flutter_map_location_marker'arrow-up-right
FAQs
dependents list on GitHubarrow-up-right

Map Marker

Your places organizer

Your advert here!

Want to advertise your project here? For more information, and to apply, please see:

  • (it's free!)

Every Door

Open Source100% Non-Profit

The most efficient OpenStreetMap editor for surveying shops and benches

Ente Photos

Open Source

End-to-end encrypted alternative to Google Photos

🇳🇱 Stichting Zeilvaart Warmond (Track & Trace)

100% Non-Profit

Follow participating ships during the race, and use the replay function after the race to analyse the performance

Your advert here!

Want to advertise your project here? For more information, and to apply, please see:

  • (it's free!)

Using Thunderforest

circle-info

'flutter_map' is in no way associated or related with Thunderforest (or Gravitystorm Limited).

Thunderstorm's home page: Thunderstorm's pricing page: Thunderstorm's documentation page:

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.

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...
        ],
      ),
    ],
  );
}
Showcase & Case Studies
Code Demo
Vector Tiles
The application form for non-OSS projectsarrow-up-right
The application form for OSS and/or non-profit projectsarrow-up-right
The application form for non-OSS projectsarrow-up-right
The application form for OSS and/or non-profit projectsarrow-up-right
thunderforest.comarrow-up-right
thunderforest.com/pricingarrow-up-right
thunderforest.com/docs/map-tiles-apiarrow-up-right

Base Widget

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

Start by adding some Layers to children, then configure the map in Options. Additionally, if required, add a MapController: .

circle-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.

hashtag
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 .

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.

Using Google Maps

circle-info

'flutter_map' is in no way associated or related with Google Maps

Google Maps' home page: https://developers.google.com/mapsarrow-up-right Google Maps' pricing page: https://mapsplatform.google.com/pricing/arrow-up-right Google Maps' documentation page: https://developers.google.com/maps/documentation/tile/2d-tiles-overviewarrow-up-right

circle-check

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).

circle-exclamation

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.

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:

Overlay Image Layer

You can overlay images on the map (for example, town or floor plans) using OverlayImageLayer and OverlayImages or RotatedOverlayImages.

Example RotatedOverlayImage
OverlayImageLayer(
  overlayImages: [
    OverlayImage( // Unrotated
      bounds: LatLngBounds(
        LatLng(45.3367881884556, 14.159452282322459),
        LatLng(45.264129635422826, 14.252585831779033),
      ),
      imageProvider: NetworkImage(),
    ),
  ],
),
circle-exclamation

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.

hashtag
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 .

Raster vs Vector Tiles

circle-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.

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.

hashtag
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.

hashtag
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.

hashtag
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 to do this.

circle-exclamation

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!

Using Lima Labs

circle-info

'flutter_map' is in no way associated or related with Lima Labs.

Lima Labs' home page: maps.lima-labs.comarrow-up-right Lima Labs' pricing page: maps.lima-labs.com/index.php/pricingarrow-up-right

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 (___) with your API key

Using Tracestrack

circle-info

'flutter_map' is in no way associated or related with Tracestrack.

Tracestrack's home page:

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!

Using Stadia Maps

circle-info

'flutter_map' is in no way associated or related with Stadia Maps.

Stadia Maps' home page: Stadia Maps' pricing page: Stadia Maps' documentation page:

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.

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 hitTests in those layers, with the exception of custom defined hit test behaviours (not those layers that support interactivity, see ), such as applying GestureDetectors around Marker.children

circle-exclamation

Supporters

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.

hashtag
Past Supporters

Caching Providers

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.

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

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


Other Options

hashtag
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:

initial positioning
Keep up to date and subscribe to the issue: https://github.com/greensopinion/flutter-vector-map-tiles/issues/120arrow-up-right.
community maintained plugin (vector_map_tiles)arrow-up-right
https://cdn.lima-labs.com/0/0/0.png?api=arrow-up-right

Use https://console.tracestrack.com/explorerarrow-up-right to find your ideal style

  • Create an account

  • Copy the URL template from the result box at the bottom and paste it into your TileLayer

  • tracestrack.comarrow-up-right
    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.
    circle-exclamation

    Attribution is required, see docs.stadiamaps.com/#legal-details-required-attributionarrow-up-right.

    Consider using the RichAttributionWidget or SimpleAttributionWidgets, which meet the requirements.

    hashtag
    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 stylesarrow-up-right. The URL should be used as above.

    hashtag
    Vector Usage

    Stadia Maps' also provides vector tiles. For more information about using vector tiles, please see Using Vector Tiles.

    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.

    stadiamaps.comarrow-up-right
    stadiamaps.com/pricingarrow-up-right
    docs.stadiamaps.comarrow-up-right

    GestureDetectors 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.

    1. Hit testing is always* performed on interactive-capable layers (see Layer Interactivity) even if they have not been set-up for interactivity: hit testing != interactivity

    2. Non-interactable layers (such as Overlay Image Layer) have no defined hitTest, and behaviour is situation dependent

    3. 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:

    • IgnorePointerarrow-up-right

    • AbsorbPointerarrow-up-right

    • 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.

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

    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.

    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();
      }
    }
    Caching
    class CustomCachingProvider
        with DisabledMapCachingProvider
        implements MapCachingProvider {}
    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

    hashtag
    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.

    https://stackoverflow.com/a/78064659/11846040arrow-up-right
    GitHub source
    pub.dev package
    Join our Discord
    Support us
    Platform Configuration
    Platform Configuration

    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.

    hashtag
    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.

    chevron-rightDisabling unbounded horizontal scrollinghashtag

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

    hashtag
    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:

    hashtag
    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.

    Circle Layer

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

    An example CircleMarker
    CircleLayer(
      circles: [
        CircleMarker(
          point: LatLng(51.50739215592943, -0.127709825533512),
          radius: 10000,
          useRadiusInMeter: true,
        ),
      ],
    ),
    circle-exclamation

    Excessive use of circles may create performance issues.

    hashtag
    Interactivity

    CircleLayers and CircleMarkers support hit detection and interactivity.

    How Does It Work?

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

    circle-check

    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 and other map features - not a map itself.

    1

    hashtag
    👀 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.

    There's loads of ways to source (see Tile Servers), store (), and reference (eg. XYZ vs WMS) tiles! We support most of them,

    circle-info

    Most map client libraries will work in a similar way, so if you've used or before, you'll be right at home 😄.

    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.

    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!

    circle-check

    For a quick code demo, check out the landing page: Code Demo!

    hashtag
    Web Demo

    circle-info

    Note that the web demo is built automatically from the , so may not reflect the the latest release on pub.dev.

    circle-exclamation

    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 , and serve the application yourself.

    hashtag
    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 for this purpose.

    circle-info

    Note that these artifacts are built automatically from the , so may not reflect the the latest release on pub.dev.

    hashtag
    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.

    Layers

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

    Example FlutterMap widget, containing multiple feature layers, atop a TileLayer

    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, or any widget as your own custom layer (Layers)!

    circle-info

    It is possible to add more than one TileLayer! Transparency in one layer will reveal the layers underneath.

    circle-info

    To display a widget in a sized and positioned box, similar to , try the community maintained !

    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.

    hashtag
    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 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.

    hashtag
    Layers With Elements: "Feature Layers"

    Some layers - such as PolygonLayer - take 'elements' - such as Polygons - 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).

    circle-info

    Since v7, it has not been possible to add elements to layers in an imperative style.

    Flutter is a 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.

    hashtag
    Hit Testing & Interactivity

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

    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 doesn't suit your needs, then you can create your own, to achieve maximum customizability.

    hashtag
    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. 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

    • preferably includes the #flutter-map pub.dev topic

    • is available via a standard pub.dev package installation

    Tile Providers

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

    Tile providers may support compatible caching providers (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.

    hashtag
    NetworkTileProvider

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

    circle-exclamation

    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.

    For more information, check the API documentation.

    hashtag
    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.

    circle-exclamation

    Specifying any fallbackUrl (even if it is not used) in the TileLayer will reduce the performance of these providers.

    It will cause with AssetTileProvider, and will cause main thread blocking when requesting tiles from FileTileProvider.

    hashtag
    AssetTileProvider

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

    circle-info

    Asset management in Flutter leaves a lot to be desired! Unfortunately, every single sub-directory (to the level of tiles) must be listed.

    hashtag
    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.

    circle-exclamation

    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 or a custom tile provider.

    hashtag
    Offline Mapping

    Marker Layer

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

    circle-check

    No more image only markers! Unlike other arrow-up-right😉 popular mapping librariesarrow-up-right, we allow usage of any widget as the marker.

    circle-exclamation

    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: .

    hashtag
    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 Markers within a MarkerLayer can be set by changing the same property on the MarkerLayer.

    hashtag
    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 Markers within a MarkerLayer can be set by changing the same property on the MarkerLayer.

    circle-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.

    hashtag
    Handling Gestures

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

    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.

    hashtag
    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 Mobile vs Static Layers.

    hashtag
    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.

    circle-exclamation

    Using these methods will restrict this widget to only being usable inside the context of a FlutterMap.

    Cover
    Cover
    Cover

    Installation

    hashtag
    Depend On It

    Depend on flutter_map from as normal! Use the command line or add the dependency manually to your pubspec.yaml.

    chevron-rightDepend from GitHubhashtag

    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.

    circle-exclamation

    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.

    Credits & Contributing

    hashtag
    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:

    includes good documentation (at least to setup basic functionality), and a code example

    23% slower asset tile requestsarrow-up-right
    NetworkTileProvider
    Offline Mappingchevron-right
    except vector tiles
    . This documentation primarily uses examples referring to
    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 Layerfor more information.

    2

    hashtag
    🤩 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 catalogue, make your own layers directly in Flutter, or use one of the excellent community-maintained plugins!

    3

    hashtag
    🛠️ 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 for more info.

    4

    hashtag
    🎮 Control The Map

    You can also control the map programmatically using a simple controller pattern. See Programmatic Interaction for more info.

    'tiled & WMS web' mapsarrow-up-right
    Raster vs Vector Tiles
    leaflet.jsarrow-up-right
    OpenLayersarrow-up-right
    flutter_mapchevron-right
    👀Demo & Exampleschevron-right
    https://commons.wikimedia.org/wiki/File:XYZ_Tiles.pngarrow-up-right
    Slippy Mapsarrow-up-right
    Cover
    Cover
    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/arrow-up-right
    Original: https://www.theseus.fi/bitstream/handle/10024/820026/Ushakov_Sergey.pdfarrow-up-right Archive: https://archive.org/details/incorporating-maps-into-flutter-a-study-of-mapping-sdks-and-their-integration-inarrow-up-right
    Cover
    drawInSingleWorld: false (default)
    drawInSingleWorld: true
    class CustomMobileLayer extends StatelessWidget {
      const CustomMobileLayer({super.key});
    
      @override
      Widget build(BuildContext context) {    
        return MobileLayerTransformer(
          child: // your child here
        );
      }
    }
    class CustomStaticLayer extends StatelessWidget {
      const CustomStaticLayer({super.key});
    
      @override
      Widget build(BuildContext context) {
        return SizedBox.expand();
        // and/or
        return Align();
      }
    }
    class Epsg3857NoRepeat extends Epsg3857 {
      const Epsg3857NoRepeat();
    
      @override
      bool get replicatesWorldLongitude => false;
    }
    FlutterMap(
        options: MapOptions(
          cameraConstraint: const CameraConstraint.containLatitude(),
        ),
        children: [],
    ),
    final camera = MapCamera.of(context);
    final controller = MapController.of(context);
    final options = MapOptions.of(context);
    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.
    Overlay Image Layer
    flutter_map_polywidget pluginarrow-up-right
    AttributionLayer
    declarative UIarrow-up-right
    Layer Interactivitychevron-right
    circle-exclamation

    Unreleased commits from the source repo may be unstable.

    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:

    hashtag
    Platform Configuration

    circle-check

    Most apps that already communicate over the Internet won't need to change their configuration.

    Add the following line to android\app\src\main\AndroidManifest.xml to enable the INTERNET permission in release builds.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
    

    hashtag
    Wasm/Renderer

    circle-check

    We support Wasm! Build and run your app with the '-wasm' flag and benefit from potentially improved performance when the browser can handle Wasm.

    hashtag
    CORS

    On the web platform, 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 for more details. We load images using a standard Image widget.

    Add the following lines to macos/Runner/Release.entitlements:

    circle-info

    hashtag
    Having issues loading tiles?

    1. Check you've correctly configured your TileLayer:

    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)

    circle-info

    hashtag
    Map looking wrong or layers glitching?

    If you're testing on a platform which is using Impellerarrow-up-right, 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.

    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 documentation.

    pub.devarrow-up-right
    pubspec.yaml
    dependency_overrides:
        flutter_map:
    

    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 herearrow-up-right. Other servers may have different terms.

    circle-check

    Please consider crediting flutter_map. It helps us to gain more awareness, which helps make this project better for everyone!

    hashtag
    RichAttributionWidget

    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.

    Closed RichAttributionWidget
    Opened RichAttributionWidget, as in the example app

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

    hashtag
    SimpleAttributionWidget

    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.

    SimpleAttributionWidget, as in the example app
    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 Listarrow-up-right

  • Anyone who has made plugins for flutter_map: Plugins List

  • Anyone who has donated to flutter_map: Past Supporters

  • hashtag
    Contributing

    hashtag
    Apply To Be A Maintainer

    A variety of Markers on a rotated map
    Layer Interactivitychevron-right
    'master' brancharrow-up-right
    dhttpdarrow-up-right
    'master' brancharrow-up-right
    Prebuilt Artifacts

    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 Defines the location of the map when it is first loaded

    • Permanent rules Defines restrictions that last throughout the map's lifetime

    • Defines methods that are called on specific map events

    hashtag
    Initial Positioning

    circle-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: .

    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

    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.

    hashtag
    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:

    circle-check

    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.

    hashtag
    Custom CRS

    FM does have some support for using alternative CRSs.

    Custom CRSs

    hashtag
    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:

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

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

    For more possible ways to register proj4.Projection see .

    hashtag
    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

    An example:

    hashtag
    Usage

    Proj4Crs has multiple uses:

    • Set FlutterMap's default CRS:

    • Set a WMS layer's CRS

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

    Using Bing Maps

    circle-info

    'flutter_map' is in no way associated or related with Bing Maps.

    Bing Maps' home page: microsoft.com/mapsarrow-up-right

    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.

    circle-info

    Thanks to for discovering the .

    circle-exclamation

    Attribution is not demonstrated here, but may be required. Ensure you comply with Bing Maps' ToS.

    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.

    circle-info

    Check the Plugins List for providers that already implement the behaviour you wish to replicate.

    hashtag
    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 constant default.

    circle-info

    If using an object that needs closing/cancelling, such as an HttpClient, override the dispose method.

    hashtag
    2. Setup Tile Retrieval

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

    circle-check

    It is best to put as much logic as possible into a custom ImageProvider, to avoid blocking the main thread.

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

    Prefer overriding getImageWithCancelLoadingSupport for TileProviders 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.

    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 .

    circle-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.

    Tile providers can support the MapCachingProvider contract/interface to support built-in caching. See .

    hashtag
    (Optionally) Override URL Generation

    Some custom TileProviders 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

    circle-exclamation

    Avoid overriding the generation method itself, as it is not usually necessary.

    Using Mapbox

    circle-info

    'flutter_map' is in no way associated or related with Mapbox.

    Mapbox's Maps home page: mapbox.com/mapsarrow-up-right Mapbox's Maps pricing page: mapbox.com/pricing#mapsarrow-up-right Mapbox's Maps documentation: docs.mapbox.com/api/maps/static-tilesarrow-up-right

    circle-exclamation

    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.

    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.

    hashtag
    Integration

    hashtag
    Custom Styles

    Mapbox supports creating and using custom styled maps through Studio.

    circle-exclamation

    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.

    1. Create a custom style using the editor

    2. Click "Share...", or the share icon

    3. Choose between Draft or Production

    hashtag
    Configuring

    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 for more information.

    hashtag
    Other configurations

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

    hashtag
    Adding Attribution

    circle-exclamation

    Attribution is required, and quite extensive compared to some alternatives: see .

    Consider using the , which meets the requirements by supporting both logo and text attribution.

    hashtag
    Prebuilt Styles

    Mapbox offers a variety of ready-made map styles that don't require customization. An example URL can be found in .

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

    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.

    hashtag
    Flags

    flags is a bitfieldarrow-up-right that enables and disables the vast majority of gestures. Although technically the type is of int, it is usually set with a combination of InteractiveFlags.

    circle-exclamation

    Note that some gestures must be configured by other means, either instead of using flags, or in addition to.

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

    circle-info

    The recommended way to create an entirely non-interactive map is to wrap the FlutterMap widget in an IgnorePointer widget.

    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

    For example, to disable flinging:

    hashtag
    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:

    hashtag
    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.

    hashtag
    "Win" Gestures

    circle-exclamation

    This is advanced behaviour that affects how gestures 'win' in the gesture arena, and does not usually need changing.

    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.

    • Automatically store tiles as the user loads them through interacting with the map, usually on a temporary basis

    • Download an entire area/region of tiles in one shot, ready for a known no-Internet situation

    flutter pub add flutter_map latlong2
    children: [
      RichAttributionWidget(
        attributions: [
          // Suggested attribution for the OpenStreetMap public tile server
          TextSourceAttribution(
            'OpenStreetMap contributors',
            onTap: () => launchUrl(Uri.parse('https://openstreetmap.org/copyright')),
          ),
        ],
      ),
    ],
    children: [
      SimpleAttributionWidget(
        source: Text('OpenStreetMap contributors'),
      ),
    ],
    MarkerLayer(
      markers: [
        Marker(
          point: LatLng(30, 40),
          width: 80,
          height: 80,
          child: FlutterLogo(),
        ),
      ],
    ),
    import 'package:proj4dart/proj4dart.dart' as proj4;
    git:
    url: https://github.com/fleaflet/flutter_map.git
    # ref: master (or commit hash, branch, or tag)
    ...
    <uses-permission android:name="android.permission.INTERNET"/>
    ...
    </manifest>
    CORS arrow-up-right
    Flutter documentationarrow-up-right
    Tile Layer
    Polygon Layer
    ...
    <dict>
        ...
        <key>com.apple.security.network.client</key>
        <true/>
        ...
    </dict>
    ...

    <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 TopLeftCorners for each TileMatrix of your TileMatrixSet.

  • <Transformation> transformation: the transformation to use when transforming projected coordinates into pixel coordinates

  • herearrow-up-right
    proj4dart documentationarrow-up-right
    Luka Glušicaarrow-up-right
    basic initial implementationarrow-up-right
    the generation method itself: getTileUrl and/or getTileFallbackUrl
    Caching Providers
    Tile Providers
    flutter run --no-enable-impeller
    +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
    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');
    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,
    );
      FlutterMap(
        options: MapOptions(
          // Set the default CRS
          crs: epsg3413CRS,
          center: LatLng(65.05166470332148, -19.171744826394896),
          zoom: 3.0,
          maxZoom: maxZoom,
        ),
        layers: [],
      );
      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'],
        ),
      );
    bing_maps.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,
            );
          },
        );
      }
    }
    class CustomTileProvider extends TileProvider {
        CustomTileProvider({
            // Suitably initialise your own custom properties
            super.headers, // Accept a `Map` of custom HTTP headers
        })
    }
        @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,
            );
        @override
        ImageProvider getImage(TileCoordinates coordinates, TileLayer options) =>
            CustomImageProvider(
                url: getTileUrl(coordinates, options),
                fallbackUrl: getTileFallbackUrl(coordinates, options),
                tileProvider: this,
            );
    by bounds (circumscribed): CameraFit.bounds
  • by bounds (inscribed): CameraFit.insideBounds

  • by coordinates (circumscribed): CameraFit.coordinates

  • 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 Configures the gestures that the user can use to interact with the map - for example, disable rotation or configure cursor/keyboard rotation

  • Event handling
    Custom CRSschevron-right
    Scroll to the bottom of the dialog, and choose Third Party
  • Select "CARTO" from the dropdown menu

  • Click the copy button to copy the template URL, then paste it into your TileLayer

  • Retina Mode
    docs.mapbox.com/help/getting-started/attributionarrow-up-right
    the example herearrow-up-right
    RichAttributionWidget
    )

    Bundling Provide a set of pre-determined tiles to all users through app assets or the filesystem

    hashtag
    Caching

    circle-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.

    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 ImageProviders (such as the NetworkTileProvider).

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

    Since v8.2.0, flutter_map provides 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 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_clientarrow-up-right', 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 with a caching ImageProvider (either custom or using a package such as '')

      • Use 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

    hashtag
    Bulk Downloading

    circle-exclamation

    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.

    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, 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'arrow-up-right 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:

    hashtag
    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.

    hashtag
    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:

    • MBTilesarrow-up-right: flutter_map_mbtilesarrow-up-right (vector_map_tiles_mbtiles arrow-up-rightwhen using vector tiles)

    • PMTilesarrow-up-right: flutter_map_pmtilesarrow-up-right (vector_map_tiles_pmtilesarrow-up-right when using vector tiles, also works in online contexts)

    hashtag
    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 TileProviders.

    hashtag
    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.

    circle-exclamation

    If using AssetTileProvider, every sub-directory of the tree must be listed seperately. See the example application's 'pubspec.yaml' for an example.

    hashtag
    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.

    Caching
    Bulk downloading

    What's New In v8.2?

    hashtag
    Overview

    Here's some highlights since v8.0:

    circle-check

    hashtag
    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 .

    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 () supports aborting requests with the 3 default clients natively, so Dio is no longer required. This makes it easier for you and for us!

    circle-check

    hashtag
    Inverted filling for PolygonLayer & multi-yet-single world support for Poly*Layer

    circle-check

    hashtag
    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.

    circle-check

    hashtag
    Polygon label placement improvements

    This is split into 3 parts:

    circle-check

    hashtag
    Documentation improvements

    This documentation has also had a bit of a renewal!

    circle-exclamation

    hashtag
    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

    That's already a lot, but it's only scratching the surface. Alongside the community, we've improved our example app, , added even more customizability and fine-grained control - 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:

    circle-info

    For completeness, here were the highlights from v8.0:

    • Unbounded horizontal scrolling

    • Keyboard controls for map gestures

    hashtag
    Migration

    hashtag
    To v8.2

    circle-check

    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

    chevron-rightChanges to Polygon label placementhashtag

    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

    chevron-rightDeprecation of official CancellableNetworkTileProvider pluginhashtag

    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.

    hashtag
    To v8.0

    circle-check

    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.

    chevron-rightMost uses of replaced by & , by , and by (IntegerBounds)hashtag

    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:

    chevron-rightTileLayer.tileSize replaced by tileDimensionhashtag

    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.

    Polyline Layer

    You can add lines formed from coordinates to maps using PolylineLayer and Polylines.

    A variety of Polylines, including gradients, mixed opacity and colors including borders, and dashes

    hashtag
    Pattern

    Polylines support a solid, dotted, and dashed style, through a StrokePatternpassed as an argument toPolyline.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.

    hashtag
    Interactivity

    PolylineLayers and Polylines support hit detection and interactivity.

    circle-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.

    hashtag
    Multi-Worlds

    The PolylineLayer paints its Polylines across all visible worlds by default. This can be changed.

    hashtag
    Performance Optimizations

    circle-check

    The example application includes a stress test which generates a Polyline with 200,000 points.

    hashtag
    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.

    circle-exclamation

    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.

    circle-exclamation

    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.

    hashtag
    Simplification

    To improve performance, polylines are 'simplified' before being culled and painted/rendered. The well-known 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.

    circle-exclamation

    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.

    circle-exclamation

    On layers with (many) only 'short' polylines (those with few points), disabling simplification may yield better performance.

    hashtag
    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 (which includes a public demo server) and . 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.

    hashtag
    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 , together with a custom method. You can use the code snippet below, which can just be pasted into a file and imported whenever needed:

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

    Routing/Navigation
    flutter_map Demodemo.fleaflet.devchevron-right

    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.

    circle-exclamation

    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 for information about implementing more appropriate solutions.

    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

    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 for more information.

    circle-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).

    hashtag
    Configuring the built-in provider

    circle-check

    Built-in caching is enabled by default on non-web platforms, using the BuiltInMapCachingProvider implementation.

    On web platforms, the browser usually performs caching automatically.

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

    circle-info

    It is not possible to change the configuration after the provider instance has been created (without first destroying 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.

    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:

    chevron-rightUsing a custom tileKeyGeneratorhashtag

    Where parts of the URL are volatile or do not represent the tile's contents/image - for example, API keys contained with the query 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

    hashtag
    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

    hashtag
    Using other providers

    You can also use any other MapCachingProvider implementation, such as provided by plugins, or ! 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.

    hashtag
    Disabling built-in caching

    circle-exclamation

    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 . Disabling it may make your project non-compliant.

    circle-info

    This is not necessary when running on the web.

    If you prefer to disable built-in caching, use the DisabledMapCachingProvider on each tile provider:

    flutter_map Demodemo.fleaflet.devchevron-right
    Unexpected error with integration github-files: Integration is not installed on this space
    CircleLayer class - flutter_map library - Dart APIpub.devchevron-right

    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.

    triangle-exclamation

    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.

    Marker Clustering
    flutter_map - Thunderforestwww.thunderforest.comchevron-right
    Visit our documentation over on Thunderforest's site (may be outdated)
    Providers - Switch2OSMswitch2osm.orgchevron-right
    Raster tile providers - OpenStreetMap Wikiwiki.openstreetmap.orgchevron-right
    BaseOverlayImage class - flutter_map library - Dart APIpub.devchevron-right
    Sponsor @fleaflet on GitHub SponsorsGitHubchevron-right
    options: MapOptions(
        interactionOptions: InteractionOptions(
            flags: ~InteractiveFlag.flingAnimation,
        ),
    ),
    PolylineLayer(
      polylines: [
        Polyline(
          points: [LatLng(30, 40), LatLng(20, 50), LatLng(25, 45)],
          color: Colors.blue,
        ),
      ],
    ),
    Example FlutterMap widget, containing multiple feature layers, atop a TileLayer

    Use flutter_map_tile_cachingarrow-up-right Supports very advanced caching use-cases and Bulk Downloading, but GPL licensed

    custom TileProvider
    package:cached_network_imagearrow-up-right
    flutter_map_cachearrow-up-right
    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, and the community! We hope to open more bounties in future.

    Check out the effect in our demo for multi-world functionalityarrow-up-right. You can enable it in your project just by passing CameraConstraint.containLatitude() to the MapOptions.cameraConstraint option.

    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.

    Follow the new guide to setup a TileLayer as we recommend: Recommended Setup. More to come soon!

  • The guide for interactive layers has been simplified, reworked, and example added. Check it out: Layer Interactivity.

  • We've added some information about using flutter_map with the OpenStreetMap public tile servers: Using OpenStreetMap (direct).

  • , 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).

    Performance improvements (particularly with Polygon/lineLayer)

    ), 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()

    circle-exclamation

    Note that only the simpleMultiWorldCentroid calculator supports polygons which may lie across the anti-meridian.

    These classes have been described as legacy since Feb 2024arrow-up-right in Dart/Flutter, and will be deprecated in futurearrow-up-right
  • This reduces internal casting (which we did a whole lot) and usage of generic types (which are inefficientarrow-up-right), 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 Rects from Offsets and Sizes

  • 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 PRarrow-up-right 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.

    Caching
    #1773arrow-up-right
    reduced the size of our demo & packagearrow-up-right
    Point<double>arrow-up-right
    Offsetarrow-up-right
    Sizearrow-up-right
    Bounds<double>arrow-up-right
    Rectarrow-up-right
    Bounds<int>arrow-up-right

    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

  • if they just wish to modify the input URL.

    Convenient methods to modify URLs can be found by first parsing it to a Uriarrow-up-right using Uri.parse, working on it (such as with replacearrow-up-right), 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.

    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.
    Offline Mapping
    create one yourself
    OpenStreetMap public tile server

    Use plugins at your own risk.

    circle-exclamation

    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.

    circle-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.

    hashtag
    Tools

    hashtag
    Additional Layers

    hashtag
    Offline Mapping

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

    hashtag
    Tile Container Parsers/Providers

    hashtag
    Better Markers

    hashtag
    Marker Clustering

    hashtag
    Better Polylines & Polygons

    hashtag
    Miscellaneous

    Offline Mappingchevron-right
    Layer Interactivitychevron-right
    Unbounded Horizontal Scrollingchevron-right
    Ramer–Douglas–Peucker algorithmarrow-up-right
    OSRMarrow-up-right
    Valhallaarrow-up-right
    'google_polyline_algorithm'arrow-up-right
    Illustration of the 5 types of PatternFit applied to a dashed Polyline From left to right: none, appendDot, extendFinalDash, scaleUp (default), scaleDown

    Layer Interactivity

    circle-info

    Layer interactivity is different to map interactivity. See Interaction Options to control map interactivity.

    The following layers support 'interactivity':

    • Polyline Layer


    • These layers don't provide their own 'gesture' callbacks, such as onTap

    • These layers automatically perform hit testing with Flutter APIs

    hashtag
    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 GestureDetectors (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:

    hashtag
    Identifying hit elements

    To identify which elements (such as Polygons) were hit, flutter_map APIs are required:

    • A LayerHitNotifier exposes results of hit tests

    • Elements may have metadata known as hitValues 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

    1

    hashtag
    Create a hit notifier

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

    In this example, the types of the hitValue identifiers will be Strings.

    hashtag
    Example

    Polygon Layer

    You can add areas/shapes formed from coordinates to maps using PolygonLayer and Polygons.

    A variety of Polygons including labels, holes, mixed colors & opacities, and dotted borders

    hashtag
    Interactivity

    PolygonLayers and Polygonss support hit detection and interactivity.

    hashtag
    Multi-Worlds

    The PolygonLayer paints its Polygons across all visible worlds by default. This can be changed.

    hashtag
    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.

    circle-check

    The example application includes a stress test which loads multiple Polygons from an optimized format, with a total of 138,000 points.

    chevron-rightCulling (enabled by default)hashtag

    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.

    chevron-rightBatching (enabled by default, but improvable with effort)hashtag
    circle-exclamation

    Overlapping colors that are not completely opaque will not receive the 'darkening'/layering effect - the overlapping area will just be the single colour.

    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.

    chevron-rightSimplification (enabled by default, adjustable)hashtag
    circle-exclamation

    On layers with (many) only small polygons (those with few points), disabling simplification may yield better performance.

    To improve performance, polygon outlines (points) are 'simplified' before the polygons are culled and painted/rendered. The well-known

    chevron-rightPainter Fill Methodhashtag

    See for more information. evenOdd is more performant, but at the expense of potential visual glitches.

    chevron-rightPerformant Rendering, with drawVertices (disabled by default)hashtag
    circle-exclamation

    Self-intersecting (complex) Polygons are not supported by the triangulation algorithm, and could cause errors.

    chevron-rightUse No/Thin Borders or Cheaper StrokeCaps/StrokeJoins (external)hashtag

    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.

    hashtag
    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.

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

    The PolygonPainterFillMethod.pathCombine option uses lesser-used Flutter APIs: the constructor. This allows for more intricate 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 .

    Additionally, it has slightly worse performance (especially at scale) than . 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).

    hashtag
    Inverted Filling

    circle-exclamation

    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.

    This is due to multiple limitations/bugs within Flutter. See and for the problematic bug reports.

    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.

    hashtag
    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 . 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).

    OverlayImageLayer class - flutter_map library - Dart APIpub.devchevron-right
    Logo
    2D Tiles overview  |  Google Maps Tile API  |  Google for DevelopersGoogle for Developerschevron-right
    Serving your own tiles - Switch2OSMswitch2osm.orgchevron-right
    Logo
    Marker class - flutter_map library - Dart APIpub.devchevron-right
    MarkerLayer class - flutter_map library - Dart APIpub.devchevron-right
    nightly.link | Repository fleaflet/flutter_map | Workflow master.yml | Branch masternightly.linkchevron-right
    Latest Build Artifacts (thanks nightlyarrow-up-right)
    CircleMarker class - flutter_map library - Dart APIpub.devchevron-right
    Logo

    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.

    circle-exclamation

    Changing the state of MapOptions.initial* will not update the map. Map control in 'flutter_map' is imperative.

    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.

    Tile Layer

    circle-exclamation

    hashtag
    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.

    Tile Layer
    SimpleAttributionWidget class - flutter_map library - Dart APIpub.devchevron-right
    RichAttributionWidget class - flutter_map library - Dart APIpub.devchevron-right
    flutter_map/CONTRIBUTING.md at master · fleaflet/flutter_mapGitHubchevron-right
    InteractionOptions class - flutter_map library - Dart APIpub.devchevron-right
    configured_built_in.dart
    TileLayer(
        urlTemplate: '...',
        userAgentPackageName: '...',
        tileProvider: NetworkTileProvider(
            cachingProvider: BuiltInMapCachingProvider.getOrCreateInstance(
                maxCacheSize: 1_000_000_000, // 1 GB is the default
            ),
        ),
    );
    custom.dart
    TileLayer(
        urlTemplate: '...',
        userAgentPackageName: '...',
        tileProvider: NetworkTileProvider(
            cachingProvider: CustomMapCachingProvider(),
        ),
    );
    disabled.dart
    TileLayer(
        urlTemplate: '...',
        userAgentPackageName: '...',
        tileProvider: NetworkTileProvider(
            cachingProvider: const DisabledMapCachingProvider(),
        ),
    );
    unpack_polyline.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();
    }
    import 'unpack_polyline.dart';
    
    decodePolyline('<encoded-polyline>').unpackPolyline(); // Returns `List<LatLng>` for a map polyline
    PolygonLayer(
      polygons: [
        Polygon(
          points: [LatLng(30, 40), LatLng(20, 50), LatLng(25, 45)],
          color: Colors.blue,
        ),
      ],
    ),

    flutter_map_animations (TesteurManiak)

    Replacement MapController which provides animated movement alternatives

    vector_map_tiles (greensopinion)

    Suite of tools and layers for working with vector maps and associated style files

    flutter_map_geojson2 (Zverik)

    Parse GeoJSON into map features, with simplespec styling support

    flutter_map_polywidget (TimBaumgart)

    Layer that allows any widget to be displayed inside a positioned box, similar to Overlay Image Layer

    fluttermap_heatmap (tprebs)

    Layer that represents multiple data points in a density-to-color relationship

    flutter_map_compass (josxha)

    Simple compass that illustrates the direction of the map rotation (not the user's real heading)

    flutter_map_tile_caching (JaffaKetchup)

    Advanced, performant, highly configurable caching & bulk downloading (under a GPL license)

    flutter_map_cache (josxha)

    Lightweight mid-term tile caching with support for most storage backends and request cancellation

    flutter_map_mbtiles (josxha)

    Offline and online MBTiles parser and provider (vector variant also available)

    flutter_map_pmtiles (josxha)

    Offline and online PMTiles parser and provider (vector variant also available)

    flutter_map_location_marker (tlserver)

    Provides a prebuilt solution to display the user's location and heading/direction

    flutter_map_marker_popup (rorystephenson)

    Provides a prebuilt solution to display a popup above a marker when tapped

    flutter_map_floating_marker_titles (androidseb)

    Enables the display of 'floating' titles over markers

    flutter_map_dragmarker (ibrierley)

    An interactive draggable Marker

    flutter_map_supercluster (rorystephenson)

    Superfast™ marker clustering solution, without animations

    flutter_map_marker_cluster (lpongetti)

    Beautiful and animated marker clustering solution

    flutter_map_radius_cluster (rorystephenson)

    Marker clustering solution with support for async marker searching within a radius

    flutter_map_line_editor (ibrierley)

    Provides interactive dynamic creation & editing of Polylines and Polygons

    flutter_map_polygon_editor (tdz4)

    Provides interactive dynamic creation & editing of Polylines and Polygons

    line_animator (ibrierley)

    Interpolates along a set of points, allowing gradual drawing of lines and animating moving markers

    flutter_map_rastercoords (jeremejazz)

    Basic utilities to make working with non-geographical tiled large images easier

    Logo
    Logo
    Logo
    Logo
    A white box with attribution text displayed over a map
    An icon and a button displayed over a map, in the bottom right corner
    Logo
    Logo
    Logo
    Logo
    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 & gestures
  • For advanced information about how flutter_map hit tests, see 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

  • s within a layer share the same type
    chevron-right(Advanced) Listening to the notifier directlyhashtag

    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:

    This also allows handling of null notifier values (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).

    2

    hashtag
    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:

    3

    hashtag
    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.

    4

    arrow-progressDetecting hits & gestures

    5

    hashtag
    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:

    circle-info

    Most users can ignore results which are null (when getting the result within a gesture callback, for example).

    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 hitValues were defined.

    Polygon Layer
    Circle Layer
    To further improve performance, consider defining all Polygon points in a clockwise order, and place similar appearance Polygons adjacent to each other in the polygons list (where elevation does not matter).
    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.


    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.

    Holes are supported.
    circle-exclamation

    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.

    circle-exclamation

    Rarely, some visible artefacts may be introduced by the triangulation algorithm.

    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 triangulationarrow-up-right. flutter_map uses an earcutting algorithm through dart_earcutarrow-up-right (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 drawVerticesarrow-up-right method. Borders are then drawn as normal.

    The PolygonPainterFillMethod.evenOdd option uses more simple Flutter APIs: the PathFillTypearrow-up-right.evenOdd rulearrow-up-right.

    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 is used, or when polygon holes can intersect with other holes.

    An example of a visual defect caused by using the .evenOdd setting
    Layer Interactivitychevron-right
    Unbounded Horizontal Scrollingchevron-right
    Path.combinearrow-up-right
    PathOperationarrow-up-right
    Flutter issuearrow-up-right
    https://github.com/flutter/flutter/issues/124675arrow-up-right
    https://github.com/flutter/flutter/issues/149743arrow-up-right
    'poly_bool_dart'arrow-up-right
    Inverted filling working correctly (native)
    Inverted filling broken on web
    Painter Fill Method
    Even/Odd
    Ramer–Douglas–Peucker algorithmarrow-up-right

    hashtag
    Camera

    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:

    hashtag
    Controller

    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:

    hashtag
    Accessing the 'aspects'

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

    circle-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.

    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:

    circle-exclamation

    Using MapController.of(context).camera is an anti-pattern and not recommended.

    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:

    1

    hashtag
    Declare an external MapController

    The

    hashtag
    Reacting to map events

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

    circle-info

    Remember that when using MapCamera.of, that widget will automatically rebuild when the camera changes.

    hashtag
    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:

    circle-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:

    hashtag
    Multiple or complex event handling

    There's two methods to handle raw MapEvents, 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.

    MapOptions has a callback named onMapEvent. For example:

    In addition to controlling the map, the MapController also has a getter called mapEventStream. For example:

    The OpenStreetMap public tile server, as is used for demonstration throughout this project, is NOT free to use by everyone. Their terms of service can be found herearrow-up-right. Production apps should be extremely cautious about using this tile server; other projects, libraries, and packages suggesting that OpenStreetMap provides free-to-use map tiles are incorrect. The examples in this documentation do not necessarily create fully compliant maps. See Using OpenStreetMap (direct) for more info.

    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.

    hashtag
    Recommended Setup

    1

    hashtag
    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.

    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.

    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.

    chevron-right(Advanced) Fallback URL Templatehashtag

    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.

    See for detailed info and potential approaches to supporting offline users.

    hashtag
    From the app's assets (bundled offline)

    1. Set the tileProvider to AssetTileProvider()

    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 , or follow the .

    TMS is also supported. Follow the instructions for the XYZ source, and enable the TileLayer.tms setting.

    2

    hashtag
    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

    Set the userAgentPackageName

    3

    hashtag
    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

    4

    hashtag
    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 , which are tiles at twice the resolution of normal tiles.

    5

    hashtag
    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

    hashtag
    Other Properties

    hashtag
    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.

    circle-exclamation

    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.

    hashtag
    Tile Update Transformers

    circle-info

    TileUpdateTransformer(s) is a power-user feature. Most applications won't require it.

    A TileUpdateTransformer restricts and limits TileUpdateEvents (which are emitted 'by' MapEvents), 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:

    Logo
    Logo
    CursorKeyboardRotationOptions class - flutter_map library - Dart APIpub.devchevron-right
    InteractiveFlag class - flutter_map library - Dart APIpub.devchevron-right

    Using OpenStreetMap (direct)

    circle-info

    This does not apply to users using OpenStreetMap data through other tile servers, only to users using the public OpenStreetMap tile servers directly.

    triangle-exclamation

    MapOptions class - flutter_map library - Dart APIpub.devchevron-right
    PolylineLayer class - flutter_map library - Dart APIpub.devchevron-right
    class _InteractivityDemoState extends State<InteractivityDemo> {
        final LayerHitNotifier<String> hitNotifier = ValueNotifier(null)
            ..addListener(() {
                final LayerHitResult<String>? result = hitNotifier.value;
                // ...
            });
    }
    class _InteractivityDemoState extends State<InteractivityDemo> {
        Widget build(BuildContext context) {
            return FlutterMap(
                // ...
                children: [
                    // ...
                    PolygonLayer<String>(
                        hitNotifier: hitNotifier,
                        polygons: [
                            // ...
                        ],
                    ),
                ],
            );
        }
    }
    polygons: [
        Polygon<String>(
            points: [],
            label: "Flamingo Fields",
            hitValue: "Flamingo",
        ),
        Polygon<String>(
            points: [],
            label: "Hedgehog House",
            hitValue: "Hedgehog",
        ),
        // ...
    ],
    class _InteractivityDemoState extends State<InteractivityDemo> {
        Widget build(BuildContext context) {
            return FlutterMap(
                // ...
                children: [
                    // ...
                    MouseRegion(
                        hitTestBehavior: HitTestBehavior.deferToChild,
                        cursor: SystemMouseCursors.click,
                        child: GestureDetector(
                            onTap: () {
                                // ...
                            },
                            child: PolygonLayer(
                                // ...
                            ),
                        ),
                    ),
                ],
            );
        }
    );
    class _InteractivityDemoState extends State<InteractivityDemo> {
        final LayerHitNotifier<String> hitNotifier = ValueNotifier(null);
    }
    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",
                                    ),
                                ],
                            ),
                        ),
                    ),
                ],
            );
        }
    );
    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());
                },
            ),
        ],
    );
    options: MapOptions(
        interactionOptions: InteractionOptions(
            flags: ~InteractiveFlag.doubleTapZoom,
        ),
    ),
    options: MapOptions(
        onMapEvent: (evt) {
            if (evt is MapEventMove) {
                final oldCamera = evt.oldCamera;
                final newCamera = evt.newCamera;
            }
        },
    ),
    StreamSubscription<MapEvent> listenToMapEvents(MapController controller) {
        return mapController.mapEventStream.listen((evt) {
            if (evt is MapEventMove) {
                final oldCamera = evt.oldCamera;
                final newCamera = evt.newCamera;
            }
        });
    }
    final MapCamera mapCamera = getMapCamera(); // See below
    print(mapCamera.zoom);
    final MapController mapController = getMapController(); // See below
    mapController.move(LatLng(51.505, -0.124), 9); // Position the map to show London, UK
    return FlutterMap(
        options: MapOptions(
            // ...
            onTap: (tapPosition, point) {
                final screenTapped = tapPosition.global;
                final coordinatesTapped = point;
            },
            onPositionChanged: (camera, hasGesture) {
                if (hasGesture) {
                    disableUserLocationFollow();
                }
                print(camera.center);
            },
        ),
        // ...
    );
    TileLayer(
      urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
      userAgentPackageName: 'dev.fleaflet.flutter_map.example',
      // + many other options
    ),
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    final LayerHitResult<String>? result = hitNotifier.value;
    MapController
    object can be constructed, like in the example below. We recommend using a
    StatefulWidget
    .
    circle-info

    The MapController works similarly to a ScrollController.

    2

    hashtag
    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:

    circle-exclamation

    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 for more information.

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

    hashtag
    Pitfalls of an external controller

    chevron-rightThe controller is not safe to use until attachedhashtag

    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:

    circle-exclamation

    MapController methods that control the map (such as changing its position) should not be used directly in onMapReady - see .

    Configure the initial map position in MapOptions instead.

    chevron-rightConstruct the controller in the widget tree & don't pass it up the treehashtag

    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).

    chevron-rightThe MapController does not animate the maphashtag

    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_animationsarrow-up-right.

    circle-exclamation

    Specifying a fallbackUrl does have negative effects on performance and efficiency. Avoid specifying fallbackUrl unless necessary.

    See in-code documentation and Tile Providers for more information.

    circle-info

    Some TileProviders may not support/provide any functionality for fallbackUrl template.

    hashtag
    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.

    chevron-rightSubdomainshashtag
    circle-exclamation

    Subdomains are now usually considered redundantarrow-up-right 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 (), avoid using 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.

    chevron-rightTile Dimensionhashtag

    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, 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.

  • Set the urlTemplate to the path to each tile from the assets directory, using the placeholders as necessary. For example:

  • Add each lowest level directory to the pubspec's assets listing.

  • hashtag
    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

    parameter to your app's package name (such as
    com.example.app
    or any other unique identifying information). flutter_map will identify your client to the server via the header as:

    flutter_map (<packageName or 'unknown'>)

    circle-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

    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.

    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 for more information.

    See Tile Providers for more information about tile providers generally.

    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:

    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.

    chevron-rightEmulating retina modehashtag

    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.

    , 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.
    Offline Mapping
    full API documentationarrow-up-right
    example apparrow-up-right
    high-resolution "@2x" tilesarrow-up-right
    hashtag
    The OSMF have blocked inadequately identified requests to the public OpenStreetMap tile servers

    See What has OSMF done? & What should I do? if your app has suddenly stopped working.

    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 ODbLarrow-up-right.

    The OpenStreetMap Foundation run OpenStreetMap as a not-for-profit. They also provide a public tile server at https://tile.openstreetmap.orgarrow-up-right, 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:

    circle-exclamation

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

    flutter_map can be setup to conform to these requirements - but it may not conform by default.

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

    hashtag
    What is flutter_map doing?

    circle-check

    hashtag
    We're adding automatically enabled Caching, available from v8.2 with compatible tile providers

    This reduces the strain on tile servers, improves compliance with their policies, and has numerous other benefits for your app!

    circle-exclamation

    hashtag
    From v8.2, 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.

    hashtag
    What has OSMF done?

    triangle-exclamation

    hashtag
    Requests to the public OpenStreetMap tile servers from User-Agents which are not properly set have been blocked

    Due to excessive usage, the OpenStreetMap Foundation have blocked requests with one of the following configurations:

    • No specified TileLayer.userAgentPackageName (recommended setup) & no manually specified "User-Agent" header

    • userAgentPackageName set to 'com.example.app' (or equivalent manual config)

    This does not apply to web users, who cannot set a User-Agent different to what is provided by the browser.

    See for more information.

    To restore access, follow .

    hashtag
    Why?

    The OpenStreetMap tile server is NOT free to use by everyone.

    Data collected by OSMarrow-up-right 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.)

    User-Agent
    tiles/second
    "missed" t/s

    flutter_map (*) This represents the majority of FM users on non-web platforms.

    1610

    53

    Mozilla/5.0 QGIS/*

    1155

    358

    Mozilla/5.0 ...

    476

    33

    com.facebook.katana

    263

    And looking in more detail at the User-Agent's making up the primary 'flutter_map (*)' agent, on 2025/05/28:

    Specific User-Agent
    Daily tile requests

    flutter_map (unknown) This represents users failing to use TileLayer.userAgentPackageName.

    99,258,371

    flutter_map (com.example.app) This is likely a combination of users copy-pasting from the docs & personal projects.

    7,808,777

    flutter_map (dev.fleaflet.flutter_map.example) This is likely a combination of users using the Demo app (primarily), and users copy-pasting from the example app.

    6,402,576

    Next largest User-Agent is an adequately identified app

    3,953,312

    We are extremely proud to see flutter_map being used so much! At the same time, we are aware that there are many users placing a potential strain on OpenStreetMap, which we want to minimize:

    • We do not want to discourage legitimate use-cases from using the OpenStreetMap tile servers

    • We want to help users who may be accidentally or unknowingly breaking the OpenStreetMap usage policies adjust their project so they can continue to benefit from cost-free tiles

    • However, we do wish to discourage illegitimate use-cases or users who are intentionally breaking the OpenStreetMap usage policies

    Therefore, we are introducing measures to force users to read the OpenStreetMap Tile Usage Policy before allowing them to use the servers in release builds:

    • This is easy for legitimate users who have already read the policy and follow it

    • It helps users accidentally breaking the policies to see why it's so important to follow them, and what they can do to fix any issues

    • It adds friction for users intentionally breaking the policies

    circle-exclamation

    Ultimately however, 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.

    hashtag
    What should I do?

    1

    hashtag
    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.

    2

    hashtag
    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.

    To note two important general terms:

    3

    hashtag
    Make all necessary adjustments

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

    4

    hashtag
    Disable the console messages

    We show a console message as a reminder and reference if you're using the servers, even if you're in compliance. You can disable this info - if flutter_map implements further blocks in future, this may also disable those.

    circle-exclamation

    hashtag
    I have more questions

    If you've got a question or comment regarding this new policy, or if you think we've missed something, please reach out to us on Discord (or via GitHub Discussions).

    Logo
    Polyline class - flutter_map library - Dart APIpub.devchevron-right
    Is FMTC Right For Me? | FMTC Docsfmtc.jaffaketchup.devchevron-right
    flutter_map changelog | Flutter packageDart packageschevron-right
    CHANGELOG
    BuiltInMapCachingProvider class - flutter_map library - Dart APIpub.devchevron-right
    // (`StatefulWidget` definition) ...
    
    class _MapViewState extends State<MapView> {
        final _mapController = MapController();
        
        @override
        Widget build(BuildContext context) {
            return FlutterMap(
                controller: _mapController,
                // ...
            );
        }
    }
    double getCurrentZoomLevel(MapController controller) {
        return controller.camera.zoom;
    }
    assets/map/{z}/{x}/{y}.png
        retinaMode: RetinaMode.isHighDensity(context),
    https://tile.openstreetmap.org/{z}/{x}/{y}.png
    Logo
    Logo
    Logo
    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...

    Enable conforming caching
    circle-check

    v8.2.0 introduces automatically enabled Caching! This is designed to meet the caching requirements of the usage policy. Upgrade to v8.2.0 to enable this functionality.

    There's also other options to implement Caching to meet the requirements, and go beyond the capabilities of the built-in caching.

  • Add sufficient attribution

    circle-check

    The RichAttributionWidget or SimpleAttributionWidget can both be used to setup attribution which looks great, is unintrusive, and is conforming - provided you add the necessary sources. See Attribution Layer for more info and a simple code snippet you can add to meet the attribution requirement.

    You can also add attribution in any other way that meets the requirements.

  • Set a more specific user-agent to identify your client

    circle-check

    flutter_map provides its own user-agent on native platforms, but this isn't enough to meet the requirements. You should set TileLayer.userAgentPackageName: see the Recommended Setup for the TileLayer. This is not necessary when running solely on the web, where it is not possible to set a User-Agent manually.

  • This will not disable any blocks enforced by the OpenStreetMap foundation.

    To do this, set the flutter.flutter_map.unblockOSM environment variable when building/running/compiling. Use the dart-define flag to do this.

    To evidence that you've read and understood the tile policy, you should set it to the exact string (excluding any surrounding whitespace, including all other punctuation) following the phrase from the policy:

    OpenStreetMap data is free for everyone to use. _____

    For example, to run the project:

    You can also add this to your IDE's configuration to automatically pass this argument when running from your IDE. For example, in VS Code:

    3

    Dart/* (dart:io) This represents FM users on older versions (not on web) & other Flutter mapping libraries not using FM (not on web).

    182

    17

    Mozilla/5.0 ...

    175

    6

    Mozilla/5.0 ...

    171

    14

    https://github.com/fleaflet/flutter_map/issues/2123#issuecomment-3062197581arrow-up-right
    What should I do?
    issue #1507arrow-up-right
    The controller is not safe to use until attached
    how to checkarrow-up-right
    Flowchart describing the best method to optimize a tile layer & tile provider setup. Is your `FlutterMap` or `TileLayer` rebuilt (frequently)? Or, are you using a different tile provider to the default? If not, don't worry about it, the `TileLayer` 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.
    Logo
    Comparing v8.1.1...v8.2.0 · fleaflet/flutter_mapGitHubchevron-right
    Polygon class - flutter_map library - Dart APIpub.devchevron-right
    PolygonLayer class - flutter_map library - Dart APIpub.devchevron-right
    flutter run --dart-define=flutter.flutter_map.unblockOSM="_____"
    .vscode/launch.json
    {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "app",
                "request": "launch",
                "type": "dart",
                "args": [
                    "--dart-define",
                    "flutter.flutter_map.unblockOSM=_____" // no inner quotes
                ]
            }
        ]
    }
    final _mapControllerReady = Completer<void>();
    
    return FlutterMap(
        mapController: _mapController,
        options: MapOptions(
            onMapReady: () {
                // `_mapController` safe to use
                _mapControllerReady.complete();
            },
        ),
    );
    Logo
    Logo
    Logo
    Logo
    TileUpdateTransformer typedef - flutter_map library - Dart APIpub.devchevron-right
    TileLayer class - flutter_map library - Dart APIpub.devchevron-right
    Logo
    Logo
    Logo
    MapCamera class - flutter_map library - Dart APIpub.devchevron-right
    Logo
    Logo
    WMSTileLayerOptions class - flutter_map library - Dart APIpub.devchevron-right
    Logo
    Tile Usage Policyoperations.osmfoundation.orgchevron-right
    OpenStreetMap public tile server usage policy
    Tile Usage Policyoperations.osmfoundation.orgchevron-right
    OpenStreetMap public tile server usage policy
    Logo
    Logo
    MapController class - flutter_map library - Dart APIpub.devchevron-right
    Logo