Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
be00c16
Start of modern tile layer implementation
JaffaKetchup Jul 6, 2025
c3745ad
Continued implementation, mostly of the tile loader part
JaffaKetchup Jul 7, 2025
59eaa6a
Started transplanting `TileImage` to `RasterTileData`
JaffaKetchup Jul 8, 2025
7dfd59d
Add TODO
JaffaKetchup Jul 8, 2025
cd9541d
Merge branch 'master' into modern-tile-layer
JaffaKetchup Jul 10, 2025
ac648ca
Merge branch 'master' into modern-tile-layer
JaffaKetchup Jul 10, 2025
21d2945
Integrated caching
JaffaKetchup Jul 11, 2025
12ee8eb
Merge remote-tracking branch 'origin' into modern-tile-layer
JaffaKetchup Jul 11, 2025
f65d3e3
Integrate fix from #2125
JaffaKetchup Jul 11, 2025
96ed2ca
Start allowing multiple fallbacks
JaffaKetchup Jul 13, 2025
3f995e7
Re-introduce `TileSource` as an extension of `Iterable<String>`
JaffaKetchup Jul 20, 2025
fb8fe83
Refactoring & renaming
JaffaKetchup Jul 20, 2025
1e2ef7f
Merge branch 'master' into modern-tile-layer
JaffaKetchup Aug 11, 2025
bcd51f9
Removed generic `TileLoader`, collapsing responsibilities into `Raste…
JaffaKetchup Aug 11, 2025
cac5393
Fixed leaky abstraction of caching metadata by decomposing metadata c…
JaffaKetchup Aug 12, 2025
2d7f644
Replaced public `CachedTileMetadata` constructor with `stale` & `fres…
JaffaKetchup Aug 12, 2025
8de1f3c
Avoid putting bytes which aren't a valid resource into the cache
JaffaKetchup Sep 11, 2025
85912ba
Merge remote-tracking branch 'origin/master' into modern-tile-layer
JaffaKetchup Nov 23, 2025
1c23823
Basic modern raster tile layer working
JaffaKetchup Nov 23, 2025
88887b6
Cleanup and refactoring
JaffaKetchup Nov 24, 2025
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ migrate_working_dir/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
.vscode/

# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
Expand Down
5 changes: 4 additions & 1 deletion example/lib/pages/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ class _HomePageState extends State<HomePage> {
initialZoom: 5,
),
children: [
openStreetMapTileLayer,
const RasterTileLayer.simple(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
uaIdentifier: 'dev.fleaflet.flutter_map.demo',
),
RichAttributionWidget(
popupInitialDisplayDuration: const Duration(seconds: 5),
animationConfig: const ScaleRAWA(),
Expand Down
6 changes: 1 addition & 5 deletions lib/flutter_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart';
export 'package:flutter_map/src/layer/attribution_layer/simple.dart';
export 'package:flutter_map/src/layer/circle_layer/circle_layer.dart';
export 'package:flutter_map/src/layer/marker_layer/marker_layer.dart';
export 'package:flutter_map/src/layer/modern_tile_layer/modern_tile_layer.dart';
export 'package:flutter_map/src/layer/overlay_image_layer/overlay_image_layer.dart';
export 'package:flutter_map/src/layer/polygon_layer/label/deprecated_placements.dart';
export 'package:flutter_map/src/layer/polygon_layer/label/placement_calculators/placement_calculator.dart';
Expand All @@ -49,11 +50,6 @@ export 'package:flutter_map/src/layer/tile_layer/tile_provider/asset/provider.da
export 'package:flutter_map/src/layer/tile_layer/tile_provider/base_tile_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/file/stub_tile_provider.dart'
if (dart.library.io) 'package:flutter_map/src/layer/tile_layer/tile_provider/file/native_tile_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/built_in/built_in_caching_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/caching_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/disabled/disabled_caching_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/tile_metadata.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/tile_read_failure_exception.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/tile_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_update_event.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart';
Expand Down
35 changes: 35 additions & 0 deletions lib/src/layer/modern_tile_layer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Modern Tile Layer

The modern tile layer is a rework of the original tile layer which:

* should be significantly more flexible (-> provide better integration support for plugins)
* resolve some hard-to-debug bugs
* improve performance in the default case

It does this by:

* splitting the logic of the current `TileLayer` & `TileProvider` into 3-5 parts:
* `BaseTileLayer`: responsible for tile management (initial workings provided by @mootw)

* a tile loader: responsible for getting the data for individual tiles given the coordinates from the manager
In the default implementation, this is further split:
* a source generator: responsible for telling the source fetcher what to fetch for the tile
* a source fetcher: responsible for actually fetching the tile data
In the default implementation, this is further split:
* a bytes fetcher: responsible for actually fetching the tile data

* a tile renderer: responsible for painting tiled data

* using a canvas implementation for the default raster tile layer

Significant questions remaining:

* Is the default tile loader setup (with two stages) too much frameworking/overly-complicated?
* Simulating retina mode affects all parts of the system - but only (conceptually/for reasoning) applies to raster tiles (although technically it's no different to a top layer option). How should this be represented?
* What should the top-level options be (`TileLayerOptions`)? See also retina mode simulation.
* Who's responsibility is enforcing the max-zoom level? Is max-zoom = native max-zoom or MapOptions.maxZoom?

This new functionality has no deadline or estimated completion date - although it's something we've been wanting to do for a while, and we have some work in the
background which may be integrating with this.

Contribution greatly appriciated!
79 changes: 79 additions & 0 deletions lib/src/layer/modern_tile_layer/base_tile_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'dart:async';

import 'package:flutter_map/src/layer/modern_tile_layer/base_tile_layer.dart';
import 'package:flutter_map/src/layer/modern_tile_layer/tile_loader/tile_loader.dart';
import 'package:meta/meta.dart';

/// Data associated with a particular tile coordinate, which allows
/// bi-directional communication between the [TileLoader] and
/// [BaseTileLayer.renderer].
///
/// The tile layer's internal logic consumes this interface's fields, and can
/// also manipulate the renderer.
abstract interface class BaseTileData {
/// Should be completed when the tile is fully optically visible: the tile
/// layer will start a prune of eligible tiles, and this tile will become
/// eligible for pruning.
///
/// If the tile never becomes fully optically visible, this shouldn't usually
/// be completed.
Future<void> get triggerPrune;

/// Called when a tile is removed from the map of visible tiles passed to the
/// renderer.
///
/// This should usually be used to abort loading of the underlying resource
/// if it has not yet loaded, or release the resources held by it if already
/// loaded.
///
/// If called, then it is assumed that the tile layer's internal logic no
/// longer cares about any other field in the object.
@internal
void dispose();
}

/// Wrapper for custom-shape data as a [BaseTileData].
///
/// The data carried is usually made available asynchronously, for example as
/// the result of an I/O operation or HTTP request. Alternatively, data may be
/// available synchronously if the data is loaded from prepared memory. This
/// container supports either form of data.
class WrapperTileData<D extends Object?> implements BaseTileData {
D? _data;

/// Data resource
///
/// This may be `null` if [D] is nullable & the data is `null`. In this case,
/// use [isLoaded] to determine whether this accurately reflects the `null`
/// data. Otherwise, `null` means the data is not yet available.
D? get data => _data;

final _loadedTracker = Completer<D>.sync();

/// Completes with loaded data when the data is loaded successfully
///
/// This never completes if the data completes to an error.
@override
Future<D> get triggerPrune => _loadedTracker.future;

/// Whether [data] represents the loaded data
bool get isLoaded => _loadedTracker.isCompleted;

@internal
@override
void dispose() => _dispose?.call();
final void Function()? _dispose;

/// Create a container with the specified data (or the data result of the
/// specified future)
WrapperTileData({
required FutureOr<D> data,
void Function()? dispose,
}) : _dispose = dispose {
if (data is Future<D>) {
data.then((data) => _loadedTracker.complete(_data = data));
} else {
_loadedTracker.complete(_data = data);
}
}
}
Loading
Loading