Skip to content

Commit

Permalink
Go all in: use the inverse logic of storage pruning for render prunin…
Browse files Browse the repository at this point in the history
…g to render the absolute minimum number of tiles that maximizes coverage.

As a opportunistic cleanup, also use Bounds<double> specifically wherever we deal with pixels.
  • Loading branch information
ignatz committed Nov 6, 2023
1 parent 2f99218 commit f32f36b
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 55 deletions.
8 changes: 4 additions & 4 deletions lib/src/geo/crs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ abstract class Crs {
double zoom(double scale) => math.log(scale / 256) / math.ln2;

/// Rescales the bounds to a given zoom value.
Bounds? getProjectedBounds(double zoom) {
Bounds<double>? getProjectedBounds(double zoom) {
if (infinite) return null;

final b = projection.bounds!;
final s = scale(zoom);
final min = transformation.transform(b.min, s);
final max = transformation.transform(b.max, s);
return Bounds(min, max);
return Bounds<double>(min, max);
}

bool get infinite;
Expand Down Expand Up @@ -239,7 +239,7 @@ class Proj4Crs extends Crs {

/// Rescales the bounds to a given zoom value.
@override
Bounds? getProjectedBounds(double zoom) {
Bounds<double>? getProjectedBounds(double zoom) {
if (infinite) return null;

final b = projection.bounds!;
Expand All @@ -249,7 +249,7 @@ class Proj4Crs extends Crs {

final min = transformation.transform(b.min, s);
final max = transformation.transform(b.max, s);
return Bounds(min, max);
return Bounds<double>(min, max);
}

/// Zoom to Scale function.
Expand Down
17 changes: 8 additions & 9 deletions lib/src/layer/tile_layer/tile_bounds/tile_bounds.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'package:flutter_map/src/geo/latlng_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_range.dart';
import 'package:flutter_map/src/misc/bounds.dart';
import 'package:flutter_map/src/misc/point_extensions.dart';
import 'package:latlong2/latlong.dart';
import 'package:meta/meta.dart';

Expand Down Expand Up @@ -76,13 +75,13 @@ class DiscreteTileBounds extends TileBounds {
TileBoundsAtZoom _tileBoundsAtZoomImpl(int zoom) {
final zoomDouble = zoom.toDouble();

final Bounds<num> pixelBounds;
final Bounds<double> pixelBounds;
if (_latLngBounds == null) {
pixelBounds = crs.getProjectedBounds(zoomDouble)!;
} else {
pixelBounds = Bounds(
crs.latLngToPoint(_latLngBounds!.southWest, zoomDouble).floor(),
crs.latLngToPoint(_latLngBounds!.northEast, zoomDouble).ceil(),
pixelBounds = Bounds<double>(
crs.latLngToPoint(_latLngBounds!.southWest, zoomDouble),
crs.latLngToPoint(_latLngBounds!.northEast, zoomDouble),
);
}

Expand Down Expand Up @@ -115,13 +114,13 @@ class WrappedTileBounds extends TileBounds {
WrappedTileBoundsAtZoom _tileBoundsAtZoomImpl(int zoom) {
final zoomDouble = zoom.toDouble();

final Bounds<num> pixelBounds;
final Bounds<double> pixelBounds;
if (_latLngBounds == null) {
pixelBounds = crs.getProjectedBounds(zoomDouble)!;
} else {
pixelBounds = Bounds(
crs.latLngToPoint(_latLngBounds!.southWest, zoomDouble).floor(),
crs.latLngToPoint(_latLngBounds!.northEast, zoomDouble).ceil(),
pixelBounds = Bounds<double>(
crs.latLngToPoint(_latLngBounds!.southWest, zoomDouble),
crs.latLngToPoint(_latLngBounds!.northEast, zoomDouble),
);
}

Expand Down
19 changes: 16 additions & 3 deletions lib/src/layer/tile_layer/tile_image_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,23 @@ class TileImageManager {
bool get allLoaded =>
_tiles.values.none((tile) => tile.loadFinishedAt == null);

Iterable<TileImage> get tiles => _tiles.values;
/// Filter tiles to only tiles that would visible on screen. Specifically:
/// 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<TileImage> renderTiles({
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();

// Creates missing tiles in the given range. Does not initiate loading of the
// tiles.
/// Creates missing tiles in the given range. Does not initiate loading of the
/// tiles.
void createMissingTiles(
DiscreteTileRange tileRange,
TileBoundsAtZoom tileBoundsAtZoom, {
Expand Down
20 changes: 20 additions & 0 deletions lib/src/layer/tile_layer/tile_image_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ class TileImageView {
return stale.where((tile) => !retain.contains(tile));
}

Iterable<TileImage> renderTiles() {
final retain = HashSet<TileImage>();

for (final tile in _tileImages.values) {
final c = tile.coordinates;
if (_visibleRange.contains(c)) {
retain.add(tile);

if (!tile.readyToDisplay) {
final retainedAncestor =
_retainAncestor(retain, c.x, c.y, c.z, c.z - 5);
if (!retainedAncestor) {
_retainChildren(retain, c.x, c.y, c.z, c.z + 2);
}
}
}
}
return retain;
}

// Recurses through the ancestors of the Tile at the given coordinates adding
// them to [retain] if they are ready to display or loaded. Returns true if
// any of the ancestor tiles were ready to display.
Expand Down
38 changes: 7 additions & 31 deletions lib/src/layer/tile_layer/tile_layer.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:collection';
import 'dart:math';

import 'package:collection/collection.dart' show MapEquality;
Expand Down Expand Up @@ -534,36 +533,13 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {

_tileScaleCalculator.clearCacheUnlessZoomMatches(map.zoom);

// Note: We're filtering out tiles that are off screen and all tiles at a
// different zoom level but only if all tiles at the target level are ready
// to display. This saves us render time later.
bool allTilesAtTargetZoomLevelReady = true;
for (final tile in _tileImageManager.tiles) {
if (tile.coordinates.z == tileZoom) {
allTilesAtTargetZoomLevelReady &= tile.readyToDisplay;
}
}

final otherVisibleTileRanges = HashMap<int, DiscreteTileRange>();
final tiles = _tileImageManager.tiles
.where((tile) {
final c = tile.coordinates;
final zoom = c.z;

if (zoom != tileZoom && allTilesAtTargetZoomLevelReady) {
return false;
}

final visibleRange = (zoom == tileZoom)
? visibleTileRange
: (otherVisibleTileRanges[zoom] ??=
_tileRangeCalculator.calculate(
camera: map,
tileZoom: zoom,
));

return visibleRange.contains(c);
})
// Note: `renderTiles` filters out all tiles that are either off-screen or
// tiles at non-target zoom levels that are would be completely covered by
// tiles that are *ready* and at the target zoom level.
// We're happy to do a bit of diligent work here, since tiles not rendered are
// cycles saved later on in the render pipeline.
final tiles = _tileImageManager
.renderTiles(visibleRange: visibleTileRange)
.map((tileImage) => Tile(
// Must be an ObjectKey, not a ValueKey using the coordinates, in
// case we remove and replace the TileImage with a different one.
Expand Down
14 changes: 7 additions & 7 deletions lib/src/layer/tile_layer/tile_range.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class DiscreteTileRange extends TileRange {
factory DiscreteTileRange.fromPixelBounds({
required int zoom,
required double tileSize,
required Bounds<num> pixelBounds,
required Bounds<double> pixelBounds,
}) {
final Bounds<int> bounds;
if (pixelBounds.min == pixelBounds.max) {
Expand Down Expand Up @@ -77,9 +77,9 @@ class DiscreteTileRange extends TileRange {

return DiscreteTileRange(
zoom,
Bounds(
Point(math.max(min.x, minX), min.y),
Point(math.min(max.x, maxX), max.y),
Bounds<int>(
Point<int>(math.max(min.x, minX), min.y),
Point<int>(math.min(max.x, maxX), max.y),
),
);
}
Expand All @@ -92,9 +92,9 @@ class DiscreteTileRange extends TileRange {

return DiscreteTileRange(
zoom,
Bounds(
Point(min.x, math.max(min.y, minY)),
Point(max.x, math.min(max.y, maxY)),
Bounds<int>(
Point<int>(min.x, math.max(min.y, minY)),
Point<int>(max.x, math.min(max.y, maxY)),
),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import 'package:test/test.dart';
void main() {
group('TileBoundsAtZoom', () {
const hugeCoordinate = TileCoordinates(999999999, 999999999, 0);
final hugePoint =
Point<double>(hugeCoordinate.x.toDouble(), hugeCoordinate.y.toDouble());
final tileRangeWithHugeCoordinate = DiscreteTileRange.fromPixelBounds(
zoom: 0,
tileSize: 1,
pixelBounds: Bounds(hugeCoordinate, hugeCoordinate),
pixelBounds: Bounds(hugePoint, hugePoint),
);

DiscreteTileRange discreteTileRange(
Expand Down

0 comments on commit f32f36b

Please sign in to comment.