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...
FMTC is licensed under GPL-v3.
If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution. For more information, please see (Proprietary) Licensing.
This page guides you through a simple, fast setup of FMTC that just enables basic browse caching, without any of the cool features that you can discover throughout the rest of this documentation.
Depend on the latest version of the package from pub.dev, then import it into the appropriate files of your project.
Perform the startup procedure to allow usage of FMTC's APIs and allow FMTC to spin-up the underlying connections & systems.
Here, we'll use the built-in, default 'backend' storage, which uses ObjectBox. We'll perform the intialisation just before the app starts, so we can be sure that it will be ready and accessible throughout the app, at any time.
Create a container that is capable of storing tiles, and can be used to and bulk download.
Here, we'll create one called 'mapStore', directly after initialisation. Any number of stores can be created, at any point!
Add FMTC's specialised TileProvider
to the TileLayer
, to enable browse caching, and retrieval of tiles from the specified store.
Double check that the name of the store specified here is the same as the store created above!
You should now have a basic working implementation of FMTC that caches tiles for you as you browse the map!
There's a lot more to discover, from store management to bulk downloading, and from statistics to exporting/importing.
Before using FMTC, especially to bulk download or import/export, ensure you 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.
For example, OpenStreetMap's tile server forbids bulk downloading: https://operations.osmfoundation.org/policies/tiles. And Mapbox has restrictions on importing/exporting from outside of the user's own device.
For testing purposes, check out the testing tile server included in the FMTC project: Testing Tile Server.
Hi there 👋 My name is Luka, but I go by JaffaKetchup!
I'm currently in full-time education in the UK studying Computer Science, Geography, and Mathematics, and have been building my software development skills alongside my education for many years. I specialise in the Flutter and Dart technologies to build cross-platform applications, and have over 4 years of experience both from a hobby and commercial standpoint.
I've worked as a senior developer with a small team at WatchEnterprise to develop their Flutter-based WatchCrunch social media app for Android & iOS.
I'm one of a small team of maintainers for Flutter's №1 non-commercially aimed mapping library 'flutter_map', for which we make internal contributions, regulate and collaborate with external contributors, and offer support to a large community. I also personally develop a multitude of extension libraries, such as 'flutter_map_tile_caching'. In addition, I regularly contribute to OpenStreetMap, and continously improve my skills with personal experimental projects.
I'm eager to earn other languages, and more than happy to talk about anything software development related!
Sponsorships & donations allow me to further my education, whilst spening even more time developing the open-source projects that you use & love. I'm grateful for any amount you can spare, all support means a lot to me :) If you can't support me financially, please consider leaving a star and a like on projects that worked well for you.
I'm extremely greatful for any amount you can spare!
Many thanks to all my supporters, donations of all sizes mean a lot to me, and encourage and enable me to continue my work on FMTC and other open-source projects.
In no particular order:
+ more anonymous or private donors
And also a thank you to the businesses listed in #trusted-by-many, and more, for abiding by the GPL license of this repository.
A plugin for 'flutter_map' providing advanced offline functionality
In addition to our generous , FMTC is also trusted and all around the world. Here's just a few!
Easy! Take a look at Google Maps, or Strava, or whichever other app of your choice.
This project is wholly open source and funded by generous supporters like you! Any amount you can spare that you think FMTC deserves is hugely appreciated, and means a lot to me :)
FMTC is licensed under GPL-v3.
If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution.
Not quite sure about something? No problem, I'm happy to help!
Please get in touch via the correct method below for your issue, and I'll be there to help ASAP!
FMTC is licensed under GPL-v3.
If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution.
I am not a lawyer, and this information is to the best of my understanding only. You are urged to read the license yourself for a thorough understanding.
This project is released under GPL v3. For detailed information about this license, see . summarises the license with the following paragraph:
Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights.
Essentially, whilst you can use this code within commercial projects, they must not be proprietary - they incorporate this 'licensed work' so they must be available under the same license. You must distribute your source code (at least on request) (under the same GPL v3 license) to anyone who uses your program.
I learnt (and am still learning) to code with free, open-source software due to my age and lack of money, and for that reason, I believe in promoting open-source wherever possible to give equal opportunities to everybody, no matter their age, financial position, or any other characteristic. I'm not sure it's fair for commercial & proprietary applications to use software made by people for free out of generosity without giving back to the ecosystem or maintainer(s). On the other hand, I recognise that commercial businesses may want to use my projects for their own proprietary applications, and are happy to support me, and I am also trying to make a small amount of money from my projects, by donations and by selling licenses!
Therefore, if you would like a license to use this software within a proprietary application, I am willing to sell a (preferably yearly) license. If this seems like what you'd be interested in, please do not hesitate to get in touch at . Please include details of your project if you can, and the approximate scale/audience for your app; I try to find something that works for everyone, and I'm happy to negotiate! If you're a non-profit organization, I'm happy to also offer an alternative license for free!
Want to see who else uses FMTC? Check out !
Because FMTC has complex internals that rely heavily on external factors and preconditions being met, it is important to properly anticipate, catch, and handle errors & exceptions.
Errors & exceptions in FMTC come in two primary forms:
Errors: usually generated by FMTC & descended from FMTCBackendError
These indicate incorrect usage on the user's part - for example, an incorrect assumption such as usage of a method on a store before initialisation. These may also be non-specfic types, such as ArgumentError
.
Exceptions: usually generated by the depdenencies of backends, such as databases
These indicate some sort of unexpected failure, such as a database reaching its maximum size limit during a write operation. These are usually not generated/typed by FMTC, and so are often of types from other libraries, such as ObjectBox when using the default FMTCObjectBoxBackend
.
The difference between errors & exceptions extends beyond FMTC, and is a general concept in Dart. See about the difference between Exception
s and Error
s.
Exceptions may be caught using standard try
/catch
blocks (it's not usually recommended to catch FMTCBackendError
s).
FMTC automatically adjusts and changes the thrown StackTrace
to include useful additional info, and ensures that the trace is followed across the many isolates and asynchronous gaps (eg. streams) of the FMTC internals. When creating a bug report, please include the full trace, including debug info at the end, if any is available.
It can usually be assumed that the requested operation did not & will not complete when an exception is thrown from it.
One particular place where exceptions can occur more frequently is during initialisation. The code sample above includes a try
/catch
block to catch these errors. If an exception occurs at this point, it's likely unrecoverable (for example, it might indicate that the underlying database has been corrupted), and the best course of action is often to manually delete the FMTC root directory from the filesystem.
The default directory can be found and deleted with the following snippet (which requires 'package:path' and 'package:path_provider':
FMTC is licensed under GPL-v3.
If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution. For more information, please see .
FMTC relies on a self-contained 'environment', called a , that requires initialisation (and configuration) before it can be used. This allows the backend to start any necessary seperate threads/isolates, load any prerequisites, and open and maintain a connection to a database. This environment/backend is then accessible internally through a() singleton, so initialisation is not required again.
Initialisation should be performed before any other FMTC or backend methods are used, and so it is usually placed just before runApp
, in the main
method. This shouldn't have any significant effect on application startup time.
If initialising in the main
method before runApp
is called, ensure you also call WidgetsFlutterBinding.ensureInitialised()
prior to the backend initialisation.
Do not call any other FMTC methods before initialisation. Doing so will cause a RootUnavailable
error to be thrown.
Do not attempt to initialise the same backend multiple times, or initialise multiple backends simultaenously. Doing so will cause a RootAlreadyInitialised
error to be thrown.
Avoid using FMTC in a seperate thread/Isolate
. FMTC backends already make extensive use of multi-threading to improve performance.
If it is essential to use FMTC in a seperate thread, ensure that the initialisation is called in the thread where it is used. Be cautious of using FMTC manually across multiple threads simultaneously, as backends may not properly support this, and unexpected behaviours may occur.
It is also possible to un-initialise FMTC and the current backend. This should be rarely required, but can be performed through the uninitialise
method of the backend if required. Initialisation is possible after manual uninitialisation.
FMTC supports attachment of any custom storage mechanism, through an FMTCBackend
. This allows users to pick their favourite database engine, or conduct in-memory testing.
This is not an issue for the majority of applications. However, ObjectBox is known to be (rightly or wrongly) banned as a dependency from apps on F-Droid (last checked September 2024).
Future updates to FMTC will implement alternative backends using other libraries, and the default/preferred backend may indeed change in future.
FMTC is licensed under GPL-v3.
If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution. For more information, please see .
Looking to start using FMTC in your project? Check out the guide!
This is the recommended method of installing this package as it ensures you only receive the latest stable versions, and you can be sure pub.dev is reliable.
Just import the package as you would normally, from the command line:
If you urgently need the latest version, a specific branch, or a specific fork, you can use this method.
Commits available from Git (GitHub) may not be stable. Only use this method if you have no other choice.
First, add the normal dependency following the instructions. Then, add the following lines to your pubspec.yaml file under the dependencies_override
section:
After installing the package, import it into the necessary files in your project:
Also ensure you've followed flutter_map's installation instructions!
This package contains a full example application - prebuilt for Android and Windows by GitHub Actions - showcasing the most important features of this package and its modules.
The example app isn't intended for beginners or as a starting point for a project. It is intended for evaluation purposes, to discover FMTC's capabilities, and how it might be implemented into an app.
To start using FMTC in your own app, please check out the guide instead.
The example application pairs perfectly with the testing tile server included in the FMTC project: !
If you can't build from source for your platform, our GitHub Actions CI system compiles the example app to artifacts for Windows and Android, which just require unzipping and installing the .exe or .apk found inside.
Note that these artifacts are built automatically from the , so may not reflect the the latest release on pub.dev.
If you need to use the example app on another platform, you can build from source, using the 'example' directory of the repository.
FMTC supports bulk downloading from any tile server, so you can choose whichever one suits you best.
And with export/import functionality, user's can download regions just once, then keep the download themselves for another time. Or, you can provide a bundle of tiles to all your user's, while still allow it to be updated per-user in future!
For (proprietary) licensing information, as FMTC is licensed under GPL-v3, please see .
For bug reports & feature requests:
For implementation/general support: The #plugin channel on the
For other inquiries and licensing:
Only one backend is built-into FMTC: the FMTCObjectBoxBackend
. This backend uses the to store data.
ObjectBox has a complex license model - the build time dependency is open-source, whilst the native library runtime only dependency is under a closed-source (but relatively relaxed) (that is liable to change at ObjectBox's will).
For more information, please see: .
Although FMTC will compile on the web, the default FMTCObjectBoxBackend
(see ) does not support the web platform: the initialise
and uninitialise
methods (see ) will throw UnsupportedError
s, and other methods will throw RootUnavailable
.
◉ 📲
Integrated Caching × Bulk Downloading
Get both dynamic browse caching that works automatically as the user browses the map, and bulk downloading to preload regions onto the user's device, all in one convenient, integrated API!
◉ 🏃
Ultra-fast & Performant
Multi-threaded setup to minimize load on main thread, even when browse caching
Streamlined internals to reduce memory consumption
Successfully downloaded tiles aren't re-downloaded when an unexpectedly failed download is recovered
◉ 🧩
Import & Export
Export and share stores, then import them later, or on other devices! You could even remote control your organization's devices, by pushing tiles to them, keeping your tile requests (& costs) low!
◉ 💖
Quick To Implement & Easy To Experiment
Stores contain any metadata associated with them, cached statistics, and maintain a reference to all the tiles that belong to it.
They are referenced by name, the single argument of FMTCStore
.
Ensure names of stores are consistent across every access. "Typed"/code-generated stores are not provided, to maintain flexibility.
Construction of an FMTCStore
object does not imply/infer that the underlying store has been created and is ready for use. Therefore, a store will require creation via its StoreManagement
object (accessed via FMTCStore.manage
) before it can be used.
After a store reference is constructed, the following actions can be performed with it:
FMTC uses a root and stores to structure its data.
There is generally a single root (which generally corresponds to a single ), which contains multiple stores. Cached tiles can belong to multiple stores, which keeps duplication minimized.
The structures use the ambient backend when a method is invoked on it, not at construction time.
Therefore, it is possible to construct an FMTCStore
/FMTCRoot
(see below) before initialisation, but 'using' it will throw RootUnavailable
.
Keep the number of stores small to improve performance!
FMTC isn't internally optimized to handle large numbers of stores within the database queries and processing, and algorithms often have polynomial time complexities based on the number of stores. This is because of the feature that tiles can belong to multiple stores, and therefore a lot of extra work is required to keep track of this all.
In addtion to the BrowseStoreStrategy
(), FMTCTileProvider
s also take a BrowseLoadingStrategy
in the FMTCTileProvider.loadingStrategy
argument, which determines whether the network or cache is preferred during browse caching, and how to fallback if the primary method doesn't work.
Strategy | Preferred Method | Fallback Method |
---|
FMTCTileProvider
must have at least one store specified. Use a standard NetworkTileProvider
if caching is disabled.
StoreStats
, accessed via FMTCStore().stats
, allows access to cached statistics, as well as retrieval of a recent tile (as an image), and the watching over changes in the store.
v10 focuses on completing the 'tiles-across-stores' functionality from v9, by bringing it to browse caching, which huge amounts of customizability and relexibility.
Check out the CHANGELOG: ! This page only covers breaking changes, not feature additions and fixes.
Please consider donating: ! Any amount is hugely appriciated!
with
No need to bore your users to death anymore! Bulk downloading is super-fast, and can even reach speeds of over 1000 tiles per second. Existing cached tiles can be displayed on the map almost instantly.
A basic caching implementation can be setup in four quick steps, and shouldn't even take 5 minutes to set-up. Check out our instructions.
Ready to experiment with bulk downloading, but don't want to make costly and slow tile requests? Check out the testing tile server included in the FMTC project: !
Management: manage
Control the store: create it, delete it, rename it, etc.
Statistics: stats
Retrieve information about the store and its contents
Metadata: metadata
Access simple persistent storage, with no direct influence on FMTC's functioning
Bulk Download: download
Prepare/plan, start, and manage bulk downloads
Integrate With TileLayer
Get a specialised TileProvider
that allows flutter_map to access cached tiles
| Cache | None |
| Cache | Network |
| Network | Cache |
Standard Tile Provider | Network | None |
See #id-4.-connect-to-flutter_map in the Quickstart guide for an example.
To integrate caching (known as 'browse caching' in FMTC), FMTC provides FMTCTileProvider
, which can be set in TileLayer.tileProvider
. The tile provider can communicate with the cache and get tiles from the network.
link
To know which stores to use from the cache, one or more store names may be specified, each with a BrowseStoreStrategy
.
Additionally, the argument otherStoresBehavior
may be used to set the BrowseStoreStrategy
for all unspecified stores. If null
, then no unspecified stores will be used.
BrowseStoreStrategy
The BrowseStoreStrategy
determines when tiles should be written to a store during browse caching. There are 3 available stratefies:
read
Only read tiles from the associated store
readUpdate
In addition to reading tiles, update existing tiles, but don't cache any new ones
readUpdateCreate
Read, update, and create tiles
Avoid getting/constructing the TileProvider
from within the build
er, especially if the widget is rebuilt frequently.
To get an instance of the tile provider when only using a single store (except for otherStoresBehavior
), use the following method:
To get an instance of the tile provider when using multiple stores, use the following method:
To get an instance of the tile provider that will use all stores with the same StoreReadWriteBehavior
, use the following method:
FMTC also provides the ability to bulk download areas of maps in one-shot, known as 'regions'. There are multiple different types/shapes of regions available: #types-of-region.
Before using FMTC, especially to bulk download or import/export, ensure you 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.
For example, OpenStreetMap's tile server forbids bulk downloading: https://operations.osmfoundation.org/policies/tiles. And Mapbox has restrictions on importing/exporting from outside of the user's own device.
For testing purposes, check out the testing tile server included in the FMTC project: Testing Tile Server.
Downloading is extremely efficient and fast, and uses multiple threads and isolates to achieve write speeds of hundreds of tiles per second (if the network/server speed allows). After downloading, no extra setup is needed to use them in a map (other than the usual Getting A Tile Provider).
It is also simple to understand and implement:
Convert that region into a downloadable region ↳ Optionally, check the number of tiles in the region before downloading
Start downloading that region ↳ Optionally, when testing, try the miniature tile server
Listen for progress events to update your user
Optionally, control (pause/resume/cancel) the download
Regions (BaseRegion
s) are geographical areas that do not yet have any of the necessary extra information to start a download (this is the responsibility ofDownloadableRegion
).
There are 5 types of BaseRegion
.
RectangleRegion
s are defined by a LatLngBounds
: two opposite LatLng
s.
This is usually all you get from most apps, so why not give your user a unique experience with some of our other region types...
CircleRegion
s are defined by a center LatLng
and radius in kilometers.
If you instead have two coordinates, one in the center, and one on the edge, you can use 'latlong2's Distance.distance()
method, as below:
LineRegion
s are defined by a list of LatLng
s, and a radius in meters.
This could be used to download tiles along a planned travel route, for example hiking or long-distance driving. Import coordinates from a routing engine, or from a GPX/KML file for maximum integration!
This region may generate more tiles than strictly necessary to cover the specified region. This is due to an internal limitation with the region generation algorithm, which uses (rotated) rectangles to approximate the actual desired shape.
This type of region may consume more memory/RAM when generating tiles than other region types.
CustomPolygonRegion
s are defined by a list of LatLng
s defining the outline of a simple polygon.
Polygons should not contain self-intersections. These may produce unexpected results.
Holes are not supported, however multiple CustomPolygonRegion
s may be downloaded at once using a MultiRegion
.
MultiRegion
s are defined by a list of multiple BaseRegion
s (which may contain more nested MultiRegion
s).
When downloading, each sub-region specified is downloaded consecutively (to ensure that any start
& end
tile range defined is respected consistently.
toOutline
is not supported by MultiRegion
.
RecoveredRegion
s aren't technically the same type of region as the others, as it doesn't inherit from BaseRegion
.
It is a hybrid of a BaseRegion
(region
), with parts of the DownloadableRegion
used to start the download, and some additional information about the recovery, such as the original store name & original download start time.
It can be converted to a DownloadableRegion
given a TileLayer
using toDownloadable
.
For more info, see Recovery.
RootStats
, accessed via FMTCRoot.stats
, allows access to cached statistics, as well as listing of all existing stores, and the watching over changes in multiple/all stores and the bulk download recovery system.
Remember that the size
and length
statistics in the root may not be equal to the sum of the same statistics of all available stores, because tiles may belong to many stores, and these statistics do not count any tile multiple times.
BaseRegion
s must be converted to DownloadableRegion
s before they can be used to download tiles.
These contain the original BaseRegion
, but also some other information necessary for downloading, such as zoom levels and URL templates.
The TileLayer
passed to the options
parameter must include both a urlTemplate
(or WMS configuration) and a userAgentPackageName
, unless it is only being used to check
the number of tiles in the region.
Before continuing to downloading the region, use check()
to count the number of tiles it will attempt to download. This is accessible through FMTCStore().download
.
The method takes the DownloadableRegion
generated above, and will return an int
number of tiles. For larger regions, this may take a few seconds.
This figure will not take into account any skipped sea tiles or skipped existing tiles, as those are handled at the time of download.
Now that you have constructed a DownloadableRegion
, you're almost ready to go.
Before you call startForeground
(via FMTCStore().download
) to start the download, check out the customization parameters:
parallelThreads
(defaults to 5)
The number of simultaneous download threads to run
maxBufferLength
(defaults to 200)
The number of tiles to temporarily persist in memory before writing to the cache
skipExistingTiles
(defaults to false
)
Whether to avoid re-downloading tiles that have already been cached
skipSeaTiles
(defaults to true
)
Whether to avoid caching tiles that are entirely sea (based on whether they have the same pixels as the tile at z17, x0, y0, which is assumed to be sea)
rateLimit
The maximum number of tiles that can be attempted per second
maxReportInterval
(defaults to 1 second)
The duration in which to emit at least one DownloadProgress
event
Ensure skipSeaTiles
is disabled when downloading from a server where the tile at z17, x0, y0 is not a consitently colored sea tile, or where different sea tiles look different, such as with satellite imagery. FMTC cannot yet skip sea tiles that match these conditions.
Therefore, where speed is significant and the download is unlikely to be unexpectedly interrupted, consider disabling download recovery.
Before using FMTC, especially to bulk download or import/export, ensure you 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.
Whilst not recommended, it is possible to start and control multiple downloads simultaneously, by using a unique Object
as the instanceId
argument. This 'key' can then later be used to control its respective download instance.
Note that this option may be unstable.
The startForeground
method returns a (non-broadcast) Stream
of DownloadProgress
events, which contain information about the overall progress of the download, as well as the state of the latest tile attempt.
In addition to display each individual event to your user, you may also need to keep track of information from across multiple DownloadProgress
events. In this case, you'll likely need to use the latestTileEvent
getter to access the latest TileEvent
object, and keep track of its properties.
For example, you may wish to keep a list of all the failed tiles' URLs.
However, there are 3 important things to keep in mind when doing this:
There are 3 methods available to control a download, and 1 method to check the download's current state.
It may take some time to perform these operations if the download has a low TPS, as each thread checks the current state/awaits a signal between each of their tiles.
pause
& cancel
are asynchronous for this reason, and will complete when all threads are paused or cancelled. resume
returns immediately.
If your user needs to temporarily pause the download, with the ability to resume it at some point later (within the same app session), use pause
and resume
.
pause
allows all threads to finish the tile they're currently downloading, then forces them to wait for a signal sent by resume
before they can download their next tile. It does not write any buffered tiles to the store.
Use isPaused
to check whether the download is currently paused.
If your user needs to stop the download entirely, use cancel
.
cancel
allows all threads to finish the tile they're currently downloading, then forces them to cleanup and 'kill' themselves. Any buffered tiles are written to the store before the future returned by cancel
is completed.
It is safe to use cancel
after pause
without resume
ing first.
disableRecovery
(defaults to false
)
Whether to avoid registering this download with the system for safe recovery if the download fails
The system can slow a download, as it must be regularly updated with the latest progress of the download, and this data is not currently batched (so it occurs for every downloaded tile). This is an optimization planned for later implementation.
For example, OpenStreetMap's tile server forbids bulk downloading: . And Mapbox has restrictions on importing/exporting from outside of the user's own device.
For testing purposes, check out the testing tile server included in the FMTC project: .
To reflect the information from a single event back to the user, use a StreamBuilder
, and build the UI dependent on the 'snapshots' of the stream. If you need to keep track of information from across multiple events, see below.
To store and retrieve tiles, FMTC uses a tile's 'storage-suitable UID'. Before a tile is created/updated/read from the cache, the tile real URL may first be transformed. A storage-suitable UID must refer to the same tile from the same server every time (unless the server changes and serves different tiles from the same path).
A storage-suitable UID may be tile's own real URL, because the URL is guaranteed to refer only to that tile from that server. However, some parts of the tile URL should not be stored. For example, an API key transmitted as part of the query parameters should not be stored. This is because, if the API key changes, the cached tile will still use the old UID containing the old API key, and thus the tile will never be retrieved from storage, even if the image is the same: the UID does not necessarily refer to the same tile (it is reliant on the API key).
FMTCTileProvider.urlTransformer
(& the urlTransformer
elsewhere) takes the tile's real URL as input, and should output the storage-suitable UID. By default, it uses the identity function and returns the input - this is not always suitable as discussed above.
The callback will be passed to a different isolate: therefore, avoid using any external state that may not be properly captured or cannot be copied to an isolate spawned with Isolate.spawn
(see SendPort.send
).
RootRecovery
, accessed via FMTCRoot.recovery
, allows access to the bulk download recovery system, which is designed to allow rescue (salvation and restarting) of failed downloads when they crashed due to an unexpected event.
RecoveredRegion
RecoveredRegion
s are wrappers containing recovery & some downloadable region information, around a DownloadableRegion
.
Once a RecoveredRegion
has been retreived, it contains the original BaseRegion
in the region
property.
To create a DownloadableRegion
using the other available information with a provided TileLayer
, use toDownloadable
.
A RecoveredRegion
(and a DownloadableRegion
generated from it) will point to only any remaining, un-downloaded tiles from the failed download.
The start
tile will be adjusted from the original to reflect the progress of the download before it failed, meaning that tiles already successfully cached (excluding buffered) will not be downloaded again, saving time and network transfers.
The end
tile will be either the original, or the maximum number of tiles normally in the region (which will have no resulting difference than null
, but allows for a quick estimate of the number of remaining tiles to be made without needing to recheck
the entire region).
In a one word answer: Yes.
FMTC aims to provide all the functionality you will need for advanced offline mapping in your app, and the unique features that will help set your app apart from the competition, in a way that requires little knowledge of the internals of 'flutter_map' and other caching fundamentals, and little effort from you (for most setups).
However, this doesn't mean there aren't any other options to consider! The flutter_map documentation gives a good overview of the different types of caching.
In general, there's a few reasons why I wouldn't necessarily recommend using FMTC:
You're not planning to make use of bulk downloading or import/export functionality
You don't need the fine grained control of stores (and their many-to-many relationship with tiles that keeps duplication minimal across them)
You want to ship every user a standardized tileset, and you don't really need other functionality after that point
Although FMTC will still handle all these situations comfortably, other options may be better suited, and/or more lightweight.
FMTC is an all-in-one solution. It specialises in the functionalities which are difficult and time-consuming to get right (such as bulk downloading), and their integration with the essential function of browse caching, through a clean, unified API, and a clean, decluttered, and fast backend.
Other libraries or DIY solutions may not be all-in-one, but they may be all that's required for your app. Caching alone is not difficult or time consuming to setup yourself, either through a custom TileProvider
backed by a non-specialised image caching provider such as 'cached_network_image', or the other browse-caching-only flutter_map plugin 'flutter_map_cache' if you need to save even more time at the expense of more external dependencies.
Another consideration for non-GPL-licensed projects is abiding by FMTC's GPL license. This is especially true for proprietary products.
For more information about licensing, please see (Proprietary) Licensing.
If you're still not sure, please get in touch: #get-help! I'm always happy to offer honest guidance :)
StoreManagement
, accessed via FMTCStore().manage
, allows control over the store and its contents.
Roots contain cached statistics and recovery information.
Because there is only a single root at any time, the root is not named. Instead, it is accessed via FMTCRoot
- and all members are static.
After the root reference is constructed, the following actions can be performed with it:
Management of the root is not provided by FMTCRoot
. Instead, the backend should implement any necessary methods.
Statistics: stats
Retrieve information about the store and its contents
Recovery: recovery
Recovery from unexpectedly failed bulk downloads
External: external
Export & import store archives
StoreMetadata
, accessed via FMTCStore().metadata
, allows access and control over a simple peristent storage mechanism, designed for use with custom data/properties/fields tied to the store (such as a CacheBehavior
or URL template).
Data is interpreted in key-value pair form, where both the key and value are String
s. Internally, the default backend stores it as a flat JSON structure. The metadata is stored directly on the store: if the store is deleted, it is deleted, and an exported store will retain its metadata. More advanced requirements will require use of a seperate persistance mechanism.
Remember that metadata
does not have any effect on internal logic: it is simply an auxiliary method of storing any data that might need to be kept alongside a store.
Before using FMTC, especially to bulk download or import/export, ensure you 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.
For example, OpenStreetMap's tile server forbids bulk downloading: https://operations.osmfoundation.org/policies/tiles. And Mapbox has restrictions on importing/exporting from outside of the user's own device.
For testing purposes, check out the testing tile server included in the FMTC project: Testing Tile Server.
FMTC allows stores (including all necessary tiles and metadata) to be exported to an 'archive'/a standalone file, then imported on the same or a different device!
FMTC does not support exporting tiles to a raw Z/X/Y directory structure that can be read by other programs.
For example, this can be used to create backup systems to allow user's to store maps for later off-device, sharing/distribution systems, or to distribute a preset package of tiles to all users without worrying about managing IO or managing assets, and still allowing users to update their cache afterward!
Archives are backend specific. They cannot necessarily be imported by a backend different to the one that exported it.
External functionality is accessed via FMTCRoot.external('~/path/to/file.fmtc')
.
The path should only point to a file. When used with export
, the file does not have to exist. Otherwise, it should exist.
The path must be accessible to the application. For example, on Android devices, it should not be in external storage, unless the app has the appropriate (dangerous) permissions.
On mobile platforms (/those platforms which operate sandboxed storage), it is recommended to set this path to a path the application can definitely control (such as app support), using a path from 'package:path_provider', then share it somewhere else using the system flow (using 'package:share_plus').
The import()
method copies the specified archive to a temporary location, then opens it and extracts the specified stores (or all stores if none are specified) & all necessary tiles, merging them into the in-use database. The specified archive must exist, must be valid, and should contain all the specified stores, if applicable.
There is no support for directly overwriting the in-use database with the archived database, but this may be performed manually while FMTC is uninitialised.
There must be enough storage space available on the device to duplicate the entire archive, and to potentially grow the in-use database.
This is done to preserve the original archive, as this operation writes to the temporary archive. The temporary archive is deleted after the import has completed.
The returned value is complex. See the API documentation for more details:
If an importing store has the same name as an existing store, a conflict has occurred, because stores must have unique names. FMTC provides 4 resolution strategies:
skip
Skips importing the store
replace
Deletes the existing store, replacing it entirely with the importing store
rename
Appends the current date and time to the name of the importing store, to make it unique
merge
Merges the two stores' tiles and metadata together
In any case, a conflict between tiles will result in the newer (most recently modified) tile winning (it is assumed it is more up-to-date).
If the user must be given a choice as to which stores to import (or it is helpful to know), and it is unknown what the stores within the archive are, the listStores
getter will list the available store names without performing an import.
The same storage pitfalls as import
exist. listStores
must also duplicate the entire archive.
To track (eg. for debugging and logging) the internal tile loading mechanisms, FMTCTileProvider.tileLoadingInterceptor
may be used.
For example, this could be used to debug why tiles aren't loading as expected (perhaps in combination with TileLayer.tileBuilder
& ValueListenableBuilder
as in the example app & below), or to perform more advanced monitoring and logging (than the hit & miss statistics provide).
The interceptor uses a ValueNotifier
, which allows FMTC internals to notify & push updates of tile loads, and allows the owner to listen for changes as well as retrieve the latest update (value
) immediately.
The object within the ValueNotifier
is a mapping of TileCoordinates
to TileLoadingInterceptorResult
s. These result objects contain multiple fields, which can be explored in the Full API Documentation:
link
See the following example, which demonstrates how to listen directly to it, and how to use it in combination with a custom tileBuilder
using a ValueListenableBuilder
to listen to it in a widget tree.
A miniature tile server, intended to test and calibrate FMTC, has been included in the project.
Avoid making too many costly and slow requests to your chosen tile server during development by using this miniature tile server!
For internal testing and development purposes, it also doubles down as a handy way to test your application without making too many costly and slow requests to your chosen tile server. When in use with the example application, it can handle over 2000 tiles/second.
It is a very simple web HTTP server written in Dart, that responds to all* requests with a tile. There is a theoretically 90% chance that this tile will be a specific land tile, and a 10% chance that it will be a sea tile - designed to test the sea tile skipping functionality. There are only these two tiles - it is not a full tile server.
To use this tile server:
The tile server is hardcoded to use standard HTTP port 7070 to serve content, which is usually free. Other programs must not be using this port.
Download/compile & start the tile server (no permanent installation required)
On Windows or Linux Download a copy of the latest '<platform>-ts' artifact from GitHub Actions, and run the executable inside: https://nightly.link/JaffaKetchup/flutter_map_tile_caching/workflows/main/main
On other platforms Clone the FMTC GitHub repository to your device, then run '/tile_server/bin/tile_server.dart' manually
Use the following URL to connect to it
From the local device: http://localhost:7070/{z}/{x}/{y}.png
From the same network (on another device): http://<your-local-ip>:7070/{z}/{x}/{y}.png
To find your local IP address, follow the instructions for your OS here
Control the tile server using keyboard key presses in the console window
q
: Release port 7070 and quit the executable
UP arrow: Increase the artificial delay between request and response by 2ms
DOWN arrow: Decrease the artificial delay between request and response by 2ms
The export()
method copies the stores, along with all necessary tiles, to a seperate archive at the specified location (creating it if non-existent, overwriting it otherwise), in the FMTC (.fmtc) format.
The specified stores must contain at least one tile.