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...
You're viewing documentation for an older version of FMTC (v6).
I work on all of my projects in my spare time, including maintaining (along with a team) Flutter's № 1 (non-commercially maintained) mapping library 'flutter_map', bringing it back from the brink of abandonment, as well as my own plugin for it ('flutter_map_tile_caching') that extends it with advanced caching and downloading. Additionally, I also own the Dart encoder/decoder for the QOI image format ('dqoi') - and I am slowly working on 'flutter_osrm', a wrapper for the Open Source Routing Machine.
Sponsorships & donations allow me to continue my projects and upgrade my hardware/setup, as well as allowing me to have some starting amount for further education and such-like. And of course, a small amount will find its way into my Jaffa Cakes fund (https://en.wikipedia.org/wiki/Jaffa_Cakes) - why do you think my username has "Jaffa" in it?
Many thanks for any amount you can spare, it means a lot to me!
You can read more about me and what I do on my GitHub Sponsors page, where you can donate as well.
Alternatively, if you prefer not to use GitHub Sponsors, please feel free to use my Ko-fi. Note that the PayPal backend will take a small percentage amount of donations made through this method.
I am not a lawyer, and this information is to the best of my understanding. 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 https://www.gnu.org/licenses/gpl-3.0.en.html. choosealicense.com 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 on request (under the same GPL v3 license) to anyone who uses your program.
However, I am willing to sell alternative (proprietary) licenses on a case-by-case basis and on request.
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 or financial position. I'm not sure it's fair for commercial proprietary applications to use software made by people for free out of generosity. On the other hand, I am also trying to make a small amount of money from my projects, by donations or by selling licenses. And I recognise that commercial businesses may want to use my projects for their own proprietary applications.
Therefore, if you would like a license to use this software within a proprietary, I am willing to sell a (preferably yearly or usage based) license for a reasonable price. If this seems like what you want/need, please do not hesitate to get in touch via github@jaffaketchup.com.
Not quite sure about something? No problem.
For the latest documentation, see . For migration information, see .
Raise an issue on GitHub, or get quicker support on the official flutter_map Discord server's plugin channel. I'm always happy to help as soon as possible .
Browse Caching
Setup customisable caching that works in the background, and makes your app more reliable for your user! Choose from three different behaviours including: cache only, forced update, or update after expiry.
Add this functionality with virtually zero performance impact compared to native flutter_map, and benefit from reduced tile loading speed if the tiles are already cached.
Bulk Downloading
Use alongside Browse Caching to provide large areas of maps to your users, just like other major mapping apps! There are multiple region types available, including: rectangle, circle, and line/locus.
This library also provides background bulk downloading in the background on Android, as well as automatic recovery just in case the download unexpectedly stops.
If you aren't fully familiar with flutter_map yet, you should first read through it's detailed documentation.
For development with this package, it is essential to become familiar with some terminology used throughout the documentation and API:
A 'Root' can hold multiple 'Stores'.
There is usually only one root per application, but more complex applications may wish to use more than one. In this case, the initialisation function can be run more than once.
'Browse Caching' (or just 'caching') is the caching performed when a user pans over a tile in a map view and it becomes visible.
'Bulk Downloading' is the caching performed when a user initiates a download by specifying a region to download at once.
This package contains a full example application - prebuilt for Android and Windows - showcasing the most important features of this package.
Feel free to use the app as a starting point for your application, it contains some useful UI patterns.
Before using the example application, make ensure you comply with the appropriate rules and terms of service set by your tile server.
OpenStreetMap's can be found here: specifically bulk downloading is discouraged, and forbidden after zoom level 13. Other servers may have different terms.
This library and/or the creator(s) are not responsible for any violations you make using this package. Remember that this library automatically adds a clear 'with flutter_map_tile_caching' to the User-Agent header.
There are prebuilt applications for Android and Windows available on GitHub.
These are automatically built (using GitHub Actions) from the latest available commits every time the example source files change, so may include functionality not yet available via pub.dev installation.
Additionally, they both contain auto-updaters, which download and install the newer versions from the same source.
To run the prebuilt Android application on most devices, download the .apk package (from #prebuilt-example-applications) to your device, then execute it to install it.
After installation, it will appear in the launcher like any other application.
The operating system may request permissions to install applications from unknown sources: you must allow this.
To run the prebuilt Windows application on most devices, download the .exe package (from #prebuilt-example-applications) to your device, then execute it to install it.
It will require a very simple installation with no administrator privileges required, then it will appear in the Start Menu and search bars like any other application. You can optionally choose to create a desktop shortcut.
You may receive security warnings depending on your system setup: these are false positives and occur due to the package being unsigned.
For other platforms, there are no prebuilt applications.
You'll have to clone this project, open the 'example' directory, and then build for your desired platform using Dart and Flutter as normal.
There is no auto-updater support for other platforms.
Once you can access FMTC.instance
, you can chain on other methods to use the majority of this library's functionality. These are the base chains you will need:
To get the Root and it's associated information, just chain on rootDirectory
, as so:
To use a Store without automatically creating it (recommended for performance), use ()
(call()
). Place the store name inside the parenthesis. For example:
All examples in this documentation will use this method of accessing Stores, assuming that they are already ready.
To use a Store and automatically synchronously create it if it doesn't exist, use []
. Place the store name inside the parenthesis. For example:
This is not recommended, as it uses synchronous IO operations on every get, which can block your application's main thread.
Only use this if you cannot use asynchronous techniques in your current context, or the slight blocking doesn't matter.
After this, you can chain any of the following members/accessors (each will be accessible on a Root, a Store, or both):
Many of the symbols one level beneath the ones listed here, for example those under manage
, have asynchronous versions which are recommended for performance.
To use them, if available, just add 'Async' to the end of the symbol. For example, manage.ready
and manage.readyAsync
.
If you receive warnings in your IDE about using internal members outside its package, you should use the chains listed above instead of directly using the indicated object.
access
Access the underlying file/directory structure as standard 'dart:io' objects (advanced)
manage
Modify and control the structure itself, such as creation and deletion operations
stats
Retrieve statistics about a structure, such as size and length
recovery
Recover failed bulk downloads, and prepare them to restart
migrator
Migrate the incompatible file/directory structure of a root from a previous version of FMTC to a newer version
import
Import and prepare a store from a previously exported archive file
download
Prepare, start, and manage a store's bulk downloads
metadata
A simple key-value pair store designed for storing simple, store related information
export
Export a store to an archive file, for future importing
Applies only to Stores
This library provides a very simple persistent key-value pair storage system, designed to store any custom information about the store. For example, your application may use one store per urlTemplate
, in which case, the URL can be stored in the metadata.
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.
Keys are used as the name of a file. Therefore, keys must be safe, as they are not sanitized by this library.
Add a new key-value pair to the store. For example:
Read all the key-value pairs from the store, and return them in a Map<String, String>
. For example:
Remove a key-value pair from the store. For example:
Remove all the key-value pairs from the store. For example:
Applies only to Roots
The recovery system is designed to rescue failed bulk downloads, in the event of an unexpected error - such as a fatal crash or other external event.
You should not rely on the recovery system to be 100% reliable, even though it is built to be as close to that as possible.
Recoverable regions are identified internally by an int
ID number. All the methods above make use of this ID number, and so (to ensure continuity) the RootRecovery
object returned is internally a singleton.
This number is generated using the following algorithm, which hopefully ensures a unique number:
Applies to Roots and Stores
The StoreDirectory
used to run this method will remain valid but not ready. Therefore, if you have a variable with it, you should replace it.
Using the normal FMTC.instance('newStoreName')
will work afterwards.
randomRange
controls the randomness of the tile chosen (defaults to null
):
null
: no randomness - the first tile is chosen
<= 0: any tile may be chosen
>= store length: any tile may be chosen
< store length: any tile up to this range may be chosen, enforcing an iteration limit internally
Tiles are not necessarily ordered chronologically. They are usually ordered alphabetically, which may affect your expectations of your chosen range.
Returns null
if there are no cached tiles in this store, otherwise an Image
with size
height and width.
Applies to Roots and Stores
recursive
is only available in Roots. If true
(default false
), then the resulting Stream
merges in the sub-Stores' watchChanges
methods.
debounce
defaults to 200ms, and is useful to remove small consecutive changes to prevent spam to your interface.
fileSystemEvents
defaults to FileSystemEvent.all
, and is useful to remove only some types of change from the resulting Stream
.
Returns a Stream<void>
, useful to trigger UI updates with other statistics.
Many statistics are cached for better performance, as some take a long time to calculate.
If this causes problems, chain noCache
before the below API getters/methods, like this: stats.noCache.storeSize
.
Alternatively, clear the currently cached statistics using invalidateCachedStatistics()
. This is automatically called when new tiles are added to the store.
Applies to Roots and Stores
This package relies on a carefully constructed structure that can be easily damaged to the point where it may not be able to be repaired without being completely recreated.
Avoid using access
. If you choose to use it within your application, I may be unable to offer support.
API Member | Explanation |
---|---|
API Member | Structure | Explanation |
---|---|---|
API Member | Structure | Explanation |
---|---|---|
recoverableRegions
Get a list of all recoverable regions, not just those that have failed
failedRegions
Get a list of recoverable regions that have failed
getRecoverableRegion()
Get a specific region from the recoverable list
getFailedRegion()
Get a specific region from the failed list
cancel()
Safely cancel/remove a recoverable region
ready
Both
Check if the necessary directory structure exists
create()
Both
Create the necessary directory structure, or do nothing if it already exists
delete()
Both
Delete the directory structure, fail if it doesn't exist
reset()
Roots
Reset the directory structure (delete and recreate)
reset()
Stores
Reset the tiles directory structure (delete and recreate)
Stores
Safely rename the store and the necessary directories
Stores
Retrieve a tile and extract it's Image
asynchronously
Both
Use a filesystem watcher to watch for changes, useful for a StreamBuilder
Both
Remove any cached statistic information
storesAvailable
Roots
List all the currently ready stores under the root
rootSize
Roots
Get the current root size in KiB including all sub-stores
rootLength
Roots
Get the number of tiles currently cached in all sub-stores
storeSize
Stores
Get the current store size in KiB
storeLength
Stores
Get the number of tiles currently cached
cacheHits
Stores
Get the number of tiles retrieved from the store during browsing
cacheMisses
Stores
Get the number of tiles not retrieved from the store during browsing
Applies only to Roots
To migrate from older versions with different, incompatible, file structures, migration methods have been provided.
After using the snippet above, use the appropriate method(s) to reach the package version in use. These automatically check for the the structure of a previous version, and migrate it to the correct structure for one version newer. If they successfully migrated a structure, they will return true
; otherwise (if it wasn't successful, or one wasn't found), they will return false
.
For example, to automatically check for and migrate v4 structures to v5, use fromV4
. It is safe to call this every time the application is loaded, and the example application demonstrates this.
It is possible to customize package-wide defaults through this method.
defaultTileProviderSettings
)Provide a set of #tile-provider-settings to use when not specified by other methods, such as the tile provider getter or bulk download methods.
filesystemSanitiser
)This method is commonly used to sanitise strings that will appear as the name of a directory or file in the filesystem.
The method should return a FilesystemSanitiserResult
given a single input string. The internal situation decides whether an invalid input throws an error, or just uses the new string. For example, the tile storer just uses the valid output, whereas the store creator requires a valid input (because the output is shown to the user, so must be the same as the input).
The default method is suitable for most use-cases, but you may wish to override this for added reliability.
FilesystemSanitiserResult)
The object returned from any valid filesystem string sanitiser. Has a validOutput
string, which is the sanitised result, and an errorMessages
list of strings, which can be empty if the input string was valid or contain one or more errors.
filesystemFormFieldValidator
)This method is available from the root of the settings, and uses the filesystemSanitiser
to validate (throw error if invalid) the input string.
Is suitable for direct usage (no wrapper function needed) in the validator
property of the TextFormField
.
Creating regions is designed to be easy for the user and you (the developer).
The Example Application contains a great way you might want to allow your users to choose a region to download, and it shows how to use Provider to share a created region and the number of approximate tiles it has to a download screen.
All regions (before conversion to DownloadableRegion
) implement BaseRegion
.
The most basic type of region, defined by a North West and South East coordinate bound - specifically a LatLngBounds
object provided by flutter_map.
A more advanced type of region, defined by a center coordinate and radius (in kilometers).
If you have two coordinates, one center, and one on the edge of the circle you want, you can use 'latlong2's Distance.distance()
method, as below:
The most advanced type of region, defined by a list of coordinates and radius (in meters).
After you've created your region, you can convert it to a drawable polygon (below), or convert it to a DownloadableRegion
ready for downloading.
All BaseRegions
can be drawn on a map with minimal effort from you or the user, using toDrawable()
.
Internally, this uses the toList
or toOutline
methods to generate the points forming the Polygon
, then it places this/these polygons into a PolygonLayerOptions
.
Line regions are bit more complicated to draw, as they involve a lot of logic and maths to make smooth. For this reason, they are also the worst performing when it comes to drawing.
Converting a LineRegion
with many points spaced close together may yield unexpected results, due to the curves that must be drawn.
There are additional important parameters:
Set prettyPaint
to false
and overlap
to -1
to get the 'reduced' appearance. Or, set to 0
to get the normal appearance, and 1
to get the rectangles that are actually downloaded.
Set prettyPaint
to true
and overlap
to -1
to get the rectangles joined by their nearest corners. Setting to 0
will show the line with joining curves - the default. Setting to 1
will cause an error.
Disabling prettyPaint
will increase speed and is recommended on slower devices. Decreasing the curveSmoothening
will also increase speed - set to a smaller value if corners are likely to be small (for example along a route).
API Parameter | Type | Explanation | Default |
---|---|---|---|
prettyPaint
bool
Controls whether the rectangles formed are joined nicely, whether they are just joined by the closest corners, or whether they are just left as rectangles
true
curveSmoothening
int
Amount of curve segments per curve, if prettyPaint
is enabled
50
overlap
int
Controls the rendering if prettyPaint
is disabled
0
Unfortunately, background downloading is available on Android only, due to the strict limitations imposed by iOS. This is unlikely to change in the future, especially as I am currently unable to develop for iOS.
In addition, there is no planned support for other platforms.
There is some confusion about the way background process handling works on Android, so let me clear it up for you: it is confusing.
Each vendor (eg. Samsung, Huawei, Motorola) has their own methods of handling background processes.
Some manage it by providing the bare minimum user-end management, resulting in process that drain battery because they can't be stopped easily; others manage it by altogether banning/strictly limiting background processes, resulting in weird problems and buggy apps; many manage it by some layer of control on top of Android's original controls, making things more confusing for everyone.
Therefore there is no guaranteed behaviour when using this functionality. You can see how many vendors will treat background processes here: dontkillmyapp.com; you may wish to link your users to this site so they can properly configure your app to run in the background.
To try and help your users get to the right settings quicker, use the requestIgnoreBatteryOptimizations()
method before starting a background download. This will interrupt the app with either a dialog or a settings page where they can opt-in to reduced throttling. There is no guarantee that this will work, but it should help: this is not required and the background download will still try to run even if the user denies the permissions.
Internally, a foreground service is actually used. This allows the service to run as long as the app hasn't been force stopped.
The effectiveness of the Recovery system is reduced by background downloading.
If the user leaves the application, then the recovery system may report the ongoing background download as failed, as it has no way of knowing about it. If the user tries to retry the download, both downloads may then fail, and the recovery system may fail also.
There is no way of resolving this situation. You may prefer to disable recovery on background downloads all together.
Unlike foreground downloading, where you can Listen For Progress, background downloading does not provide any way to do this, so it is much less customisable.
The download progress notification only displays the percentage progress and number of tiles attempted/max number of tiles (see #available-statistics).
withGUI
)To display a file/archive selection screen/popup to the user using the platform specific picker, use this method.
On desktop platforms, this uses the save path selector dialog. On mobile platforms, this uses the share sheet/dialog.
Specifying fileExtension
will attempt to limit the type of files that can be selected (defaulting to '.fmtc'). Note that this is not supported on every platform, and it will fallback to allowing any type of file to be chosen, but only files ending in the extension will be imported.
Specifying forceFilePicker
is not recommended (null
by default). This overrides the default platform check to decide which interface to use.
context
must be specified when using the share sheet/dialog. Therefore, it is always recommended to specify it.
It returns a Future<void>
which completes when the export is complete.
File
(manual
)To export a store to a known (non-existing) file/archive, use this method.
It returns a Future<void>
which completes when the export is complete.
withGUI
)To display a file/archive selection screen/popup to the user using the platform specific picker, use this method. Multiple files can be selected at once, and each will be imported simultaneously.
Specifying fileExtension
will attempt to limit the type of files that can be selected (defaulting to '.fmtc'). Note that this is not supported on every platform, and it will fallback to allowing any type of file to be chosen, but only files ending in the extension will be imported.
Disabling emptyCacheBeforePicking
is not recommended (true
by default). It dictates whether a cached version of the chosen file should be imported or not.
It returns a mapping of the name of the store to its success state (a Future
which will resolve to a boolean flag).
File
(manual
)To import a store from a known file/archive, use this method.
It returns its success state (a Future
which will resolve to a boolean flag).
Available on Android only
You should read about the limitations and tradeoffs of background downloading before you start using it.
Before continuing with usage, make ensure you comply with the appropriate rules and terms of service set by your tile server.
OpenStreetMap's can be found here: specifically bulk downloading is discouraged, and forbidden after zoom level 13. Other servers may have different terms.
This library and/or the creator(s) are not responsible for any violations you make using this package. Remember that this library automatically adds a clear 'with flutter_map_tile_caching' to the User-Agent header.
You should also wrap your application's root widget (such as a Scaffold
) with the FMTCBackgroundDownload
widget.
This is designed to stop the app from terminating when it is taken off the widget tree, such as when the user closes the application. It is safe to leave there even when not downloading: it is intelligent enough to only keep the application alive if there is an ongoing background download.
Available since v5.1.0
It is possible to import and export an entire store to an archive file that can be easily shared and distributed between devices. For example, they can be used to distribute a common tiles 'package' to all users.
Exported stores contain all of their contents, including metadata and cached statistics.
The archives are in the ZIP format, meaning the contents can be inspected and modified easily by many programs (such as 7zip). The archives do not have any compression applied as it is ineffective against the already compressed tiles.
Although the exported stores are a ZIP format internally, they can have any file extension applied to them - '.fmtc' is used in the example application. The name of the file dictates the name of the store that will be used when importing.
This project is currently maintained by JaffaKetchup (Luka S). I am currently a maintainer of flutter_map, but this project has no other internal links.
Thanks to all contributors, and 'bugDim88' who originally came up with the idea and created a PR for flutter_map. When that PR was closed (it was decided a plugin would be more suitable), I took over the project.
In terms of the example application, open-source licenses and additional thanks are visible on the Settings page.
And thanks to my...
Please see #supporting-me for more information about why and how to sponsor me, for any amount you think is suitable
Many thanks to all my sponsors, not matter how much or how little they donated (in no particular order):
+ 3 anonymous or private donors
The main basis of this package is the FlutterMapTileCaching
object. There are other high level objects, but they are usually for more advanced usage, and can be explored in more detail through the API documentation.
FMTC
is a shorthand type alias for FlutterMapTileCaching
, and works in exactly the same way. Often, documentation will use the shortened version to save space, and you should do so in your code as well.
This singleton must first be initialised, usually in the main
(asynchronous) function at the start of your app, like this: FMTC.initialise()
. The function takes a rootDir
argument, usually await RootDirectory.normalCache
, and custom global settings
, which is optional.
Once initialised, it is safe to use FMTC.instance.
At this point, most functionality is accessed through chaining.
You must call initialise()
before trying to use instance.
Failure to do so will throw a StateError
.
For example:
You're viewing documentation for an older version of FMTC (v6).
This is the recommended method of installing this package as it ensures you only receive stable versions, and you can be sure pub.dev is reliable. It also keeps the size of your pubspec.yaml small.
Just import the package as you would normally, from the command line:
This should automatically import the latest version of the package and create an entry for it in your pubspec.yaml. Otherwise follow the old method and add the latest version of the 'flutter_map_tile_caching' dependency to the pubspec.yaml manually.
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.
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:
After installing and importing wherever you need it, be sure to follow the Additional Setup instructions to ensure your application builds and runs correctly.
Before continuing with usage, make ensure you comply with the appropriate rules and terms of service set by your tile server.
This library and/or the creator(s) are not responsible for any violations you make using this package. Remember that this library automatically adds a clear 'with flutter_map_tile_caching' to the User-Agent header.
flutter_map
Installation & SetupYou must make sure you follow flutter_map
's installation and additional setup instructions.
You may need to follow the instructions below depending on your platform. If your platform is not listed, that means no extra setup is necessary.
This setup is only required if using background bulk downloading within your app.
Add the following to 'android\app\src\main\AndroidManifest.xml' and any other manifests:
This will allow the application to acquire the necessary permissions (should the user allow them at runtime) to a background process.
FOREGROUND_SERVICE
: allows the application to start a foreground service - a type of Android service that can run in the background, as long as the application isn't force stopped.
WAKE_LOCK
: allows the background process (technically foreground service) to run even when the device is locked/asleep. Also allows the acquisition of a WiFi lock.
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
(must be requested at runtime): assists with the background process not being killed by the system.
This setup is only required if using the prebuilt import/export functionality within your app.
Please follow these additional instructions for supporting Android versions above 11 and building for release:
Unfortunately, I do not have the hardware to test this library on Apple platforms. If you find issues, please report them!
It is unknown whether this setup is needed in all cases, so it is recommended to follow these only when you receive errors during building your app.
Some developers may have issues when releasing the app or uploading to TestFlight - see issue #69 for the first report of this problem. This is due to some of this library's dependencies on platform plugins.
Annotate that access is not needed to the Media, Audio, and Documents directories - this package uses only custom file types. Add these lines to your Podfile just before target 'Runner' do
.
Add UIBackgroundModes
capability with the fetch
and remote-notifications
keys to Xcode, to describe why your app needs to access background tasks - in this case to bulk download maps in the background.
This package provides the ability to download areas of maps, known as regions throughout this documentation.
Before continuing with usage, make ensure you comply with the appropriate rules and terms of service set by your tile server.
This library and/or the creator(s) are not responsible for any violations you make using this package. Remember that this library automatically adds a clear 'with flutter_map_tile_caching' to the User-Agent header.
All APIs listed in this section are children of the download
getter.
The recovery system is designed to support bulk downloading, and provide some form of recovery if the download fails unexpectedly - this might happen if the app crashes, for example.
Read more about the recovery system here:
For the latest documentation, see . For migration information, see .
After downloading, tiles are stored in the same place as when Browse Caching, meaning that no extra setup is needed to use them in a map (other than the usual )!
OpenStreetMap's can be : specifically bulk downloading is discouraged, and forbidden after zoom level 13. Other servers may have different terms.
Start downloading that region, either in the or
API Member | Explanation |
---|
Start a download in the foreground |
Start a download in the background |
Check the number of tiles in a certain region |
Cancel any ongoing downloads |
Before continuing with usage, make ensure you comply with the appropriate rules and terms of service set by your tile server.
OpenStreetMap's can be found here: specifically bulk downloading is discouraged, and forbidden after zoom level 13. Other servers may have different terms.
This library and/or the creator(s) are not responsible for any violations you make using this package. Remember that this library automatically adds a clear 'with flutter_map_tile_caching' to the User-Agent header.
To actually start downloading any tiles, you must Listen For Progress, even if you do not plan to use the #available-statistics.
Stores also have the method getTileProvider()
. This is the point of integration with flutter_map, providing browse caching through a custom image provider, and can be used as so:
This method (and others) optionally take a FMTCTileProviderSettings
. These configure the behaviour of the tile provider. Defaults to #default-tile-provider-settings-defaulttileprovidersettings, or the package default (see table below) if that is not specified.
FMTCTileProviderSettings
can take the following arguments:
Note that using maxStoreLength
can have a negative effect on performance, as sorting the tiles to find the oldest is (currently) expensive.
This enumerable contains 3 values, which are used to dictate which logic should be used to store and retrieve tiles from the store.
The object returned by this function also has the method checkTileCached
, with the following parameters:
Pass the coords
of the tile to check for, and the same/similar TileLayer
used to store the tile in the first place.
It will return a boolean dependent on whether or not the tile could be found.
To listen for progress events, you can listen to the Stream
of DownloadProgress
events returned by the startForeground()
method.
Listening can be done through any method, such as listen()
or the await for
loop.
To actually start downloading any tiles, you must attach a listener to the returned Stream
, even if you do not plan to use the #available-statistics.
BaseRegions
must be converted to DownloadableRegions
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.
See this basic example:
By not storing pure tiles of sea, we can save a bunch of space on the user's device with every download. But how to do this?
Well, this package does it by analysing the bytes of the tile and checking if it's identical to a sample taken at lat/lng 0, 0 (Null Island) with zoom level 17. This tile should always be sea, and therefore any matching tile must also be sea.
In this way, we can delete tiles after we've checked them, if they are indeed sea. This method also means that tiles with ferry paths and other markings remain safe.
Sea Tile Removal does not reduce time or data consumption. Every tile must still be downloaded to check it.
Before downloading the region, you can count the number of tiles it will attempt to download. This is done by the check()
method.
The method takes the DownloadableRegion
generated above, and will return an int
number of tiles. Note that this may take a while for large regions, as it is generating a list of all the tiles within the region.
API Parameter | Type | Explanation | Default |
---|---|---|---|
Value | Explanation |
---|---|
Statistic | Type | Explanation |
---|---|---|
API Parameter | Type | Explanation | Default |
---|---|---|---|
region
The actual region to download
required
tileProviderSettings
Settings for the downloader tile provider to use
As in Global Settings
disableRecovery
bool
Disable the Recovery system (not recommended)
false
behavior
: CacheBehavior
Determine the logic used during handling storage and retrieval of browse caching
CacheBehavior.cacheFirst
cachedValidDuration
: Duration
Length of time a tile remains valid, after which it must be fetched again (ignored in onlineFirst
mode)
const Duration(days: 16)
maxStoreLength
: int
Maximum number of tiles allowed in a cache store (deletes oldest tile)
0
: disabled
cacheFirst
Get tiles from the local cache if possible.
Only uses the Internet if it doesn't exist, or to update it if it has expired.
onlineFirst
Get tiles from the Internet if possible.
Updates every cached tile every time it is fetched (ignores expiry).
cacheOnly
Only get tiles from the local cache, and throw an error if not found.
Recommended for dedicated offline modes.
successfulTiles
int
Number of tiles successfully downloaded
failedTiles
List<String>
List of tile URLs that failed to download
maxTiles
int
Number of expected tiles in the download region
successfulSize
double
Total size of all successful tiles (in kibibytes)
seaTiles
int
Number of tiles 'skipped' due to #sea-tile-removal
existingTiles
int
Number of tiles skipped due to preventRedownload
duration
Duration
Current duration spent downloading tiles
tileImage
MemoryImage?
Image of the last tile downloaded, or null
if unavailable
attemptedTiles
int
Number of tiles attempted (successful + failed)
remainingTiles
int
Number of tiles remaining (max - attempted)
seaTilesDiscount
double
Percentage of tiles skipped due to #sea-tile-removal
existingTilesDiscount
double
Percentage of tiles skipped due to preventRedownload
percentageProgress
double
Percentage of download currently complete
averageTPS
double
Average number of tiles per second being downloaded.
This uses a specialised exponentially smoothed moving average algorithm for better results (where more recent results affect the value more)
estTotalDuration
Duration
Estimated duration for the entire download
estRemainingDuration
Double
Estimated duration for the remaining part of the download
parallelThreads
int
Number of download threads allowed to run simultaneously
10
preventRedownload
bool
Controls whether to skip downloading tiles that already exist
false
bool
Controls whether to remove tiles that are entirely sea
false
start
int
Skip past a number of tiles 'at the start' of a region
0
end
int?
Skip a number of tiles 'at the end' of a region
crs
Crs
Map projection to use to calculate tiles
const Epsg3857()
errorHandler
void Function(Object?)?
Function to be called if a tile fails to download
v7 is now in development!
Please see this PR for the latest changes and keep an eye on the pub.dev page for prereleases.
The information below was written before I started v7 implementation, so may be outdated.
I maintain this to the best of my ability given my relatively limited spare time. However, I do have some plans for improvement in the future!
Please do contribute (code or just opinions) if you would like to and have the time to!
Before v2, this plugin used an 'sqflite' database to store tiles. However, I removed this in order to add support for multiple stores.
Whilst this impacted both functionality and performance positivley at the time, there are now bottlenecks caused by the reliance on the native filesystem. These include, but are by no means limited to:
Struggles with sanitizing the tile name to convert it to a file name that is suitable on every device - harder than you might think, given the large number of differing restrictions set by differing filesystems/operating systems.
Limitations to the speed of the import/export functionality: stores must first be compressed (and uncompressed on the other side) into a ZIP format. For a larger number of tiles, this can be quite slow!
Limitations to the speed of statistic fetching: reading each tile to find its length (size) and summing this up doesn't sound like it would take long, but it does. It's also quite memory inefficient. This was partially resolved by the statistic caching introduced in v5, but is not ideal.
The whole storage structure being easily edited and broken by a curious user. This also includes the fact that the bulk download functionality can very easily lead to copying: whilst keeping them as blobs in an SQL table won't stop this, it will make it significantly more difficult.
Migrating to a database won't be easy and will take time. Currently, I'm evaluating Isar (see this discussion I opened).
If you need to stop a bulk download early, you can use the cancel()
method to safely exit. This will also remove the active recovery link. Any progress streams will stop emitting events.
This is automatically called at the end of a successful download.
There is currently no way to temporarily pause a download, and this functionality is not planned.
To implement, track the attemptedTiles
from #available-statistics. Then start the download with that offset.