Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/src/layer/tile_layer/tile_cover.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class _TileSet implements TileCover {
Set<TileCoordinates>? _wrappedTiles;

final bool Function(TileCoordinates tile)? _isCovered;
final _expansions = <int, TileCover>{};

_TileSet(
Iterable<TileCoordinates> tiles, {
Expand All @@ -60,8 +61,10 @@ class _TileSet implements TileCover {
TileCoordinates(tile.x % (1 << tile.z), tile.y, tile.z);

@override
TileCover expand(int count) {
if (count == 0) return this;
TileCover expand(int count) =>
count == 0 ? this : _expansions.putIfAbsent(count, () => _expand(count));

TileCover _expand(int count) {
final expanded = LinkedHashSet<TileCoordinates>.of(_tiles);
for (final tile in _tiles) {
for (var y = tile.y - count; y <= tile.y + count; y++) {
Expand Down
98 changes: 66 additions & 32 deletions lib/src/layer/tile_layer/tile_cover_calculator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class _Cover {
final List<Offset> _corners;
final Offset _pixelCenter;
final int _tileZoom;
final _polygons = <int, List<Offset>>{};
final _grounds = <int, _Ground>{};

_Cover({
required this.camera,
Expand Down Expand Up @@ -95,12 +95,15 @@ class _Cover {
camera.getZoomScale(zoom.toDouble(), _tileZoom.toDouble());

/// The visible ground in pixels at [zoom].
List<Offset> _polygon(int zoom) => _polygons.putIfAbsent(zoom, () {
_Ground _ground(int zoom) => _grounds.putIfAbsent(zoom, () {
final center = _centerAt(zoom);
final scale = camera.getZoomScale(viewingZoom, zoom.toDouble());
return [for (final corner in _corners) center + corner / scale];
return _Ground(
[for (final corner in _corners) center + corner / scale]);
});

List<Offset> _polygon(int zoom) => _ground(zoom).points;

TileCover tiles() {
final startZoom =
_zoomAtScale(_corners.map(camera.groundScale).reduce(math.min));
Expand Down Expand Up @@ -158,35 +161,12 @@ class _Cover {
);

/// Whether [tile] and the visible ground overlap in more than an edge, as
/// in [DiscreteTileRange.fromPixelBounds]: a separating axis test.
bool _intersects(TileCoordinates tile) {
final polygon = _polygon(tile.z);
final square = _square(tile);
final squareCorners = [
square.topLeft,
square.topRight,
square.bottomRight,
square.bottomLeft,
];
bool separated(Offset axis) {
double dot(Offset p) => p.dx * axis.dx + p.dy * axis.dy;
final a = polygon.map(dot);
final b = squareCorners.map(dot);
return a.reduce(math.max) <= b.reduce(math.min) ||
b.reduce(math.max) <= a.reduce(math.min);
}

if (separated(const Offset(1, 0)) || separated(const Offset(0, 1))) {
return false;
}
for (var i = 0; i < polygon.length; i++) {
final edge = polygon[(i + 1) % polygon.length] - polygon[i];
// Edges along the x and y axes were tested above.
if (edge.dx == 0 || edge.dy == 0) continue;
if (separated(Offset(-edge.dy, edge.dx))) return false;
}
return true;
}
/// in [DiscreteTileRange.fromPixelBounds].
bool _intersects(TileCoordinates tile) => _ground(tile.z).overlaps(
(tile.x * tileDimension).toDouble(),
(tile.y * tileDimension).toDouble(),
tileDimension.toDouble(),
);

/// The part of the convex [polygon] inside [rect].
static List<Offset> _clip(List<Offset> polygon, Rect rect) {
Expand Down Expand Up @@ -221,3 +201,57 @@ class _Cover {
static Offset _atY(Offset a, Offset b, double y) =>
Offset(a.dx + (b.dx - a.dx) * (y - a.dy) / (b.dy - a.dy), y);
}

/// A convex polygon, prepared for a separating axis test against squares.
class _Ground {
final List<Offset> points;

/// The x and y axes, and the normals of the edges that aren't along them.
final List<Offset> _axes;

/// The smallest and largest projection of [points] onto each of [_axes].
final List<double> _mins;
final List<double> _maxs;

factory _Ground(List<Offset> points) {
final axes = [const Offset(1, 0), const Offset(0, 1)];
for (var i = 0; i < points.length; i++) {
final edge = points[(i + 1) % points.length] - points[i];
if (edge.dx != 0 && edge.dy != 0) axes.add(Offset(-edge.dy, edge.dx));
}
final mins = <double>[];
final maxs = <double>[];
for (final axis in axes) {
var min = double.infinity;
var max = double.negativeInfinity;
for (final p in points) {
final d = p.dx * axis.dx + p.dy * axis.dy;
if (d < min) min = d;
if (d > max) max = d;
}
mins.add(min);
maxs.add(max);
}
return _Ground._(points, axes, mins, maxs);
}

const _Ground._(this.points, this._axes, this._mins, this._maxs);

/// Whether the square at [left], [top] with sides of [size] and this polygon
/// overlap in more than an edge.
bool overlaps(double left, double top, double size) {
final right = left + size;
final bottom = top + size;
for (var i = 0; i < _axes.length; i++) {
final axis = _axes[i];
final x0 = axis.dx * left;
final x1 = axis.dx * right;
final y0 = axis.dy * top;
final y1 = axis.dy * bottom;
final min = math.min(x0, x1) + math.min(y0, y1);
final max = math.max(x0, x1) + math.max(y0, y1);
if (_maxs[i] <= min || max <= _mins[i]) return false;
}
return true;
}
}
38 changes: 30 additions & 8 deletions lib/src/layer/tile_layer/tile_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,15 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {
final _tileImageManager = TileImageManager();
late TileBounds _tileBounds;
late TileCoverCalculator _tileCoverCalculator;

/// The last result of [_visibleTileCover], which a build and the tile
/// update event of the same camera both use.
({
MapCamera camera,
LatLng center,
double zoom,
TileCover cover
})? _lastTileCover;
late TileScaleCalculator _tileScaleCalculator;

// We have to hold on to the mapController hashCode to determine whether we
Expand Down Expand Up @@ -450,6 +459,7 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {

// There is no caching in TileCoverCalculator so we can just replace it.
_tileCoverCalculator = TileCoverCalculator(tileDimension: _tileDimension);
_lastTileCover = null;

if (_tileBounds.shouldReplace(
_tileBounds.crs, _tileDimension, widget.tileBounds)) {
Expand Down Expand Up @@ -661,14 +671,26 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {
MapCamera camera, {
LatLng? center,
double? zoom,
}) =>
_tileCoverCalculator.calculate(
camera: camera,
tileZoom: _clampToNativeZoom(zoom ?? camera.zoom),
minNativeZoom: widget.minNativeZoom,
center: center,
viewingZoom: zoom,
);
}) {
center ??= camera.center;
zoom ??= camera.zoom;
final last = _lastTileCover;
if (last != null &&
last.camera == camera &&
last.center == center &&
last.zoom == zoom) {
return last.cover;
}
final cover = _tileCoverCalculator.calculate(
camera: camera,
tileZoom: _clampToNativeZoom(zoom),
minNativeZoom: widget.minNativeZoom,
center: center,
viewingZoom: zoom,
);
_lastTileCover = (camera: camera, center: center, zoom: zoom, cover: cover);
return cover;
}

// For all valid TileCoordinates in the [tileLoadCover], expanded by the
// [TileLayer.panBuffer], this method will do the following depending on
Expand Down
Loading