diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 8f4ba975a..a41e4b732 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -883,7 +883,18 @@ class MapInteractiveViewerState extends State final newCenterPoint = _camera.project(_mapCenterStart) + _flingAnimation.value.toPoint().rotate(_camera.rotationRad); - final newCenter = _camera.unproject(newCenterPoint); + final math.Point bestCenterPoint; + final double worldSize = _camera.crs.scale(_camera.zoom); + if (newCenterPoint.x > worldSize) { + bestCenterPoint = + math.Point(newCenterPoint.x - worldSize, newCenterPoint.y); + } else if (newCenterPoint.x < 0) { + bestCenterPoint = + math.Point(newCenterPoint.x + worldSize, newCenterPoint.y); + } else { + bestCenterPoint = newCenterPoint; + } + final newCenter = _camera.unproject(bestCenterPoint); widget.controller.moveRaw( newCenter, diff --git a/lib/src/layer/tile_layer/tile.dart b/lib/src/layer/tile_layer/tile.dart index b438380a5..509419fc1 100644 --- a/lib/src/layer/tile_layer/tile.dart +++ b/lib/src/layer/tile_layer/tile.dart @@ -21,6 +21,21 @@ class Tile extends StatefulWidget { /// visible pixel when the map is rotated. final Point currentPixelOrigin; + /// Position Coordinates. + /// + /// Most of the time, they are the same as in [tileImage]. + /// Except for multi-world or scrolled maps, for instance, scrolling from + /// Europe to Alaska on zoom level 3 (i.e. tile coordinates between 0 and 7): + /// * Alaska is first considered as from the next world (tile X: 8) + /// * Scrolling again, Alaska is considered as part of the current world, as + /// the center of the map is now in America (tile X: 0) + /// In both cases, we reuse the same [tileImage] (tile X: 0) for different + /// [positionCoordinates] (tile X: 0 and 8). This prevents a "flash" effect + /// when scrolling beyond the end of the world: we skip the part where we + /// create a new tileImage (for tile X: 0) as we've already downloaded it + /// (for tile X: 8). + final TileCoordinates positionCoordinates; + /// Creates a new instance of [Tile]. const Tile({ super.key, @@ -28,6 +43,7 @@ class Tile extends StatefulWidget { required this.currentPixelOrigin, required this.tileImage, required this.tileBuilder, + required this.positionCoordinates, }); @override @@ -54,9 +70,9 @@ class _TileState extends State { @override Widget build(BuildContext context) { return Positioned( - left: widget.tileImage.coordinates.x * widget.scaledTileSize - + left: widget.positionCoordinates.x * widget.scaledTileSize - widget.currentPixelOrigin.x, - top: widget.tileImage.coordinates.y * widget.scaledTileSize - + top: widget.positionCoordinates.y * widget.scaledTileSize - widget.currentPixelOrigin.y, width: widget.scaledTileSize, height: widget.scaledTileSize, diff --git a/lib/src/layer/tile_layer/tile_coordinates.dart b/lib/src/layer/tile_layer/tile_coordinates.dart index fc45701c5..2e5a1baa0 100644 --- a/lib/src/layer/tile_layer/tile_coordinates.dart +++ b/lib/src/layer/tile_layer/tile_coordinates.dart @@ -20,6 +20,29 @@ class TileCoordinates extends Point { /// Create a new [TileCoordinates] instance. const TileCoordinates(super.x, super.y, this.z); + /// Returns a unique value for the same tile on all world replications. + factory TileCoordinates.key(TileCoordinates coordinates) { + if (coordinates.z < 0) { + return coordinates; + } + final modulo = 1 << coordinates.z; + int x = coordinates.x; + while (x < 0) { + x += modulo; + } + while (x >= modulo) { + x -= modulo; + } + int y = coordinates.y; + while (y < 0) { + y += modulo; + } + while (y >= modulo) { + y -= modulo; + } + return TileCoordinates(x, y, coordinates.z); + } + @override String toString() => 'TileCoordinate($x, $y, $z)'; diff --git a/lib/src/layer/tile_layer/tile_image.dart b/lib/src/layer/tile_layer/tile_image.dart index 0fa452b77..882384931 100644 --- a/lib/src/layer/tile_layer/tile_image.dart +++ b/lib/src/layer/tile_layer/tile_image.dart @@ -21,7 +21,7 @@ class TileImage extends ChangeNotifier { /// indicate the position of the tile at that zoom level. final TileCoordinates coordinates; - /// Callback fired when loading finishes with or withut an error. This + /// Callback fired when loading finishes with or without an error. This /// callback is not triggered after this TileImage is disposed. final void Function(TileCoordinates coordinates) onLoadComplete; diff --git a/lib/src/layer/tile_layer/tile_image_manager.dart b/lib/src/layer/tile_layer/tile_image_manager.dart index 986e51e7f..9fabcd544 100644 --- a/lib/src/layer/tile_layer/tile_image_manager.dart +++ b/lib/src/layer/tile_layer/tile_image_manager.dart @@ -6,6 +6,7 @@ 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_image_view.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_range.dart'; +import 'package:flutter_map/src/layer/tile_layer/tile_renderer.dart'; import 'package:meta/meta.dart'; /// Callback definition to crete a [TileImage] for [TileCoordinates]. @@ -14,12 +15,14 @@ typedef TileCreator = TileImage Function(TileCoordinates coordinates); /// The [TileImageManager] orchestrates the loading and pruning of tiles. @immutable class TileImageManager { + final Set _positionCoordinates = HashSet(); + final Map _tiles = HashMap(); - /// Check if the [TileImageManager] has the tile for a given tile cooridantes. + /// Check if the [TileImageManager] has the tile for a given tile coordinates. bool containsTileAt(TileCoordinates coordinates) => - _tiles.containsKey(coordinates); + _positionCoordinates.contains(coordinates); /// Check if all tile images are loaded bool get allLoaded => @@ -29,16 +32,26 @@ class TileImageManager { /// 1. Tiles in the visible range at the target zoom level. /// 2. Tiles at non-target zoom level that would cover up holes that would /// be left by tiles in #1, which are not ready yet. - Iterable getTilesToRender({ + Iterable getTilesToRender({ required DiscreteTileRange visibleRange, - }) => - TileImageView( - tileImages: _tiles, - visibleRange: visibleRange, - // `keepRange` is irrelevant here since we're not using the output for - // pruning storage but rather to decide on what to put on screen. - keepRange: visibleRange, - ).renderTiles; + }) { + final Iterable positionCoordinates = TileImageView( + tileImages: _tiles, + positionCoordinates: _positionCoordinates, + visibleRange: visibleRange, + // `keepRange` is irrelevant here since we're not using the output for + // pruning storage but rather to decide on what to put on screen. + keepRange: visibleRange, + ).renderTiles; + final List tileRenderers = []; + for (final position in positionCoordinates) { + final TileImage? tileImage = _tiles[TileCoordinates.key(position)]; + if (tileImage != null) { + tileRenderers.add(TileRenderer(tileImage, position)); + } + } + return tileRenderers; + } /// Check if all loaded tiles are within the [minZoom] and [maxZoom] level. bool allWithinZoom(double minZoom, double maxZoom) => _tiles.values @@ -55,7 +68,13 @@ class TileImageManager { final notLoaded = []; for (final coordinates in tileBoundsAtZoom.validCoordinatesIn(tileRange)) { - final tile = _tiles[coordinates] ??= createTile(coordinates); + final cleanCoordinates = TileCoordinates.key(coordinates); + TileImage? tile = _tiles[cleanCoordinates]; + if (tile == null) { + tile = createTile(cleanCoordinates); + _tiles[cleanCoordinates] = tile; + } + _positionCoordinates.add(coordinates); if (tile.loadStarted == null) { notLoaded.add(tile); } @@ -77,7 +96,17 @@ class TileImageManager { TileCoordinates key, { required bool Function(TileImage tileImage) evictImageFromCache, }) { - final removed = _tiles.remove(key); + _positionCoordinates.remove(key); + final cleanKey = TileCoordinates.key(key); + + // guard if positionCoordinates with the same tileImage. + for (final positionCoordinates in _positionCoordinates) { + if (TileCoordinates.key(positionCoordinates) == cleanKey) { + return; + } + } + + final removed = _tiles.remove(cleanKey); if (removed != null) { removed.dispose(evictImageFromCache: evictImageFromCache(removed)); @@ -97,7 +126,7 @@ class TileImageManager { /// Remove all tiles with a given [EvictErrorTileStrategy]. void removeAll(EvictErrorTileStrategy evictStrategy) { - final keysToRemove = List.from(_tiles.keys); + final keysToRemove = List.from(_positionCoordinates); for (final key in keysToRemove) { _removeWithEvictionStrategy(key, evictStrategy); @@ -140,6 +169,7 @@ class TileImageManager { }) { final pruningState = TileImageView( tileImages: _tiles, + positionCoordinates: _positionCoordinates, visibleRange: visibleRange, keepRange: visibleRange.expand(pruneBuffer), ); @@ -154,13 +184,13 @@ class TileImageManager { ) { switch (evictStrategy) { case EvictErrorTileStrategy.notVisibleRespectMargin: - for (final tileImage + for (final coordinates in tileRemovalState.errorTilesOutsideOfKeepMargin()) { - _remove(tileImage.coordinates, evictImageFromCache: (_) => true); + _remove(coordinates, evictImageFromCache: (_) => true); } case EvictErrorTileStrategy.notVisible: - for (final tileImage in tileRemovalState.errorTilesNotVisible()) { - _remove(tileImage.coordinates, evictImageFromCache: (_) => true); + for (final coordinates in tileRemovalState.errorTilesNotVisible()) { + _remove(coordinates, evictImageFromCache: (_) => true); } case EvictErrorTileStrategy.dispose: case EvictErrorTileStrategy.none: @@ -177,6 +207,7 @@ class TileImageManager { _prune( TileImageView( tileImages: _tiles, + positionCoordinates: _positionCoordinates, visibleRange: visibleRange, keepRange: visibleRange.expand(pruneBuffer), ), @@ -189,8 +220,8 @@ class TileImageManager { TileImageView tileRemovalState, EvictErrorTileStrategy evictStrategy, ) { - for (final tileImage in tileRemovalState.staleTiles) { - _removeWithEvictionStrategy(tileImage.coordinates, evictStrategy); + for (final coordinates in tileRemovalState.staleTiles) { + _removeWithEvictionStrategy(coordinates, evictStrategy); } } } diff --git a/lib/src/layer/tile_layer/tile_image_view.dart b/lib/src/layer/tile_layer/tile_image_view.dart index eb80f7540..fb39c591b 100644 --- a/lib/src/layer/tile_layer/tile_image_view.dart +++ b/lib/src/layer/tile_layer/tile_image_view.dart @@ -7,70 +7,109 @@ import 'package:flutter_map/src/layer/tile_layer/tile_range.dart'; /// [TileCoordinates]. final class TileImageView { final Map _tileImages; + final Set _positionCoordinates; final DiscreteTileRange _visibleRange; final DiscreteTileRange _keepRange; /// Create a new [TileImageView] instance. const TileImageView({ required Map tileImages, + required Set positionCoordinates, required DiscreteTileRange visibleRange, required DiscreteTileRange keepRange, }) : _tileImages = tileImages, + _positionCoordinates = positionCoordinates, _visibleRange = visibleRange, _keepRange = keepRange; /// Get a list with all tiles that have an error and are outside of the /// margin that should get kept. - List errorTilesOutsideOfKeepMargin() => _tileImages.values - .where((tileImage) => - tileImage.loadError && !_keepRange.contains(tileImage.coordinates)) - .toList(); + List errorTilesOutsideOfKeepMargin() => + _errorTilesWithinRange(_keepRange); /// Get a list with all tiles that are not visible on the current map /// viewport. - List errorTilesNotVisible() => _tileImages.values - .where((tileImage) => - tileImage.loadError && !_visibleRange.contains(tileImage.coordinates)) - .toList(); + List errorTilesNotVisible() => + _errorTilesWithinRange(_visibleRange); + + /// Get a list with all tiles that are not visible on the current map + /// viewport. + List _errorTilesWithinRange(DiscreteTileRange range) { + final List result = []; + for (final positionCoordinates in _positionCoordinates) { + if (range.contains(positionCoordinates)) { + continue; + } + final TileImage? tileImage = + _tileImages[TileCoordinates.key(positionCoordinates)]; + if (tileImage?.loadError ?? false) { + result.add(positionCoordinates); + } + } + return result; + } /// Get a list of [TileImage] that are stale and can get for pruned. - Iterable get staleTiles { - final stale = HashSet(); - final retain = HashSet(); - - for (final tile in _tileImages.values) { - final c = tile.coordinates; - if (!_keepRange.contains(c)) { - stale.add(tile); + Iterable get staleTiles { + final stale = HashSet(); + final retain = HashSet(); + + for (final positionCoordinates in _positionCoordinates) { + if (!_keepRange.contains(positionCoordinates)) { + stale.add(positionCoordinates); continue; } - final retainedAncestor = _retainAncestor(retain, c.x, c.y, c.z, c.z - 5); + final retainedAncestor = _retainAncestor( + retain, + positionCoordinates.x, + positionCoordinates.y, + positionCoordinates.z, + positionCoordinates.z - 5, + ); if (!retainedAncestor) { - _retainChildren(retain, c.x, c.y, c.z, c.z + 2); + _retainChildren( + retain, + positionCoordinates.x, + positionCoordinates.y, + positionCoordinates.z, + positionCoordinates.z + 2, + ); } } return stale.where((tile) => !retain.contains(tile)); } - /// Get a list of [TileImage] that need to get rendered on screen. - Iterable get renderTiles { - final retain = HashSet(); + /// Get a list of [TileCoordinates] that need to get rendered on screen. + Iterable get renderTiles { + final retain = HashSet(); - for (final tile in _tileImages.values) { - final c = tile.coordinates; - if (!_visibleRange.contains(c)) { + for (final positionCoordinates in _positionCoordinates) { + if (!_visibleRange.contains(positionCoordinates)) { continue; } - retain.add(tile); - - if (!tile.readyToDisplay) { - final retainedAncestor = - _retainAncestor(retain, c.x, c.y, c.z, c.z - 5); + retain.add(positionCoordinates); + + final TileImage? tile = + _tileImages[TileCoordinates.key(positionCoordinates)]; + if (tile == null || !tile.readyToDisplay) { + final retainedAncestor = _retainAncestor( + retain, + positionCoordinates.x, + positionCoordinates.y, + positionCoordinates.z, + positionCoordinates.z - 5, + ); if (!retainedAncestor) { - _retainChildren(retain, c.x, c.y, c.z, c.z + 2); + _retainChildren( + retain, + positionCoordinates.x, + positionCoordinates.y, + positionCoordinates.z, + positionCoordinates.z + 2, + ); } } } @@ -81,7 +120,7 @@ final class TileImageView { /// them to [retain] if they are ready to display or loaded. Returns true if /// any of the ancestor tiles were ready to display. bool _retainAncestor( - Set retain, + Set retain, int x, int y, int z, @@ -92,13 +131,13 @@ final class TileImageView { final z2 = z - 1; final coords2 = TileCoordinates(x2, y2, z2); - final tile = _tileImages[coords2]; + final tile = _tileImages[TileCoordinates.key(coords2)]; if (tile != null) { if (tile.readyToDisplay) { - retain.add(tile); + retain.add(coords2); return true; } else if (tile.loadFinishedAt != null) { - retain.add(tile); + retain.add(coords2); } } @@ -112,7 +151,7 @@ final class TileImageView { /// Recurse through the descendants of the Tile at the given coordinates /// adding them to [retain] if they are ready to display or loaded. void _retainChildren( - Set retain, + Set retain, int x, int y, int z, @@ -121,10 +160,10 @@ final class TileImageView { for (final (i, j) in const [(0, 0), (0, 1), (1, 0), (1, 1)]) { final coords = TileCoordinates(2 * x + i, 2 * y + j, z + 1); - final tile = _tileImages[coords]; + final tile = _tileImages[TileCoordinates.key(coords)]; if (tile != null) { if (tile.readyToDisplay || tile.loadFinishedAt != null) { - retain.add(tile); + retain.add(coords); // If have the child, we do not recurse. We don't need the child's children. continue; diff --git a/lib/src/layer/tile_layer/tile_layer.dart b/lib/src/layer/tile_layer/tile_layer.dart index 09c0e75f5..b7067613b 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -504,16 +504,17 @@ class _TileLayerState extends State with TickerProviderStateMixin { // cycles saved later on in the render pipeline. final tiles = _tileImageManager .getTilesToRender(visibleRange: visibleTileRange) - .map((tileImage) => Tile( + .map((tileRenderer) => Tile( // Must be an ObjectKey, not a ValueKey using the coordinates, in // case we remove and replace the TileImage with a different one. - key: ObjectKey(tileImage), + key: ObjectKey(tileRenderer), scaledTileSize: _tileScaleCalculator.scaledTileSize( map.zoom, - tileImage.coordinates.z, + tileRenderer.positionCoordinates.z, ), currentPixelOrigin: map.pixelOrigin, - tileImage: tileImage, + tileImage: tileRenderer.tileImage, + positionCoordinates: tileRenderer.positionCoordinates, tileBuilder: widget.tileBuilder, )) .toList(); diff --git a/lib/src/layer/tile_layer/tile_range.dart b/lib/src/layer/tile_layer/tile_range.dart index e39200be1..8970e54e1 100644 --- a/lib/src/layer/tile_layer/tile_range.dart +++ b/lib/src/layer/tile_layer/tile_range.dart @@ -111,8 +111,24 @@ class DiscreteTileRange extends TileRange { } /// Check if a [Point] is inside of the bounds of the [DiscreteTileRange]. + /// + /// We use a modulo in order to prevent side-effects at the end of the world. bool contains(Point point) { - return _bounds.contains(point); + final int modulo = 1 << zoom; + + bool containsCoordinate(int value, int min, int max) { + int tmp = value; + while (tmp < min) { + tmp += modulo; + } + while (tmp > max) { + tmp -= modulo; + } + return tmp >= min && tmp <= max; + } + + return containsCoordinate(point.x, min.x, max.x) && + containsCoordinate(point.y, min.y, max.y); } /// The minimum [Point] of the [DiscreteTileRange] diff --git a/lib/src/layer/tile_layer/tile_renderer.dart b/lib/src/layer/tile_layer/tile_renderer.dart new file mode 100644 index 000000000..2425b32aa --- /dev/null +++ b/lib/src/layer/tile_layer/tile_renderer.dart @@ -0,0 +1,30 @@ +import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart'; +import 'package:flutter_map/src/layer/tile_layer/tile_image.dart'; + +/// Display of a [TileImage] at given [TileCoordinates]. +/// +/// In most cases, the [positionCoordinates] are equal to tileImage coordinates. +/// Except when we display several worlds in the same map, or when we cross the +/// 180/-180 border. +class TileRenderer { + /// TileImage to display. + final TileImage tileImage; + + /// Position where to display [tileImage]. + final TileCoordinates positionCoordinates; + + /// Create an instance of [TileRenderer]. + const TileRenderer(this.tileImage, this.positionCoordinates); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + return other is TileRenderer && + other.positionCoordinates == positionCoordinates; + } + + @override + int get hashCode => positionCoordinates.hashCode; +} diff --git a/lib/src/map/camera/camera.dart b/lib/src/map/camera/camera.dart index 2bd37d522..18dc81eab 100644 --- a/lib/src/map/camera/camera.dart +++ b/lib/src/map/camera/camera.dart @@ -179,13 +179,30 @@ class MapCamera { crs: crs, minZoom: minZoom, maxZoom: maxZoom, - center: center ?? this.center, + center: _adjustPositionForSeamlessScrolling(center), zoom: zoom ?? this.zoom, rotation: rotation, nonRotatedSize: nonRotatedSize, size: _cameraSize, ); + /// Jumps camera to opposite side of the world to enable seamless scrolling + /// between 180 and -180 longitude. + LatLng _adjustPositionForSeamlessScrolling(LatLng? position) { + if (position == null) { + return center; + } + double adjustedLongitude = position.longitude; + if (adjustedLongitude >= 180.0) { + adjustedLongitude -= 360.0; + } else if (adjustedLongitude <= -180.0) { + adjustedLongitude += 360.0; + } + return adjustedLongitude == position.longitude + ? position + : LatLng(position.latitude, adjustedLongitude); + } + /// Calculates the size of a bounding box which surrounds a box of size /// [nonRotatedSize] which is rotated by [rotation]. static Point calculateRotatedSize( diff --git a/test/layer/tile_layer/tile_image_view_test.dart b/test/layer/tile_layer/tile_image_view_test.dart index 9bf21922b..00e7820a7 100644 --- a/test/layer/tile_layer/tile_image_view_test.dart +++ b/test/layer/tile_layer/tile_image_view_test.dart @@ -14,18 +14,6 @@ void main() { List tileImages) => {for (final tileImage in tileImages) tileImage.coordinates: tileImage}; - Matcher containsTileImage( - Map tileImages, - TileCoordinates coordinates, - ) => - contains(tileImages[coordinates]); - - Matcher doesNotContainTileImage( - Map tileImages, - TileCoordinates coordinates, - ) => - isNot(containsTileImage(tileImages, coordinates)); - DiscreteTileRange discreteTileRange( int x1, int y1, @@ -40,19 +28,21 @@ void main() { group('staleTiles', () { test('tiles outside of the keep range are stale', () { + const zoom = 10; final tileImages = tileImagesMappingFrom([ - MockTileImage(1, 1, 1), - MockTileImage(2, 1, 1), + MockTileImage(1, 1, zoom), + MockTileImage(2, 1, zoom), ]); final removalState = TileImageView( tileImages: tileImages, - visibleRange: discreteTileRange(2, 1, 3, 3, zoom: 1), - keepRange: discreteTileRange(2, 1, 3, 3, zoom: 1), + positionCoordinates: Set.from(tileImages.keys), + visibleRange: discreteTileRange(2, 1, 3, 3, zoom: zoom), + keepRange: discreteTileRange(2, 1, 3, 3, zoom: zoom), ); expect( removalState.staleTiles, - containsTileImage(tileImages, const TileCoordinates(1, 1, 1)), + contains(const TileCoordinates(1, 1, zoom)), ); }); @@ -63,12 +53,13 @@ void main() { ]); final removalState = TileImageView( tileImages: tileImages, + positionCoordinates: Set.from(tileImages.keys), visibleRange: discreteTileRange(0, 0, 0, 0, zoom: 1), keepRange: discreteTileRange(0, 0, 0, 0, zoom: 1), ); expect( removalState.staleTiles, - doesNotContainTileImage(tileImages, const TileCoordinates(0, 0, 0)), + isNot(contains(const TileCoordinates(0, 0, 0))), ); }); @@ -81,37 +72,40 @@ void main() { ]); final removalState = TileImageView( tileImages: tileImages, + positionCoordinates: Set.from(tileImages.keys), visibleRange: discreteTileRange(0, 0, 0, 0, zoom: 1), keepRange: discreteTileRange(0, 0, 0, 0, zoom: 1), ); expect( removalState.staleTiles, - doesNotContainTileImage(tileImages, const TileCoordinates(0, 0, 2)), + isNot(contains(const TileCoordinates(0, 0, 2))), ); }); test( 'returned elements can be removed from the source collection in a for loop', () { + const zoom = 10; final tileImages = tileImagesMappingFrom([ - MockTileImage(1, 1, 1), + MockTileImage(1, 1, zoom), ]); final removalState = TileImageView( tileImages: tileImages, - visibleRange: discreteTileRange(2, 1, 3, 3, zoom: 1), - keepRange: discreteTileRange(2, 1, 3, 3, zoom: 1), + positionCoordinates: Set.from(tileImages.keys), + visibleRange: discreteTileRange(2, 1, 3, 3, zoom: zoom), + keepRange: discreteTileRange(2, 1, 3, 3, zoom: zoom), ); expect( removalState.staleTiles, - containsTileImage(tileImages, const TileCoordinates(1, 1, 1)), + contains(const TileCoordinates(1, 1, zoom)), ); // If an iterator over the original collection is returned then when // looping over that iterator and removing from the original collection // a concurrent modification exception is thrown. This ensures that the // returned collection is not an iterable over the original collection. for (final staleTile in removalState.staleTiles) { - tileImages.remove(staleTile.coordinates)!; + tileImages.remove(staleTile)!; } }); }); @@ -125,11 +119,12 @@ void main() { ]); final tileImageView = TileImageView( tileImages: tileImages, + positionCoordinates: Set.from(tileImages.keys), visibleRange: discreteTileRange(1, 2, 1, 2, zoom: 1), keepRange: discreteTileRange(1, 2, 2, 2, zoom: 1), ); expect( - tileImageView.errorTilesOutsideOfKeepMargin().map((e) => e.coordinates), + tileImageView.errorTilesOutsideOfKeepMargin(), [const TileCoordinates(1, 1, 1)], ); @@ -137,34 +132,36 @@ void main() { // looping over that iterator and removing from the original collection // a concurrent modification exception is thrown. This ensures that the // returned collection is not an iterable over the original collection. - for (final tileImage in tileImageView.errorTilesOutsideOfKeepMargin()) { - tileImages.remove(tileImage.coordinates)!; + for (final coordinates in tileImageView.errorTilesOutsideOfKeepMargin()) { + tileImages.remove(coordinates)!; } }); test('errorTilesNotVisible', () { + const zoom = 10; final tileImages = tileImagesMappingFrom([ - MockTileImage(1, 1, 1, loadError: true), - MockTileImage(2, 1, 1), - MockTileImage(1, 2, 1), - MockTileImage(2, 2, 1, loadError: true), + MockTileImage(1, 1, zoom, loadError: true), + MockTileImage(2, 1, zoom), + MockTileImage(1, 2, zoom), + MockTileImage(2, 2, zoom, loadError: true), ]); final tileImageView = TileImageView( tileImages: tileImages, - visibleRange: discreteTileRange(1, 2, 1, 2, zoom: 1), - keepRange: discreteTileRange(1, 2, 2, 2, zoom: 1), + positionCoordinates: Set.from(tileImages.keys), + visibleRange: discreteTileRange(1, 2, 1, 2, zoom: zoom), + keepRange: discreteTileRange(1, 2, 2, 2, zoom: zoom), ); expect( - tileImageView.errorTilesNotVisible().map((e) => e.coordinates), - [const TileCoordinates(1, 1, 1), const TileCoordinates(2, 2, 1)], + tileImageView.errorTilesNotVisible(), + [const TileCoordinates(1, 1, zoom), const TileCoordinates(2, 2, zoom)], ); // If an iterator over the original collection is returned then when // looping over that iterator and removing from the original collection // a concurrent modification exception is thrown. This ensures that the // returned collection is not an iterable over the original collection. - for (final tileImage in tileImageView.errorTilesOutsideOfKeepMargin()) { - tileImages.remove(tileImage.coordinates)!; + for (final coordinates in tileImageView.errorTilesOutsideOfKeepMargin()) { + tileImages.remove(coordinates)!; } }); } diff --git a/test/layer/tile_layer/tile_range_test.dart b/test/layer/tile_layer/tile_range_test.dart index 346ffb532..62d001557 100644 --- a/test/layer/tile_layer/tile_range_test.dart +++ b/test/layer/tile_layer/tile_range_test.dart @@ -263,7 +263,7 @@ void main() { test('contains', () { final tileRange = DiscreteTileRange.fromPixelBounds( - zoom: 0, + zoom: 10, tileSize: 10, pixelBounds: Bounds( const Point(35, 35),