From aef5c45eb3fdaf5732cfada54cab4f0398c9c86e Mon Sep 17 00:00:00 2001 From: Reinis Sprogis Date: Wed, 10 Jul 2024 12:24:48 +0300 Subject: [PATCH] Add caching for loaded tiles in TileLayer to improve performance and reduce redundant tile loading. The cached tiles are stored in a Map where the key is the tile coordinates (x:y:z) and the value is the tile image as a byte list. This allows for quick retrieval of already loaded tiles, avoiding unnecessary network requests. The caching logic is implemented in the `_TileLayerState` class using a `Map` called `_cachedTiles`. Each time a tile is loaded, it is added to the cache using its coordinates as the key. When a tile is requested, the cache is checked first before initiating a new load. This change improves the efficiency of the TileLayer component and reducing requests. --- lib/src/layer/tile_layer/tile_layer.dart | 49 +++++++++++++++++++----- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/lib/src/layer/tile_layer/tile_layer.dart b/lib/src/layer/tile_layer/tile_layer.dart index c0b8e19e2..69a219f34 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:math'; +import 'dart:ui'; import 'package:collection/collection.dart' show MapEquality; import 'package:flutter/foundation.dart'; @@ -330,6 +331,9 @@ class TileLayer extends StatefulWidget { class _TileLayerState extends State with TickerProviderStateMixin { bool _initializedFromMapCamera = false; + ///Saves already loaded tiles in Map where key is (x:y:z) and value is tile image as bytes list + final Map _cachedTiles = {}; + final _tileImageManager = TileImageManager(); late TileBounds _tileBounds; late var _tileRangeCalculator = @@ -633,28 +637,53 @@ class _TileLayerState extends State with TickerProviderStateMixin { }) { final tileZoom = tileLoadRange.zoom; final expandedTileLoadRange = tileLoadRange.expand(widget.panBuffer); - - // Build the queue of tiles to load. Marks all tiles with valid coordinates - // in the tileLoadRange as current. final tileBoundsAtZoom = _tileBounds.atZoom(tileZoom); final tilesToLoad = _tileImageManager.createMissingTiles( - expandedTileLoadRange, - tileBoundsAtZoom, - createTile: (coordinates) => _createTileImage( + expandedTileLoadRange, tileBoundsAtZoom, createTile: (coordinates) { + final String key = '${coordinates.x}:${coordinates.y}:${coordinates.z}'; + if (_cachedTiles.containsKey(key)) { + return TileImage( + vsync: this, + coordinates: coordinates, + imageProvider: MemoryImage(_cachedTiles[key]!), + onLoadError: _onTileLoadError, + onLoadComplete: (coordinates) { + if (pruneAfterLoad) _pruneIfAllTilesLoaded(coordinates); + }, + tileDisplay: widget.tileDisplay, + errorImage: widget.errorImage, + cancelLoading: Completer(), + ); + } + final TileImage img = _createTileImage( coordinates: coordinates, tileBoundsAtZoom: tileBoundsAtZoom, pruneAfterLoad: pruneAfterLoad, - ), - ); + ); + + img.imageProvider.resolve(ImageConfiguration()).addListener( + ImageStreamListener((ImageInfo info, bool _) async { + final ByteData? byteData = + await info.image.toByteData(format: ImageByteFormat.png); + + //Currently workaround to skip adding tiles that are not loaded fully. + //Looks like was adding record to map even if loading was canceled resulting in empty tiles. + if (byteData!.lengthInBytes <= 97) { + return; + } + _cachedTiles[key] = byteData.buffer.asUint8List(); + }), + ); + + return img; + }); - // Re-order the tiles by their distance to the center of the range. final tileCenter = expandedTileLoadRange.center; tilesToLoad.sort( (a, b) => _distanceSq(a.coordinates, tileCenter) .compareTo(_distanceSq(b.coordinates, tileCenter)), ); - // Create the new Tiles. for (final tile in tilesToLoad) { tile.load(); }