Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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!
These great projects all make use of flutter_map!
But there's more: check out the dependents list on GitHub, and the #showcase channel for more projects on our Discord server.
Flutter's №1 non-commercially aimed map client: it's easy-to-use, versatile, vendor-free, fully cross-platform, and 100% pure-Flutter
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.
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!
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!
This map uses the OpenStreetMap public tile servers, which are NOT free to use by everyone. A setup such as this, especially in production, would not be compliant with its requirements.
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
We get quite a lot of similar questions, so please check if your question is here before you ask!
Map Marker
Your places organizer
Your advert here!
Want to advertise your project here? For more information, and to apply, please see:
Every Door
The most efficient OpenStreetMap editor for surveying shops and benches
Ente Photos
End-to-end encrypted alternative to Google Photos
🇳🇱 Stichting Zeilvaart Warmond (Track & Trace)
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:
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
@override
Widget build(BuildContext context) {
return FlutterMap(
options: MapOptions(
initialCenter: LatLng(51.509364, -0.128928), // Center the map over London
initialZoom: 9.2,
),
children: [
TileLayer( // 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...
],
),
],
);
}
🗺️ No more vendor lock-in: better flexibility, quality, and price
We natively support any static 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:
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!
Use https://console.tracestrack.com/explorer 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
CircleLayer(
circles: [
CircleMarker(
point: LatLng(51.50739215592943, -0.127709825533512),
radius: 10000,
useRadiusInMeter: true,
),
],
),
CircleMarker
You can overlay images on the map (for example, town or floor plans) using OverlayImageLayer
and OverlayImage
s or RotatedOverlayImage
s.
RotatedOverlayImage
OverlayImageLayer(
overlayImages: [
OverlayImage( // Unrotated
bounds: LatLngBounds(
LatLng(45.3367881884556, 14.159452282322459),
LatLng(45.264129635422826, 14.252585831779033),
),
imageProvider: NetworkImage(),
),
],
),
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.
RotatedOverlayImage
supports rotation and parallelogram skewing, by accepting 3 points instead of 2.
To calculate a rotation without skewing, given a center and a 3rd corner, see https://stackoverflow.com/a/78064659/11846040.
A high level overview for those new to 'web' maps
Unlike other popular mapping solutions, flutter_map doesn't come with an opinion on the best map style/tiles to use, so you'll need to bring your own tiles - either using a service, such as those listed in Tile Servers, or by creating and using your own custom ones!
We then allow you to add more on top of these tiles, and control and customize as far as you need.
It's a client to display 'tiled & WMS web' maps and other map features - not a map itself.
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 (Raster vs Vector Tiles), and reference (eg. XYZ vs WMS) tiles! We support most of them, except vector tiles. This documentation primarily uses examples referring to Slippy Maps 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.
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!
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.
You can also control the map programmatically using a simple controller pattern. See Programmatic Interaction for more info.
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.
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:
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.
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).
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:
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.
flutter_map makes use of InheritedModel
to share 3 'aspects' with its built children:
MapController
: use to programatically control the map camera & access some helper functionality - control camera
MapCamera
: use to read the current state/position of the map camera & access some helper functionality that depends on the camera (such as latlngToPoint
) - read camera
All 3 aspects can be retrieved from within the context of a FlutterMap
, which all built descendants should have access to. This usually means from within a layer: anywhere where there is at least one 'visible' builder method between the FlutterMap
and the invocation.
Use the static of
(or null-safe maybeOf
) method to access the inherited aspect. For example, to access the MapCamera
:
This will attach the widget to the state of the map, causing it to rebuild whenever the depended-on aspects change. See for more information.
Using this method directly in the children
list (not inside another widget), and in any MapOptions
callback, is not possible: there is no* builder method between the FlutterMap
and the children
or callback.
Instead, follow , or, wrap the necessary layers with a Builder
widget.
For example, the code snippet below hides a TileLayer
when above zoom level 13:
MapCamera
To access the MapCamera
outside of a FlutterMap
descendant, first .
Then use the camera
getter on the MapController
instance.
Avoid using this method to access the camera when MapCamera.of()
is available.
MapController
For more information about correctly setting up an external(ly accessible) MapController
, see:
MapOptions
There's two ways to interact with the map - that is to control it, as well as receive data from it - and it's current viewport, aka. 'camera'.
The first way is through user interaction, where they perform gestures (such as drags/pans), and the map reacts automatically to those gestures to change the camera view of the map.
These are usually restricted by . It is possible to disable all input, either by disabling all gestures, or by wrapping the map with something like IgnorePointer
.
However, the map camera can also be controlled by calling methods on a controller, and its state read by getting values from an exposed camera.
Changing the state of MapOptions.initial*
will not update the map camera. It may only be updated through a MapController
.
For more information, see:
The behaviour of hit testing can be confusing at first. These rules define how hit testing usually behaves:
Gesture callbacks in MapOptions
are always invoked, no matter what is within the layers or the result of hitTest
s in those layers, with the exception of custom defined hit test behaviours (not those layers that support interactivity, see ), such as applying GestureDetector
s around Marker.child
ren
GestureDetector
s absorb hit tests, and so corresponding callbacks in MapOptions
will not be invoked if they are defined/invoked in the GestureDetector
.
Workarounds to resolve this are discussed below.
Hit testing is always* performed on interactive-capable layers (see ) even if they have not been set-up for interactivity: hit testing != interactivity
Non-interactable layers (such as ) have no defined hitTest
, and behaviour is situation dependent
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:
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.
Since v8 & v8.2.0, 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.
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.
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:
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.
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.
flags
is a that enables and disables the vast majority of gestures. Although technically the type is of int
, it is usually set with a combination of InteractiveFlag
s.
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).
Otherwise, to set flags, there's two methods:
Add flags, with the bitwise 'OR' (|
) operator in-between
For example, InteractiveFlag.drag | InteractiveFlag.rotate
Remove flags from all
, using the &
and ~
operators in-between
For example, InteractiveFlag.all & ~InteractiveFlag.rotate
Cursor/keyboard rotation 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:
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.
This is advanced behaviour that affects how gestures 'win' in the gesture arena, and does not usually need changing.
Start by adding some to children
, then configure the map in . Additionally, if required, add a MapController
: .
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.
To display their map tiles, Stadia Maps usually provides a 'Static Maps Base URL' for map styles. However, to integrate with 3rd-party APIs, they also provide a 'Raster XYZ PNGs URL' , and tiles requested through this endpoint consume 'Styled Raster Map Tiles' credits. This URL needs no extra configuration to integrate with flutter_map.
Retina tiles (high-DPI) tiles are available. Use the URLs containing '@2x' instead of '{r}'. The maximum zoom level that Stadia Maps supports is 20, so it is recommended to set maxNativeZoom
or maxZoom
as such.
Attribution is required, see .
Consider using the or s, which meet the requirements.
Stadia Maps offers a variety of ready-made map styles that don't require customization. URLs are found with the style: see the available . The URL should be used as above.
Stadia Maps' also provides vector tiles. For more information about using vector tiles, please see .
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.
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.
Sign up for an account - Lima Labs operates manual verification, so you may have to wait a few days
Use the common URL (___) with your API key
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.
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
Plugins extend flutter_map's functionality. There's no specific definition, but they often offer additional utilities and/or layers.
If one of the doesn't suit your needs, then you can create your own, to achieve maximum customizability.
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 . 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
includes good documentation (at least to setup basic functionality), and a code example
Huge thanks to everyone who uses, supports, and/or contributes to flutter_map in any way, you've helped make the most popular non-commercially aimed mapping solution for Flutter!
In particular, thanks go to:
All the current maintainers:
@ibrierley
@JaffaKetchup
@mootw (previously @MooNag)
@TesteurManiak
@josxha
All the previous maintainers:
John P Ryan - the original founder of this project, over at AppTree Software
@kengu
@maRci002
The authors of this documentation:
@JaffaKetchup
Anyone who has contributed to making flutter_map:
Anyone who has made plugins for flutter_map:
Anyone who has donated to flutter_map:
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.
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 .
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.
Using these methods will restrict this widget to only being usable inside the context of a FlutterMap
.
There are 2 main types of tiles a server can serve: raster and vector; each has their own advantages and drawbacks. This page is designed to help you choose a type for your app, and help you use vector tiles if you choose to.
Raster tiles are the 'older' type of tile, and are raster images (usually .png or .jpg). These tiles are good because they can render quickly and easily, can be viewed without special software, and are readily available from most mapping services. As such, this makes them the popular choice for beginners.
However, raster tiles cannot be easily themed: a theme needs a whole new set of map tiles. This makes apps using light and dark themes have mismatching maps. As well as this, raster tiles usually have larger file sizes meaning slower download times, and they can become blurred/pixelated when viewed at a larger scale: a problem for users when zooming between zoom levels. Another issue is that shapes/text inside tiles cannot be rotated, hence the name 'static tiles': therefore, rotating the map will not rotate the name of a road, for example.
Vector tiles 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.
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.
Worried about vector tiles performance?
Using vector tiles may significantly cut FPS and introduce jank, and that's because of the amount of UI work that must be performed on the main thread.
The community and FM maintainers are looking to improve the situation!
Keep up to date and subscribe to the issue: .
FlutterMap(
mapController: MapController(),
options: MapOptions(),
children: [],
);
class Epsg3857NoRepeat extends Epsg3857 {
const Epsg3857NoRepeat();
@override
bool get replicatesWorldLongitude => false;
}
FlutterMap(
options: MapOptions(
cameraConstraint: const CameraConstraint.containLatitude(),
),
children: [],
),
drawInSingleWorld: false
(default)drawInSingleWorld: true
When the state of a MapCamera
changes, because of an update to its position or zoom, for example, a MapEvent
, which can be handled by you.
There's two methods to catch all emitted MapEvent
s. These methods expose the raw MapEvent
, and is recommended in cases where multiple events need to be caught, or there's no more specific callback method available in MapOptions
(see Catching Specific Events).
Listening to a MapController
's mapEventStream
, which exposes events via a Stream
Specifying a callback method in MapOptions.onMapEvent
If only a couple of events need to be caught, such as just an onTap
handler, it is possible to avoid handling the raw Stream
of MapEvent
s. Instead, MapOptions
has callbacks available for the following events:
onTap
onLongPress
onPositionChanged
onPointerDown
/onPointerUp
/onPointerHover
/onPointerCancel
onMapReady
Primarily used for advanced MapController
Controllers & Cameras
Caching is extensible.
To create a new caching provider which is compatible with all tile providers which are compatible with built-in caching, create a class which implements MapCachingProvider
and its required interface.
class CustomCachingProvider implements MapCachingProvider {
@override
bool get isSupported => throw UnimplementedError();
@override
Future<({Uint8List bytes, CachedMapTileMetadata metadata})?> getTile(
String url,
) {
throw UnimplementedError();
}
@override
Future<void> putTile({
required String url,
required CachedMapTileMetadata metadata,
Uint8List? bytes,
}) {
throw UnimplementedError();
}
}
Compatible tile providers must check isSupported
before using getTile
or putTile
.
Check in-code documentation for more detail on requirements and expectations.
Many providers may only work on certain platforms. In this case, implementations can mix-in DisabledMapCachingProvider
on unsupported platforms:
class CustomCachingProvider
with DisabledMapCachingProvider
implements MapCachingProvider {}
If a provider cannot read a tile from the cache, but the tile is present, the provider should:
throw CachedMapTileReadFailure
with as much information as possible from readTile
repair or replace the tile with a fresh & valid one
ensure other mechanisms are resilient to corruption
This could occur due to corruption, for example a power cut, a sudden storage issue, or an intentional modification that did not comply with the expected specification.
It is not the provider's responsibility to check that stored tile bytes are valid. Providers may return invalid or undecodable bytes to tile providers, which they should handle gracefully by falling back to a non-caching alternative to retrieve a tile, and safely updating the invalid stored tile.
final inheritedCamera = MapCamera.of(context);
children: [
Builder(
builder: (context) {
if (MapCamera.of(context).zoom < 13) return SizedBox.shrink();
return TileLayer();
},
),
],
final camera = MapCamera.of(context);
final controller = MapController.of(context);
final options = MapOptions.of(context);
You can add single point features - including arbitrary widgets - to maps using MarkerLayer
and Marker
s.
No more image only markers! Unlike other popular mapping libraries, we allow usage of any widget as the marker.
Marker
s on a rotated mapMarkerLayer(
markers: [
Marker(
point: LatLng(30, 40),
width: 80,
height: 80,
child: FlutterLogo(),
),
],
),
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: .
The marker widget will be centered over the geographic coordinate by default. However, this can be changed with the alignment
argument, which aligns the widget relative to the point.
The center of rotation when rotate
is true
will be the point.
The default alignment for all Marker
s within a MarkerLayer
can be set by changing the same property on the MarkerLayer
.
It is possible to enable the Marker
to automatically counter-rotate to the camera's rotation, to ensure it remains facing upwards, via the rotate
argument.
The default alignment for all Marker
s within a MarkerLayer
can be set by changing the same property on the MarkerLayer
.
There is no built-in support to handle gestures on Marker
s, such as taps. However, this is easy to implement using a standard GestureDetector
.
For more information about what a MapController
is, and when it is necessary to set one up in this way, see:
The FlutterMap.controller
parameter takes an externally intialised MapController
instance, and attaches it to the map.
// Within a widget
final mapController = MapController();
@override
Widget build(BuildContext context) =>
FlutterMap(
mapController: mapController,
// ...
);
An externally attached controller will be accurately reflected when depending on the MapController
aspect.
It is not safe to assume that the MapController
is ready to use as soon as an instance has been initialised, for example within initState
.
See below for more information.
initState
)It is not safe to assume that the MapController
is ready to use as soon as an instance has been initialised, for example within initState
.
It must first be attached to the FlutterMap
, which could take up to multiple frames to occur (similar to the way a ScrollController
is attached to a scrollable view). It helps to avoid errors by thinking of the controller in this way.
Use of the MapController
before it has been attached to a FlutterMap
will result in an error being thrown, usually a LateInitialisationError
.
It is usually safe to use a controller from within a callback manually initiated by the user without further complications.
For example, it is sometimes necessary to use a controller in the initState()
method (for example to attach an event listener). However, because this method executes before the widget has been built, a controller defined here will not be ready for use.
Instead, use the MapOptions.onMapReady
callback. At this point, it is guaranteed that the controller will have been attached. You could also use this method to complete a Completer
(and await
its Future
elsewhere) if you need to use it elsewhere.
final mapController = MapController();
@override
void initState() {
// Cannot use `mapController` safely here
}
@override
Widget build(BuildContext context) {
return FlutterMap(
mapController: mapController,
options: MapOptions(
onMapReady: () {
mapController.mapEventStream.listen((evt) {}); // for example
// Any* other `MapController` dependent methods
},
),
);
}
MapController
methods that change the position of the map should not be used directly (not as a result of another callback) in onMapReady
- see issue #1507. This is an unsupported and usually unnecessary usecase.
Don't define/intialise the a MapController
within a class or widget that doesn't also contain the FlutterMap
, such as a state model (eg. ChangeNotifier
), then try to use it by querying the state in the FlutterMap.controller
parameter.
Instead, some extra care should be taken, which may feel a little backwards at first. The state model should be used AFTER the normal setup.
Setup the controller as in Basic Setup, where the MapController
is defined & initialised directly adjacent to the FlutterMap
In your state model, create a nullable (and initially uninitialised) MapController
containing field
Follow Usage Before Attachment (eg. Within initState) to setup an onMapReady
callback. Then within this callback, set the state model field.
It may then be beneficial to unset the state model field when the controller is disposed: it should be disposed when the FlutterMap
is disposed, which should occur just before the widget building the FlutterMap
is disposed. Therefore, you can override the dispose
method.
Whilst animated movements through MapController
s aren't built-in, the community maintained plugin flutter_map_animations
provides this, and much more!
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!
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)!
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.
Most layers are 'mobile', such as the TileLayer
. These use a MobileLayerTransformer
widget internally, which enables the layer to properly move and rotate with the map's current camera.
However, some layers are 'static', such as the AttributionLayer
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.
Some layers - such as PolygonLayer
- take 'elements' - such as Polygon
s - as an argument, which are then displayed by the layer. They are usually displayed bottom-to-top in the order of the list (like a Stack
).
Some layers that use elements also support interactivity via hit testing. This is described in more detail on another page:
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!
For a quick code demo, check out the landing page: Code Demo!
Please don't abuse the web demo! It runs on limited bandwidth and won't hold up to thousands of loads.
If you're going to be straining the application, please see Prebuilt Artifacts, and serve the application yourself.
If you can't build from source for your platform, our GitHub Actions CI system compiles the example app to GitHub Artifacts for Windows, Web, and Android.
The Windows and Android artifacts just require unzipping and installing the .exe or .apk found inside.
The Web artifact requires unzipping and serving, as it contains more than one unbundled file. You may be able to use dhttpd for this purpose.
If you need to use the example app on another platform, you can build from source, using the 'example' directory of the repository.
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.
You must comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.
This library and/or the creator(s) are not responsible for any violations you make using this package.
The OpenStreetMap Tile Server (as used above) ToS can be found here. Other servers may have different terms.
Please consider crediting flutter_map. It helps us to gain more awareness, which helps make this project better for everyone!
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.
RichAttributionWidget
RichAttributionWidget
, as in the example appchildren: [
RichAttributionWidget(
attributions: [
// Suggested attribution for the OpenStreetMap public tile server
TextSourceAttribution(
'OpenStreetMap contributors',
onTap: () => launchUrl(Uri.parse('https://openstreetmap.org/copyright')),
),
],
),
],
For more information about configuration and all the many options this supports, see the in-code API documentation.
SimpleAttributionWidget
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 appchildren: [
SimpleAttributionWidget(
source: Text('OpenStreetMap contributors'),
),
],
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 . 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 .
You can use your previously registered proj4.Projection
to create a custom CRS of type Proj4Crs
. You can use the following parameters:
<String>
code
(required): string identifier for the selected CRS, e.g. EPSG:3413
<proj4.Projection>
proj4Projection
(required): the proj4.Projection
object you wish to use
<Bounds>
bounds
: bounds of the CRS in projected coordinates
<List<double>>
resolutions
: an array of zoom factors (projection units per pixel, eg. meters/pixel)
<List<double>>
scales
: scale factors (pixels per projection unit); specify either scales or resolutions, but not both!
<List<Point>>
origins
: tile origin in projected coordinates (for TileLayer). Why is it needed? GeoServer by default can define different origins (top left coordinates) for each zoom levels. In case of origin mismatch the tile will be drawn on the wrong place: the map will jump at such zoom levels. If your origins vary with zoom levels the number of origins must match the number of resolutions. You can get the desired origins from a GetCapabilities
WMTS call from geoserver e.g. http://[ip:port]/geoserver/gwc/service/wmts?request=GetCapabilities
. This results an XML, and you have to look up for the TopLeftCorner
s for each TileMatrix of your TileMatrixSet.
<Transformation>
transformation
: the transformation to use when transforming projected coordinates into pixel coordinates
An example:
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:
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:
Defines the location of the map when it is first loaded
Defines restrictions that last throughout the map's lifetime
Defines methods that are called on specific map events
One part of MapOptions
responsibilities is to define how the map should be positioned when first loaded. There's two ways to do this (that are incompatible):
initialCenter
(LatLng
) & initialZoom
initialCameraFit
by bounds (): CameraFit.bounds
by bounds (): CameraFit.insideBounds
by coordinates (): CameraFit.coordinates
It is possible to also set the map's initialRotation
in degrees, if you don't want it North (0°) facing initially.
If rotation is enabled/allowed, if using initialCameraFit
, prefer defining it by coordinates for a more intended/tight fit.
One part of MapOptions
responsibilities is to define the restrictions and limitations of the map and what users can/cannot do with it.
Some of the options are described elsewhere in this documentation, in context. In addition, the API docs show all the available options, and below is a partial list of options:
cameraConstraint
camera bounds inside bounds: CameraConstraint.bounds
camera center inside bounds: CameraConstraint.center
unconstrained (default): CameraConstraint.unconstrained
maxZoom
and minZoom
Sets a hard limit on the maximum and minimum amounts that the map can be zoomed
Configures the gestures that the user can use to interact with the map - for example, disable rotation or configure cursor/keyboard rotation
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.
FM does have some support for using alternative CRSs.
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.
Mapbox supports creating and using custom styled maps through Studio.
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.
Create a custom style using the editor
Click "Share...", or the share icon
Choose between Draft or Production
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
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.
The maximum zoom level that Mapbox supports is 22, so it is recommended to set maxNativeZoom
or maxZoom
as such.
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.
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.
Depend on flutter_map from as normal! Use the command line or add the dependency manually to your pubspec.yaml.
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.
We support Wasm! Build and run your app with the '-wasm' flag and benefit from potentially improved performance when the browser can handle Wasm.
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
:
Check you've correctly configured your TileLayer
:
Check you've followed the steps above for your platform
Use Flutter DevTools on native platforms, or the browser devtools on web, and check the HTTP responses of tile requests
Try requesting a tile manually using your browser or a command line utility which supports setting any required headers (for example, for authorization)
If you're testing on a platform which is using , 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.
import 'package:proj4dart/proj4dart.dart' as proj4;
+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'],
),
);
since v8.2
This page contains references to as-of-yet unconfirmed features, which may change without warning. The information on this page is likely to change frequently, and potentially significantly, or may be removed completely.
See https://github.com/fleaflet/flutter_map/pull/2082 for progress.
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.
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:
Reduce the strain on tile servers (particularly the OpenStreetMap public tile servers)
Improve compliance with tile server terms/requirements
Reduce the costs of using tile servers by reducing unnecessary tile requests
Improve map tile loading speeds, especially on slower networks
Keep your app lightweight - it doesn't require a database
Be extensible, customizable, and integrate with multiple tile providers
It does, however, come at the expense of usage of on-device storage capacity.
Offline mapping and caching can also be implemented in other ways. See Offline Mapping for more information.
Built-in caching is enabled by default on non-web platforms, using the BuiltInMapCachingProvider
implementation.
On web platforms, the browser usually performs caching automatically.
insert link
To configure the default provider, provide arguments to the getOrCreateInstance
factory constructor. Usually this is done when constructing the TileLayer
/TileProvider
:
TileLayer(
urlTemplate: '...',
userAgentPackageName: '...',
tileProvider: NetworkTileProvider(
cachingProvider: BuiltInMapCachingProvider.getOrCreateInstance(
maxCacheSize: 1_000_000_000, // 1 GB is the default
),
),
);
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:
With the default BuiltInMapCachingProvider
, it is possible to delete the cache contents in two ways:
When the app is running, destroy
the current instance and set the deleteCache
argument to true
(then optionally create a new instance if required, which happens automatically on the next tile load by default)
When the app is not running, users may delete the storage directory
If the default cache directory is used, users may do this by 'clearing the app cache' through their operating system, for example. On some platforms, this may need to be done manually (which may be difficult for less technical users), whilst on others, it may be a simple action.
You can also use any other MapCachingProvider
implementation, such as provided by plugins, or create one yourself! 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
.
TileLayer(
urlTemplate: '...',
userAgentPackageName: '...',
tileProvider: NetworkTileProvider(
cachingProvider: CustomMapCachingProvider(),
),
);
Before disabling built-in caching, you should check that you can still be compliant with any requirements imposed by your tile server.
It is your own responsibility to comply with any appropriate restrictions and requirements set by your chosen tile server/provider. Always read their Terms of Service. Failure to do so may lead to any punishment, at the tile server's discretion.
The built-in caching is designed to be compliant with the caching requirement for the OpenStreetMap public tile server. Disabling it may make your project non-compliant.
If you prefer to disable built-in caching, use the DisabledMapCachingProvider
on each tile provider:
TileLayer(
urlTemplate: '...',
userAgentPackageName: '...',
tileProvider: NetworkTileProvider(
cachingProvider: const DisabledMapCachingProvider(),
),
);
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.
Attribution is not demonstrated here, but may be required. Ensure you comply with Bing Maps' ToS.
// 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 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();
}
}
flutter pub add flutter_map latlong2
flutter pub add # OPTIONAL
flutter run --no-enable-impeller
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
<uses-permission android:name="android.permission.INTERNET"/>
...
</manifest>
...
<dict>
...
<key>com.apple.security.network.client</key>
<true/>
...
</dict>
...
Here's some highlights:
We're repeating the trend from v7, and introducing yet another feature that's been continuously requested for longer than we can remember 😂!
Thanks to the hard work of external contributors, you can now pan and fling across the anti-meridian as much as you want (when using the default map projection only).
Feature layers (such as polygons) also work across all 'worlds'. Improvements to this are still ongoing as of v8.1.1.
This feature was bounty-funded, thanks to our generous Supporters! We hope to open more bounties in future.
Maybe not quite as highly requested as horizontal scrolling, we've now also added buttery-smooth keyboard controls! On web and desktop platforms, these are important for accessibility and general usability.
Supports arrow & WASD
keys for panning, QE
keys for rotation, and RF
keys for zoom. All key handlers are based on the physical layout of the QWERTY keyboard, so users using other keyboards will be able to use whichever keys are physically located in the same position. These are all optionally controllable via KeyboardOptions
for MapOptions
- only arrow keys are enabled by default.
We've also fixed a major performance bug with simplification on Polygon/lineLayer
s. If you previously disabled simplification to workaround the bug and improve performance, we recommend considering re-enabling it.
Also thanks to the community, we've reworked some internals to reduce overheads and reduce the number of different objects.
We've also made other changes to improve the experience for your users. Checkout the CHANGELOG for the curated changes, and the full commit listing for all the small details.
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.
You can add lines formed from coordinates to maps using PolylineLayer
and Polyline
s.
Polyline
s support a solid
, dotted
, and dashed
style, through a StrokePattern
passed 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.
PolylineLayer
s and Polyline
s support hit detection and interactivity.
The PolylineLayer
paints its Polyline
s across all visible worlds by default. This can be changed.
The example application includes a stress test which generates a Polyline
with 200,000 points.
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.
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.
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.
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.
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.
On layers with (many) only 'short' polylines (those with few points), disabling simplification may yield better performance.
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.
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:
A TileProvider
works with a TileLayer
to supply tiles (usually images) when given tile coordinates.
Tile providers may support compatible (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.
These tile providers use the TileLayer.urlTemplate
to get the appropriate tile from the a network, usually the Internet.
The underlying custom ImageProvider
s will cache tiles in memory, so that they do not require another request to the tile server if they are pruned then re-loaded. This should result in them being loaded quicker, as well as enabling already loaded tiles to appear even without Internet connection (at least in the same session).
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.
NetworkTileProvider
This is the default tile provider, and does nothing particularly special. It takes two arguments, but you'll usually never need to specify them:
httpClient
: BaseClient
By default, a RetryClient
backed by a standard Client
is used
headers
: Map<String, String>
By default, only headers sent by the platform are included with each request, plus an overridden (where possible) 'User-Agent' header based on the property
Tiles that are removed/pruned before they are fully loaded do not need to complete (down)loading, and therefore do not need to complete the HTTP interaction. Cancelling these unnecessary tile requests early could:
Reduce tile loading durations (particularly on the web)
Reduce users' (cellular) data and cache space consumption
Reduce costly tile requests to tile servers*
Improve performance by reducing CPU and IO work
This provider uses '', which supports aborting unnecessary HTTP requests in-flight, after they have already been sent.
Although HTTP request abortion is supported on all platforms, it is especially useful on the web - and therefore recommended for web apps. This is because the web platform has a limited number of simulatous HTTP requests, and so closing the requests allows new requests to be made for new tiles. On other platforms, the other benefits may still occur, but may not be as visible as on the web.
Once HTTP request abortion is , NetworkTileProvider
will be updated to take advantage of it, replacing and deprecating this provider. This tile provider is currently a separate package and not the default due to the reliance on the additional Dio dependency.
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.
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
.
AssetTileProvider
This tile providers uses the templateUrl
to get the appropriate tile from the asset store of the application.
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.
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.
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
Provide a set of pre-determined tiles to all users through app assets or the filesystem
This section contains references to as-of-yet unreleased versions and unconfirmed features.
Caching is used usually to improve user experience by reducing network waiting times, not necessarily to prepare for no-Internet situations. Caching can be temporary (eg. in-memory/session-only, where the cache is cleared after the app is closed), or longer-term (eg. app cache, where the OS takes responsibility for clearing the app cache when necessary/when requested).
Temporary caching is automatically implemented by Flutter's ImageCache
when using tile providers backed by ImageProvider
s (such as the NetworkTileProvider
).
However, many tile servers, such as the OpenStreetMap tile server, will require you to implement longer-term caching!
Since v8.2.0, flutter_map provides 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 for more info.
Use a caching HTTP client
NetworkTileProvider.httpClient
can be used to set a custom HTTP client. Some packages, such as '', 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
Use Supports very advanced caching use-cases and , but GPL licensed
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 , especially for regions that are a non-rectangular shape. Implementing this can be very time consuming and prone to issues.
The 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:
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.
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:
: (when using vector tiles)
: ( when using vector tiles, also works in online contexts)
When uncontained, tiles are usually in a tree structure formed by directories, usually 'zoom/x/y.png'. These don't require special parsing, and can be provided directly to the TileLayer
using one of the built-in local TileProvider
s.
AssetTileProvider
You can ship an entire tile tree as part of your application bundle, and register it as assets in your app's pubspec.yaml.
This means that they will be downloaded together with your application, keeping setup simple, but at the expense of a larger application bundle size.
If using AssetTileProvider
, every sub-directory of the tree must be listed seperately. See the example application's 'pubspec.yaml' for an example.
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.
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.
TileProvider
To create your own usable TileProvider
, the first step is making a class that extends
the abstract class, and adding a constructor.
The constructor should accept an argument of super.headers
, without a const
ant default.
TileProvider
s must implement a method to return an ImageProvider
(the image of a tile), given its coordinates and the TileLayer
it is used within.
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 TileProvider
s that can cancel the loading of a tile in-flight, if the tile is pruned before it is fully loaded. An example of a provider that may be able to do this is one that makes HTTP requests, as HTTP requests can be aborted on the web (although Dart does not 'natively' support it yet, so a library such as Dio is necessary). Otherwise, getImage
must be overridden.
In addition to the coordinates and TileLayer
, the method also takes a Future<void>
that is completed when the tile is pruned. It should be listened to for completion (for example, with then
), then used to trigger the cancellation.
For an example of this, see .
Tile providers can support the MapCachingProvider
contract/interface to support built-in caching. See .
Some custom TileProvider
s may want to change the way URLs are generated for tiles, given a coordinate.
It's possible to override:
how the urlTemplate
's placeholders are populated: populateTemplatePlaceholders
the values used to populate those placeholders: generateReplacementMap
the generation method itself: getTileUrl
and/or getTileFallbackUrl
Avoid overriding the generation method itself, as it is not usually necessary.
Unexpected error with integration github-files: Integration is not installed on this space
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,
);
dependency_overrides:
flutter_map:
git:
url: https://github.com/fleaflet/flutter_map.git
# ref: master (or commit hash, branch, or tag)
PolylineLayer(
polylines: [
Polyline(
points: [LatLng(30, 40), LatLng(20, 50), LatLng(25, 45)],
color: Colors.blue,
),
],
),
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
Polyline
s, including gradients, mixed opacity and colors including borders, and dashesPatternFit
applied to a dashed
Polyline
From left to right: none
, appendDot
, extendFinalDash
, scaleUp
(default), scaleDown
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.
Although these plugins have been checked by 'flutter_map' maintainers, we do not accept responsibility for any issues or threats posed by independently maintained plugins.
Use plugins at your own risk.
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.
To help choose between one of these plugins or a DIY solution, see:
Marker
sPolyline
s & Polygon
sThe following layers support 'interactivity':
These layers don't provide their own 'gesture' callbacks, such as onTap
These layers automatically perform with Flutter APIs
This means layers report hit on elements through the standard Flutter hit system, and can therefore be detected & handled externally through standard widgets: see Detecting hits & 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
You may be used to using widgets such as GestureDetector
or MouseRegion
to detect gestures on other normal widgets. These widgets ask the child to decide whether they were hit, before doing their own logic - e.g. converting the hit to the appropriate callback depending on the gesture.
Because flutter_map's layers are just widgets, they can also be wrapped with other widgets and inserted into the map's children
.
This means you can simply wrap layers with GestureDetector
s (for example) which will execute callbacks when the layer is hit. Layers tell Flutter they were hit only if at least one of their elements (such as a Polygon
) were hit.
Here's an example of how you would detect taps/clicks on polygons, and convert a cursor to a click indicator when hovering over a polygon:
class _InteractivityDemoState extends State<InteractivityDemo> {
Widget build(BuildContext context) {
return FlutterMap(
// ...
children: [
// ...
MouseRegion(
hitTestBehavior: HitTestBehavior.deferToChild,
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
// ...
},
child: PolygonLayer(
// ...
),
),
),
],
);
}
);
To identify which elements (such as Polygon
s) were hit, flutter_map APIs are required:
A LayerHitNotifier
exposes results of hit tests
Elements may have metadata known as hitValue
s attached, which identify that specific element - these are then exposed by the hit notifier's events/values.
The entire system may be strongly typed through type parameters on various parts, if all the hitValue
s within a layer share the same type
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
:
class _InteractivityDemoState extends State<InteractivityDemo> {
Widget build(BuildContext context) {
return FlutterMap(
// ...
children: [
// ...
PolygonLayer<String>(
hitNotifier: hitNotifier,
polygons: [
// ...
],
),
],
);
}
}
These can be anything useful, and are exposed when their element is hit. Remember to set the element's type parameter.
polygons: [
Polygon<String>(
points: [],
label: "Horse Field",
hitValue: "Horse",
),
Polygon<String>(
points: [],
label: "Hedgehog House",
hitValue: "Hedgehog",
),
// ...
],
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:
final LayerHitResult<String>? result = hitNotifier.value;
The result exposes 3 properties:
hitValues
: the hit values of all elements that were hit, ordered by their corresponding element, first-to-last, visually top-to-bottom
coordinate
: the geographic coordinate of the hit location (which may not lie on any element)
point
: the screen point of the hit location
Therefore, it's unnecessary to use MapOptions.on...
in combination with layer interactivity to detect the position of a tap.
Elements without a hit value are not included in hitValues
. Therefore, it may be empty if elements were hit but no hitValue
s were defined.
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('Eating the grass at ${result.coordinate}');
},
child: PolygonLayer<String>(
hitNotifier: hitNotifier,
polygons: [
Polygon<String>(
points: [], // overlapping coordinates with 2nd
label: "Horse Field",
hitValue: "Horse",
),
Polygon<String>(
points: [], // overlapping coordinates with 1st
label: "Hedgehog House",
hitValue: "Hedgehog",
),
],
),
),
),
],
);
}
);
You can add areas/shapes formed from coordinates to maps using PolygonLayer
and Polygon
s.
Polygon
s including labels, holes, mixed colors & opacities, and dotted bordersPolygonLayer(
polygons: [
Polygon(
points: [LatLng(30, 40), LatLng(20, 50), LatLng(25, 45)],
color: Colors.blue,
),
],
),
PolygonLayer
s and Polygons
s support hit detection and interactivity.
The PolygonLayer
paints its Polygon
s across all visible worlds by default. This can be changed.
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.
The example application includes a stress test which loads multiple Polygon
s from an optimized format, with a total of 138,000 points.
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 Path.combine
constructor. This allows for more intricate PathOperation
s, such as difference
and union
.
This gives the correct/intended/best visual results on native platforms. It's the default on native (non-web) platforms.
However, on the web, it causes major visual glitches due to a Flutter issue.
Additionally, it has slightly worse performance (especially at scale) than Even/Odd. 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).
The PolygonPainterFillMethod.evenOdd
option uses more simple Flutter APIs: the PathFillType
.evenOdd
rule.
This gives the best performance, and works on the web. This is the default when targeting the web.
However, it yields unintended results in certain edge cases when polygons intersect when Inverted Filling is used, or when polygon holes can intersect with other holes.
.evenOdd
settingOn 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 https://github.com/flutter/flutter/issues/124675 and https://github.com/flutter/flutter/issues/149743 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.
'flutter_map' doesn't provide any public methods to manipulate polygons, as these would be deemed out of scope.
However, some useful methods can be found in libraries such as 'latlong2' and 'poly_bool_dart'. 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).
flutter_map wants to help keep map data available for everyone. One of the largest sources of this data is OpenStreetMap. OpenStreetMap data powers the majority of non-proprietary maps - from actual map tiles/images to navigation data - in existence today. The data itself is free for everyone under the ODbL.
The OpenStreetMap Foundation run OpenStreetMap as a not-for-profit. They also provide a public tile server at https://tile.openstreetmap.org, which is run on donations and volunteering time. This server is used throughout this documentation for code examples, and in our demo app.
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".
This reduces the strain on tile servers, improves compliance with their policies, and has numerous other benefits for your app!
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.
The OpenStreetMap tile server is NOT free to use by everyone.
Data collected by OSM 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.)
flutter_map (*) This represents the of FM users on non-web platforms.
1610
53
Mozilla/5.0 QGIS/*
1155
358
Mozilla/5.0 ...
476
33
com.facebook.katana
263
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
And looking in more detail at the User-Agent's making up the primary 'flutter_map (*)' agent, on 2025/05/28:
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
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.
This policy is completely unrelated to the OpenStreetMap Foundation, and was the sole collective decision of the maintainers.
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 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! In any case, the OpenStreetMap tile server doesn't offer uptime guarantees, which may be important to your business.
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:
Should any users or patterns of usage nevertheless cause problems to the service, access may still be blocked without prior notice.
If your project uses a very large number of tiles, even if it would otherwise meet the requirements, consider switching to a different server.
Also note all the other requirements, which may require you to make adjustments to your project...
Check the OSM policy for all the adjustments you might need to make. Here's some common ones:
Enable conforming caching
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
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
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 for the TileLayer
.
If you're appropriately using the servers, you can disable the console warnings. This will also disable any future blocks.
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 leading or trailing 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:
flutter run --dart-define=flutter.flutter_map.unblockOSM="_____"
You can also add this to your IDE's configuration to automatically pass this argument when running from your IDE.
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.
The best way to do this is on the dedicated GitHub issue, or on the Discord 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 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 here. 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.
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
// + many other options
),
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.
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.
See Offline Mapping for detailed info and potential approaches to supporting offline users.
Set the tileProvider
to AssetTileProvider()
Set the urlTemplate
to the path to each tile from the assets directory, using the placeholders as necessary. For example:
assets/map/{z}/{x}/{y}.png
Add each lowest level directory to the pubspec's assets listing.
Set the tileProvider
to FileTileProvider()
Set the urlTemplate
to the path to each tile within the filesystem, using the placeholders as necessary
Ensure the app has any necessary permissions to read from the filesystem
WMS tile servers have a base URL and a number of layers. flutter_map can automatically put these together to fetch the correct tiles.
Create a WMSTileLayerOptions
and pass it to the wmsOptions
parameter. Define the baseUrl
as needed, and for each layer string, add it as an item of a list passed to layers
. You may also need to change other options, check the full API documentation, or follow the example app.
TMS is also supported. Follow the instructions for the XYZ source, and enable the TileLayer.tms
setting.
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
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'>)
The TileProvider
is responsible for fetching tiles for the TileLayer
. By default, the TileLayer
creates a NetworkTileProvider
every time it is constructed. TileProvider
s are attached to the lifecycle of the TileLayer
they are used within, and automatically disposed when their TileLayer
is disposed.
However, this can cause performance issues or glitches for many apps. For example, the HTTP client can be manually constructed to be long-living, which will keep connections to a tile server open, increasing tile loading speeds.
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 CancellableNetworkTileProvider for more information.
See Tile Providers for more information about tile providers generally.
Retina mode improves the resolution of map tiles, an effect particularly visible on high density (aka. retina) displays.
Raster map tiles can look especially pixelated on retina displays, so some servers support high-resolution "@2x" tiles, which are tiles at twice the resolution of normal tiles.
Where the display is high density, and the server supports retina tiles - usually indicated by an {r}
placeholder in the URL template - it is recommended to enable retina mode.
To enable retina mode in these circumstances, use the following:
retinaMode: RetinaMode.isHighDensity(context),
Note that where tiles are larger than the standard x256px (such as x512px), retina mode can help make them appear very similar to x256px tiles, but still retain the other benefits of larger tiles. In this case, consider fixing retinaMode
to true
, depending on your own tests.
Set the maxNativeZoom
parameter to the maximum zoom level covered by your tile source. This will make flutter_map scale the tiles at this level when zooming in further, instead of attempting to load new tiles at the higher zoom level (which will fail).
You can also set MapOptions.maxZoom
, which is an absolute zoom limit for users. It is recommended to set this to a few levels greater than the maximum zoom level covered by any of your tile layers.
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.
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.
A TileUpdateTransformer
restricts and limits TileUpdateEvent
s (which are emitted 'by' MapEvent
s), which cause tiles to update.
For example, a transformer can delay (throttle or debounce) updates through one of the built-in transformers, or pause updates during an animation, or force updates even when a MapEvent
wasn't emitted.
For more information, see: