Skip to content

Commit d7741bd

Browse files
committed
Added tile_providers
Not yet tested.
1 parent 6666b95 commit d7741bd

File tree

5 files changed

+606
-44
lines changed

5 files changed

+606
-44
lines changed

lib/flutter_map_tile_caching.dart

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import 'dart:ui';
2+
3+
import 'package:flutter/foundation.dart';
4+
import 'package:flutter/painting.dart';
5+
import 'package:flutter_map/flutter_map.dart';
6+
import 'package:flutter_map/src/layer/tile_layer.dart';
7+
import './tile_storage_caching_manager.dart';
8+
import 'package:flutter_map/src/layer/tile_provider/tile_provider.dart';
9+
import 'package:http/http.dart' as http;
10+
import 'package:tuple/tuple.dart';
11+
export './tile_storage_caching_manager.dart';
12+
13+
///Provider that persist loaded raster tiles inside local sqlite db
14+
/// [cachedValidDuration] - valid time period since [DateTime.now]
15+
/// which determines the need for a request for remote tile server. Default value
16+
/// is one day, that means - all cached tiles today and day before don't need rewriting.
17+
class StorageCachingTileProvider extends TileProvider {
18+
static final kMaxPreloadTileAreaCount = 3000;
19+
final Duration cachedValidDuration;
20+
21+
StorageCachingTileProvider(
22+
{this.cachedValidDuration = const Duration(days: 1)});
23+
24+
@override
25+
ImageProvider getImage(Coords<num> coords, TileLayerOptions options) {
26+
final tileUrl = getTileUrl(coords, options);
27+
return CachedTileImageProvider(tileUrl,
28+
Coords<int>(coords.x.toInt(), coords.y.toInt())..z = coords.z.toInt());
29+
}
30+
31+
/// Caching tile area by provided [bounds], zoom edges and [options].
32+
/// The maximum number of tiles to load is [kMaxPreloadTileAreaCount].
33+
/// To check tiles number before calling this method, use
34+
/// [approximateTileAmount].
35+
/// Return [Tuple3] with uploaded tile index as [Tuple3.item1],
36+
/// errors count as [Tuple3.item2], and total tiles count need to be downloaded
37+
/// as [Tuple3.item3]
38+
Stream<Tuple3<int, int, int>> loadTiles(
39+
LatLngBounds bounds, int minZoom, int maxZoom, TileLayerOptions options,
40+
{Function(dynamic) errorHandler}) async* {
41+
final tilesRange = approximateTileRange(
42+
bounds: bounds,
43+
minZoom: minZoom,
44+
maxZoom: maxZoom,
45+
tileSize: CustomPoint(options.tileSize, options.tileSize));
46+
assert(tilesRange.length <= kMaxPreloadTileAreaCount,
47+
'${tilesRange.length} to many tiles for caching');
48+
var errorsCount = 0;
49+
for (var i = 0; i < tilesRange.length; i++) {
50+
try {
51+
final cord = tilesRange[i];
52+
final url = getTileUrl(cord, options);
53+
// get network tile
54+
final bytes = (await http.get(Uri.parse(url))).bodyBytes;
55+
// save tile to cache
56+
await TileStorageCachingManager.saveTile(bytes, cord);
57+
} catch (e) {
58+
errorsCount++;
59+
if (errorHandler != null) errorHandler(e);
60+
}
61+
yield Tuple3(i + 1, errorsCount, tilesRange.length);
62+
}
63+
}
64+
65+
///Get approximate tile amount from bounds and zoom edges.
66+
///[crs] and [tileSize] is optional.
67+
static int approximateTileAmount(
68+
{@required LatLngBounds bounds,
69+
@required int minZoom,
70+
@required int maxZoom,
71+
Crs crs = const Epsg3857(),
72+
tileSize = const CustomPoint(256, 256)}) {
73+
assert(minZoom <= maxZoom, 'minZoom > maxZoom');
74+
var amount = 0;
75+
for (var zoomLevel in List<int>.generate(
76+
maxZoom - minZoom + 1, (index) => index + minZoom)) {
77+
final nwPoint = crs
78+
.latLngToPoint(bounds.northWest, zoomLevel.toDouble())
79+
.unscaleBy(tileSize)
80+
.floor();
81+
final sePoint = crs
82+
.latLngToPoint(bounds.southEast, zoomLevel.toDouble())
83+
.unscaleBy(tileSize)
84+
.ceil() -
85+
CustomPoint(1, 1);
86+
final a = sePoint.x - nwPoint.x + 1;
87+
final b = sePoint.y - nwPoint.y + 1;
88+
amount += a * b;
89+
}
90+
return amount;
91+
}
92+
93+
///Get tileRange from bounds and zoom edges.
94+
///[crs] and [tileSize] is optional.
95+
static List<Coords> approximateTileRange(
96+
{@required LatLngBounds bounds,
97+
@required int minZoom,
98+
@required int maxZoom,
99+
Crs crs = const Epsg3857(),
100+
tileSize = const CustomPoint(256, 256)}) {
101+
assert(minZoom <= maxZoom, 'minZoom > maxZoom');
102+
final cords = <Coords>[];
103+
for (var zoomLevel in List<int>.generate(
104+
maxZoom - minZoom + 1, (index) => index + minZoom)) {
105+
final nwPoint = crs
106+
.latLngToPoint(bounds.northWest, zoomLevel.toDouble())
107+
.unscaleBy(tileSize)
108+
.floor();
109+
final sePoint = crs
110+
.latLngToPoint(bounds.southEast, zoomLevel.toDouble())
111+
.unscaleBy(tileSize)
112+
.ceil() -
113+
CustomPoint(1, 1);
114+
for (var x = nwPoint.x; x <= sePoint.x; x++) {
115+
for (var y = nwPoint.y; y <= sePoint.y; y++) {
116+
cords.add(Coords(x, y)..z = zoomLevel);
117+
}
118+
}
119+
}
120+
return cords;
121+
}
122+
}
123+
124+
class CachedTileImageProvider extends ImageProvider<Coords<int>> {
125+
final Function(dynamic) netWorkErrorHandler;
126+
final String url;
127+
final Coords<int> coords;
128+
final Duration cacheValidDuration;
129+
130+
CachedTileImageProvider(this.url, this.coords,
131+
{this.cacheValidDuration = const Duration(days: 1),
132+
this.netWorkErrorHandler});
133+
134+
@override
135+
ImageStreamCompleter load(Coords<int> key, decode) =>
136+
MultiFrameImageStreamCompleter(
137+
codec: _loadAsync(),
138+
scale: 1,
139+
informationCollector: () sync* {
140+
yield DiagnosticsProperty<ImageProvider>('Image provider', this);
141+
yield DiagnosticsProperty<Coords>('Image key', key);
142+
});
143+
144+
@override
145+
Future<Coords<int>> obtainKey(ImageConfiguration configuration) =>
146+
SynchronousFuture(coords);
147+
148+
Future<Codec> _loadAsync() async {
149+
final localBytes = await TileStorageCachingManager.getTile(coords);
150+
var bytes = localBytes?.item1;
151+
if ((DateTime.now().millisecondsSinceEpoch -
152+
(localBytes?.item2?.millisecondsSinceEpoch ?? 0)) >
153+
cacheValidDuration.inMilliseconds) {
154+
try {
155+
// get network tile
156+
bytes = (await http.get(Uri.parse(url))).bodyBytes;
157+
// save tile to cache
158+
await TileStorageCachingManager.saveTile(bytes, coords);
159+
} catch (e) {
160+
if (netWorkErrorHandler != null) netWorkErrorHandler(e);
161+
}
162+
}
163+
if (bytes == null) {
164+
return Future<Codec>.error('Failed to load tile for coords: $coords');
165+
}
166+
return await PaintingBinding.instance.instantiateImageCodec(bytes);
167+
}
168+
}

0 commit comments

Comments
 (0)