How to Minimize Tile Requests in Flutter Map

Are you experiencing excessive tile requests when using flutter_map, potentially leading to high costs with commercial tile providers? This article explores practical strategies to reduce unnecessary tile loading and optimize your map performance.

Understanding the Problem

flutter_map requests tiles every time the map viewport changes due to panning or zooming. Even with caching, rapid movements can generate numerous requests for tiles that the user might not even see. A user panning rapidly or zooming through several levels can trigger thousands of requests in a short period, impacting performance and cost.

Solutions

1. Implement Tile Caching

Caching is a fundamental step. Store downloaded tiles locally to avoid redundant requests. Use a caching mechanism that persists tiles between app sessions. Consider using a library like cached_network_image in conjunction with your tile provider.
Be carefull while storing this sensitive tiles data, user can steal it, Use secure cache.


// Example using cached_network_image (conceptual - adapt to your tile provider)
import 'package:cached_network_image/cached_network_image.dart';

// ... inside your TileLayerOptions builder:
CachedNetworkImage(
  imageUrl: 'your_tile_url',
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
)

2. Implement Request Debouncing or Throttling

This technique introduces a delay before making tile requests. If the viewport changes again within the delay, the previous request is cancelled, and a new one is scheduled. This prevents rapid-fire requests during continuous panning or zooming.


import 'dart:async';

Timer? _debounceTimer;

void onMapMove() {
  if (_debounceTimer?.isActive ?? false) _debounceTimer?.cancel();
  _debounceTimer = Timer(const Duration(milliseconds: 200), () {
    // Trigger tile refresh or redraw here
    refreshTiles(); // Example: Call a function to refresh your tiles
  });
}

void refreshTiles() {
  // Logic to trigger a tile refresh based on the current map viewport
  // This might involve updating the tile provider URL
  print("Refreshing tiles");
  // Example, if you are using a TileLayer with a custom tile provider.
  // setState(() {
  //    tileProvider = YourNewTileProvider();
  // });
}

Explanation: onMapMove() is triggered whenever the map moves. A debounce timer is used to delay the refresh. If the map moves again before the timer completes, the timer is cancelled and restarted. Only when the map is stable for 200 milliseconds (adjust as needed) is the tile refresh triggered.

3. Optimize Zoom Levels

Carefully select the zoom levels supported by your tile provider and limit the user’s ability to zoom beyond those levels. This prevents requests for non-existent tiles.


// Example limiting zoom levels
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong2.dart';

FlutterMap(
  options: MapOptions(
    center: LatLng(51.5, -0.09),
    zoom: 5,
    minZoom: 2,
    maxZoom: 18,
  ),
  children: [
    TileLayer(
      urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
      userAgentPackageName: 'com.example.app',
    ),
  ],
)

4. Implement Viewport Culling

Only request tiles that are actually visible within the current viewport. Some tile providers offer parameters to specify the bounding box of the requested tiles. Use this feature to limit requests to the visible area.

5. Use a Custom Tile Provider (Advanced)

For fine-grained control, consider implementing a custom tile provider that handles tile requests more intelligently. This allows you to integrate custom caching logic, request throttling, and viewport culling.

6. Network request optimisation

Instead of directly using the URL in your Flutter application, consider setting up a proxy server. This server can cache tiles, implement rate limiting, and apply other optimizations to minimize requests to the actual tile provider. This approach provides a centralized point for managing tile requests, making it easier to monitor and control tile usage.

Possible Errors and Solutions

  • Error: Excessive memory usage due to caching.
    • Solution: Implement a cache eviction policy (e.g., Least Recently Used – LRU) to remove older, less frequently accessed tiles from the cache. Set a maximum cache size.
  • Error: Tiles not loading after implementing debouncing.
    • Solution: Adjust the debounce delay. A delay that is too long might prevent tiles from loading promptly. Consider using a more responsive approach like throttling instead of debouncing. Also, ensure that the refreshTiles() function is correctly triggering a tile refresh.
  • Error: Blank tiles appearing when zooming quickly.
    • Solution: Ensure your tile provider supports the zoom levels you are requesting. Implement a loading indicator to show that tiles are being fetched. Implement a Retry mechanism if the first request fails due to network issues.
  • Error: Network Security error on Android due to tile provider using HTTP
    • Solution: Ensure that the tile provider uses HTTPS for secure connections. If you must use HTTP (not recommended), you will have to configure Android’s network security configuration to allow cleartext traffic for the specific domain.

Conclusion

By combining caching, request debouncing, zoom level optimization, and viewport culling, you can significantly reduce the number of tile requests in your flutter_map application, improving performance and reducing costs. Choose the strategies that best suit your application’s needs and tile provider capabilities.