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.
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.
Looking to start using FMTC in your project? Check out the Quickstart 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 #from-pub.dev 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:
You may need to follow ObjectBox's installation instructions for your platform: https://docs.objectbox.io/getting-started.
If building for Android, and Gradle errors on build due to the 'ndkVersion' being too low, edit your Gradle config as shown in the error message.
Although FMTC will compile on the web, the default FMTCObjectBoxBackend
(see #backends) does not support the web platform: the initialise
and uninitialise
methods (see Initialisation) will throw UnsupportedError
s, and other methods will throw RootUnavailable
.
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 Quickstart guide instead.
The example application pairs perfectly with the testing tile server included in the FMTC project: Testing Tile Server!
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 'master' branch, 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.
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.
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.
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':
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.
"Browse caching" occurs as the map loads tiles as the user interacts with it (or it is controlled by a MapController
).
To inject the browse caching logic into flutter_map's tile loading process, FMTC provides a custom TileProvider
: FMTCTileProvider
.
Setup is quick and easy in many cases, but this guides through every step in the order in which it should be done to ensure best performance and all factors have been considered.
Remember that a store can hold tiles from more than one server/template URL.
Before you can get started, make sure you've & one or more !
Where & how you choose to construct the FMTCTileProvider
object has a major impact on performance and tile loading speeds, so it's important to get it right.
Minimize reconstructions of this provider by constructing it outside of the build
method of a widget wherever possible. Because it is not a const
ant constructor, and it will be in a non-const
ant context (TileLayer
), every rebuild will trigger a potentially expensive reconstruction.
However, in many cases, such as where one or more properties (as described in following stages) depends on inherited data (ie. via an InheritedWidget
, Provider
, etc.), this is not possible.
In this case, read the tip in the carefully. In summary, you should construct as many arguments as possible outside of the build
method, particularly a HTTP Client
and any objects or callbacks which do not have a useful equality/hash code themselves.
The tile provider can interact in multiple ways with multiple stores at once, affording maximum flexibility. Defining how it interacts with these stores will be done in following stages, but you first need to define which stores it will interact with.
How exactly you need to define the stores depends on how much flexibility you need:
It is more common you will want to interact with just one or a defined set of stores at any one time. In this case, use the default constructor. You'll need to choose how (what strategy) it interacts with each store in following stages.
The parameters you will need to use will depend on how advanced your use-case is, but it will progress in a linear fashion:
The mandatory stores
argument takes a mapping of store names to the strategy to be used for that store.
If you want to apply another strategy to all other available stores (whose names are not in the stores
mapping), use the otherStoresStrategy
argument.
If you define that strategy, but you still want to disable interaction with some stores altogether, add these stores to the stores
mapping with associated null
values. (If otherStoresStrategy
is not defined, stores mapped to null
have no difference to if they were not included in the mapping at all.)
If you want it to interact with all available stores (those which have been created), all in the same way (see following stages), use the allStores
named constructor, and that's it! FMTC will efficiently apply the strategy you choose in the next stage across all stores without you needing to track it yourself.
Remember that the provider can also read tiles from multiple stores, so this may not be necessary - but the option is there!
The BrowseStoreStrategy
s tell FMTC how it should read, update, and create tiles in the store it is associated to.
In the allStores
constructor, it is passed to allStoresStrategy
and applied to all available stores (as described in the previous stage).
Otherwise, in the default constructor, one strategy is assigned to each store, plus optionally one to all other available stores (as described in the previous stage).
There are three possible strategies:
.read
: only read tiles from the associated store
.readUpdate
: read tiles, and also update existing tiles in the associated store, if necessary
.readUpdateCreate
: read, update (if necessary), and create tiles in the associated store
The BrowseLoadingStrategy
s (previously known as CacheBehavior
s) tell FMTC the preferred source for tiles to be loaded from, and how to fallback if that source fails. It is passed to the loadingStrategy
parameter.
There are three possible priorities:
The cacheOnly
strategy essentially disables writing to the cache, and makes the chosen BrowseStoreStrategy
s above .read
redundant.
The onlineFirst
strategy may make tile loading appear slower when not connected to the Internet/network.
This is because the HTTP client may attempt to make the request anyway (Dart does not realise sometimes that the Internet is not available), in which case, the HTTP timeout set in the client must elapse before the tile is retrieved from the cache.
Also see how this strategy influences tile updates in stage 6.
To reference (enable correct creation/updating/reading of) tiles, FMTC uses a 'storage-suitable UID' derived from the tile's URL. Any one tile from the same server (style, etc. allowing) should have one storage-suitable UID which does not change.
On some servers, it may be acceptable for the UID to be the same as the tile URL. For example, the OpenStreetMap tile server URL for the tile at 0/0/0 will always be https://tile.openstreetmap.org/0/0/0.png
.
However, on some servers, the URL may change, but still point to the same desired tile. Consider the following URL: https://tile.paid.server/0/0/0.png?volatile_key=123
. In this case, the URL requires an API key to retrieve the tile. If the UID was the same as the URL, but the key changes - for example, because it was leaked and refreshed - then FMTC would be unable to reference this tile when it encounters the same URL with the different key. This would mean the tile could not be read or updated, which may significantly impact your app's functionality.
To fix this, the urlTransformer
parameter takes a callback which gets passed the tile's real URL, and should return a stable storage-suitable UID. For example, it should remove the offending query parameters.
The urlTransformer
defined here should usually be the same as the transformer defined for a bulk download. Otherwise, tiles which have been bulk downloaded may not be able to be referenced, for example if an API key changes.
If the TileLayer
used to start the bulk download uses an FMTCTileProvider
with a defined urlTransformer
as the tile provider, it will be used automatically, otherwise the bulk download also takes the urlTransformer
directly.
If the offending part of the URL occurs as in the example above - as part of a query string - FMTC provides a utility callback which can be used as the transformer to remove the offending key & value cleanly.
FMTCTileProvider.urlTransformerOmitKeyValues
takes the tile URL as input, as well as a list of keys. It will remove both the key and associated value for each listed key.
It may also be customized to use a different 'link' ('=') and 'delimiter' ('&') character, and it will remove any key<link>value
found in the URL, not just from after the '?' character.
And that's it! FMTC will handle everything else behind the scenes.
If you bulk download tiles, they'll be able to be used automatically as well.
This is the most simple case where one store exists, using the default constructor and no other parameters except a BrowseLoadingStrategy
.
In this case, there are two stores which never change, which use different BrowseStoreStrategy
s. There is also a urlTransformer
defined, using the utility method.
Note that the URL transformer callback and HTTP client have been defined outside of the FMTCTileProvider
constructor (which must lie within the build
method because it depends on inherited data).
Defining the URL transformer this way instead of an anonymous function ensures that the caching key works correctly, which improves the speed of tile loading.
Defining the HTTP client (although it is technically optional) ensures it remains open even when the provider is being repeatedly reconstructed, which means it does not have to keep re-creating connections to the tile server, improving tile loading speed. Note that it is not closed when the widget is destroyed: this prevents errors when the widget is destroyed whilst tiles are still being loaded, and there is very little potential for memory or performance leaks.
otherStoresStrategy
, and explicitly disabling a storeIn 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.
Another consideration for non-GPL-licensed projects is abiding by FMTC's GPL license. This is especially true for proprietary products.
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!
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.
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: . 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: .
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 ).
Before you can get started, make sure you've & one or more !
A region represents a geographical area only, not any of the other information required to start a download.
All types of region inherit from BaseRegion
.
RectangleRegion
s are defined by a LatLngBounds
: two opposite LatLng
s.
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 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.
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.
Regions which overlap will still have the overlapping tiles downloaded for each region.
Multi region's advantage is that it reduces the number of costly setup and teardown operations. It also means that statistic measuring applies over all sub-regions, so it does not need to be managed indepedently.
It is also possible to reconstruct the region from a RecoveredRegion
: .
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 countTiles()
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.
To start the download, use the startForeground
method on the existing store you wish to download to:
There are many options available to customize the download, which are described fully in the API reference:
The download starts as soon as the method is called; it does not wait for listeners.
Listening to the output streams of the download is something most apps will want to do, to display information to the user (unless operating in a headless mode or in the background).
There are two output streams returned as a record. One stream emits events that contain information about the download as a whole, whilst the other stream independently emits an event after the fetch/download of each tile in the region is attempted. See the API documentation for information on the exact emission frequencies of each stream. These are returned separately as the first stream emits events more frequently than the second, and this prevents tile events from needed to be repeated*.
An emitted TileEvent
may refer to a tile for which an event has been emitted previously.
See the API documentation for more information.
Listening, pausing, resuming, or cancelling subscriptions to the output streams will not start, pause, resume, or cancel the download. It will only change whether the download emits updates.
Instead, there are methods available to control the download itself.
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
.
Pausing does not interrupt any tiles that are being downloaded when pause
is invoked. Instead, the download will pause after the tile has been downloaded. pause
's returned Future
completes when the download has actually paused (or after resume
is called whilst still pausing).
Pausing also does not cause the buffer to be flushed (if buffering is in use).
If listening to the DownloadProgress
stream, an event will be emitted when pausing and resuming.
Use isPaused
to check whether the download is currently paused.
If your user needs to stop the download entirely, use cancel
.
Cancelling does not interrupt any tiles that are being downloaded when cancel
is invoked. The returned future completes when the download has stopped and been cleaned-up.
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.
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).
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: .
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 '', or the other browse-caching-only flutter_map plugin '' if you need to save even more time at the expense of more external dependencies.
For more information about licensing, please see .
If you're still not sure, please get in touch: ! I'm always happy to offer honest guidance :)
CustomPolygonRegion
s are defined by a list of LatLng
s defining the outline of a .
.cacheOnly
Cache
Failure
.cacheFirst
Cache
Network (URL)
.onlineFirst
Network (URL)
Cache
Standard tile provider
Network (URL)
Failure
FMTC uses a root and stores to structure its data.
There is generally a single root (which generally corresponds to a single backend), 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.
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:
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
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.
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.
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.
StoreManagement
, accessed via FMTCStore().manage
, allows control over the store and its contents.
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.
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.
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.
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 !
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 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:
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
Statistics: stats
Retrieve information about the store and its contents
Recovery: recovery
Recovery from unexpectedly failed bulk downloads
External: external
Export & import store archives
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').
◉ 📲
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
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: !