Skip to content

Commit

Permalink
Create missing TileImages during build
Browse files Browse the repository at this point in the history
See the code comments, this avoids a scenario where tiles are missing
because a given map movement triggers a TileLayer rebuild before the
tile loading is triggered.

I added a lot of comments in passing.
  • Loading branch information
rorystephenson committed Mar 31, 2023
1 parent c26d47f commit 29c1068
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 170 deletions.
8 changes: 4 additions & 4 deletions lib/src/layer/tile_layer/tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import 'package:flutter_map/src/layer/tile_layer/tile_image.dart';

class Tile extends StatefulWidget {
final TileImage tileImage;
final CustomPoint<double> currentPixelOrigin;
final double scaledTileSize;
final TileBuilder? tileBuilder;
final double scaledTileSize;
final CustomPoint<double> currentPixelOrigin;

const Tile({
super.key,
required this.tileImage,
required this.currentPixelOrigin,
required this.scaledTileSize,
required this.currentPixelOrigin,
required this.tileImage,
required this.tileBuilder,
});

Expand Down
14 changes: 10 additions & 4 deletions lib/src/layer/tile_layer/tile_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:flutter_map/src/layer/tile_layer/tile_coordinate.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart';

class TileImage extends ChangeNotifier {
bool _disposed = false;

/// The z of the coordinate is the TileImage's zoom level whilst the x and y
/// indicate the position of the tile at that zoom level.
final TileCoordinate coordinate;
Expand All @@ -25,13 +27,15 @@ class TileImage extends ChangeNotifier {
/// An optional image to show when a loading error occurs.
final ImageProvider? errorImage;

bool _disposed = false;

ImageProvider imageProvider;

/// If false this TileImage will be pruned when the next prune is run.
/// Current tiles are tiles which are in the current tile zoom AND:
/// * Are visible OR,
/// * Were previously visible and are still within the visible bounds
/// expanded by the [TileLayer.keepBuffer].
bool current = true;

/// Used during pruning to determine which tiles should be kept.
bool retain = false;

/// Whether the tile is displayable with full opacity. This means that either:
Expand Down Expand Up @@ -73,10 +77,12 @@ class TileImage extends ChangeNotifier {

bool get active => _active;

// Used to sort TileImages by their distance from the current zoom.
double zIndex(double maxZoom, int currentZoom) =>
maxZoom - (currentZoom - coordinate.z).abs();

void loadTileImage() {
// Initiate loading of the image.
void load() {
loadStarted = DateTime.now();

try {
Expand Down
111 changes: 56 additions & 55 deletions lib/src/layer/tile_layer/tile_image_manager.dart
Original file line number Diff line number Diff line change
@@ -1,41 +1,49 @@
import 'package:collection/collection.dart';
import 'package:flutter_map/src/core/point.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_bounds/tile_bounds.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_bounds/tile_bounds_at_zoom.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_coordinate.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_image.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_range.dart';

typedef TileCreator = TileImage Function(TileCoordinate coordinate);

class TileImageManager {
final Map<String, TileImage> _tiles = {};

List<TileImage> all() => _tiles.values.toList();
bool containsTileAt(TileCoordinate coords) => _tiles.containsKey(coords.key);

List<TileImage> sortedByDistanceToZoomAscending(
double maxZoom, int currentZoom) {
return [..._tiles.values]..sort((a, b) => a
.zIndex(maxZoom, currentZoom)
.compareTo(b.zIndex(maxZoom, currentZoom)));
}
bool get allLoaded =>
_tiles.values.none((tile) => tile.loadFinishedAt == null);

bool anyWithZoomLevel(double zoomLevel) {
for (final tile in _tiles.values) {
if (tile.coordinate.z == zoomLevel) {
return true;
}
}
// Returns in the order in which they should be rendered:
// 1. Tiles at the current zoom.
// 2. Tiles at the current zoom +/- 1.
// 3. Tiles at the current zoom +/- 2.
// 4. ...etc
List<TileImage> inRenderOrder(double maxZoom, int currentZoom) {
final result = _tiles.values.toList()
..sort((a, b) => a
.zIndex(maxZoom, currentZoom)
.compareTo(b.zIndex(maxZoom, currentZoom)));

return false;
return result;
}

TileImage? tileAt(TileCoordinate coords) => _tiles[coords.key];

bool get allLoaded {
for (final entry in _tiles.entries) {
if (entry.value.loadFinishedAt == null) {
return false;
}
// Creates missing tiles in the given range. Does not initiate loading of the
// tiles.
void createMissingTiles(
DiscreteTileRange tileRange,
TileBoundsAtZoom tileBoundsAtZoom, {
required TileCreator createTileImage,
}) {
for (final coordinate in tileBoundsAtZoom.validCoordinatesIn(tileRange)) {
_tiles.putIfAbsent(
coordinate.key,
() => createTileImage(coordinate),
);
}
return true;
}

bool allWithinZoom(double minZoom, double maxZoom) {
Expand All @@ -47,23 +55,27 @@ class TileImageManager {
return true;
}

bool markTileWithCoordsAsCurrent(TileCoordinate coords) {
final tile = _tiles[coords.key];
if (tile != null) {
// For each coordinate:
// * A TileImage is created if missing (current = true in new TileImages)
// * If it exists current is set to true
// * Of these tiles, those which have not started loading yet are returned.
List<TileImage> setCurrentAndReturnNotLoadedTiles(
Iterable<TileCoordinate> coordinates, {
required TileCreator createTile,
}) {
final notLoaded = <TileImage>[];

for (final coordinate in coordinates) {
final tile = _tiles.putIfAbsent(
coordinate.key,
() => createTile(coordinate),
);

tile.current = true;
return true;
} else {
return false;
if (tile.loadStarted == null) notLoaded.add(tile);
}
}

void add(TileCoordinate coords, TileImage tile) {
_tiles[coords.key] = tile;

// This must be done after storing the Tile in the TileManager otherwise
// the callbacks for image load success/fail will not find this Tile in
// the TileManager.
tile.loadTileImage();
return notLoaded;
}

/// All removals should be performed by calling this method to ensure that
Expand Down Expand Up @@ -104,26 +116,12 @@ class TileImageManager {
tileBounds.atZoom(tile.coordinate.z).wrap(tile.coordinate),
layer,
);
tile.loadTileImage();
}
}

void abortLoading(int? tileZoom, EvictErrorTileStrategy evictionStrategy) {
final toRemove = <String>[];
for (final entry in _tiles.entries) {
final tile = entry.value;

if (tile.coordinate.z != tileZoom && tile.loadFinishedAt == null) {
toRemove.add(entry.key);
}
}

for (final key in toRemove) {
_removeWithDefaultEviction(key, evictionStrategy);
tile.load();
}
}

void markToPrune(int currentTileZoom, DiscreteTileRange noPruneRange) {
void markAsNoLongerCurrentOutside(
int currentTileZoom, DiscreteTileRange noPruneRange) {
for (final entry in _tiles.entries) {
final tile = entry.value;
final c = tile.coordinate;
Expand All @@ -136,8 +134,11 @@ class TileImageManager {
}
}

void evictErrorTilesBasedOnStrategy(
DiscreteTileRange tileRange, EvictErrorTileStrategy evictStrategy) {
// Evicts error tiles depending on the [evictStrategy].
void evictErrorTiles(
DiscreteTileRange tileRange,
EvictErrorTileStrategy evictStrategy,
) {
if (evictStrategy == EvictErrorTileStrategy.notVisibleRespectMargin) {
final toRemove = <String>[];
for (final entry in _tiles.entries) {
Expand Down
Loading

0 comments on commit 29c1068

Please sign in to comment.