flutter_map_vector_tiles_mbtiles 1.0.0 copy "flutter_map_vector_tiles_mbtiles: ^1.0.0" to clipboard
flutter_map_vector_tiles_mbtiles: ^1.0.0 copied to clipboard

MBTiles support for flutter_map_vector_tiles: render a local .mbtiles archive as a flutter_map vector or raster source, fully offline, with SQLite reads kept off the UI isolate.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_vector_tiles/flutter_map_vector_tiles.dart' as vt;
import 'package:flutter_map_vector_tiles_mbtiles/flutter_map_vector_tiles_mbtiles.dart';
import 'package:latlong2/latlong.dart';

/// Renders a local `.mbtiles` archive with a hosted style's theme.
///
/// ```
/// flutter run \
///   --dart-define=MBTILES_PATH=/absolute/path/to/region.mbtiles \
///   --dart-define=SOURCE_ID=openmaptiles \
///   --dart-define=STYLE_URL=https://tiles.openfreemap.org/styles/liberty
/// ```
///
/// `SOURCE_ID` is the id of the vector source in that style that the
/// archive should replace. If you do not know it, run once and read the
/// console: every source id the style declares is logged as it is
/// offered to [vt.StyleReader.resolveProvider].
///
/// Native only — there is no web target here, since MBTiles is SQLite
/// read through `dart:ffi`.
const _archivePath = String.fromEnvironment('MBTILES_PATH');
const _sourceId = String.fromEnvironment(
  'SOURCE_ID',
  defaultValue: 'openmaptiles',
);
const _styleUrl = String.fromEnvironment(
  'STYLE_URL',
  defaultValue: 'https://tiles.openfreemap.org/styles/liberty',
);

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) => const MaterialApp(
    title: 'flutter_map_vector_tiles_mbtiles example',
    home: MbTilesMapPage(),
  );
}

class MbTilesMapPage extends StatefulWidget {
  const MbTilesMapPage({super.key});

  @override
  State<MbTilesMapPage> createState() => _MbTilesMapPageState();
}

class _MbTilesMapPageState extends State<MbTilesMapPage> {
  late final Future<vt.Style> _style;
  MbTilesVectorTileProvider? _archive;

  @override
  void initState() {
    super.initState();
    _style = _load();
  }

  Future<vt.Style> _load() async {
    if (_archivePath.isEmpty) {
      throw ArgumentError(
        'Pass --dart-define=MBTILES_PATH=/path/to/region.mbtiles',
      );
    }
    final archive = await MbTilesVectorTileProvider.open(
      _archivePath,
      logger: const vt.Logger.console(),
    );
    _archive = archive;

    // The style contributes the theme, sprites and attribution; the
    // archive contributes the tiles. Nothing about the tile path touches
    // the network once the style is cached.
    return vt.StyleReader(
      uri: _styleUrl,
      logger: const vt.Logger.console(),
      resolveProvider: (id) async {
        debugPrint('style declares source "$id"');
        return id == _sourceId ? archive : null;
      },
    ).read();
  }

  @override
  void dispose() {
    // Style.dispose() owns whatever resolveProvider handed back, so the
    // archive is closed with it — disposing _archive here as well would
    // be a double dispose.
    _style.then((style) => style.dispose()).ignore();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const _MbTilesAppBar(),
      body: FutureBuilder(
        future: _style,
        builder: (context, snapshot) {
          final style = snapshot.data;
          if (style == null) {
            final error = snapshot.error;
            return Center(
              child: error != null
                  ? Padding(
                      padding: const EdgeInsets.all(24),
                      child: Text('Failed to load:\n$error'),
                    )
                  : const CircularProgressIndicator(),
            );
          }
          final bounds = _archive?.metadata.bounds;
          return FlutterMap(
            options: MapOptions(
              // An archive covers one region, so start inside it rather
              // than wherever the style suggests.
              initialCenter:
                  bounds?.center ??
                  style.center ??
                  const LatLng(48.137, 11.575),
              initialZoom: style.zoom ?? 12,
              minZoom: 2,
              maxZoom: 21,
            ),
            children: [
              vt.VectorTileLayer(
                theme: style.theme,
                tileProviders: style.providers,
                rasterSources: style.rasterSources,
                sprites: style.sprites,
                logger: const vt.Logger.console(),
              ),
              _Attribution(style: style, archive: _archive),
            ],
          );
        },
      ),
    );
  }
}

class _MbTilesAppBar extends StatelessWidget implements PreferredSizeWidget {
  const _MbTilesAppBar();

  @override
  Size get preferredSize => const Size.fromHeight(kToolbarHeight);

  @override
  Widget build(BuildContext context) => AppBar(title: const Text('MBTiles'));
}

/// Whatever the style's sources declare, plus the archive's own
/// `attribution` row — archives routinely carry the one their data
/// requires, and it is not shown for you.
class _Attribution extends StatelessWidget {
  final vt.Style style;
  final MbTilesVectorTileProvider? archive;

  const _Attribution({required this.style, required this.archive});

  @override
  Widget build(BuildContext context) {
    final parts = <String>[
      for (final attribution in style.attributions) attribution.text,
      ?archive?.metadata.attribution,
    ];
    if (parts.isEmpty) return const SizedBox.shrink();
    return Align(
      alignment: Alignment.bottomCenter,
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: DecoratedBox(
          decoration: BoxDecoration(
            color: Colors.white.withValues(alpha: 0.7),
            borderRadius: BorderRadius.circular(16),
          ),
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
            child: Text(
              'flutter_map | © ${parts.join(' · ')}',
              textAlign: TextAlign.center,
              style: const TextStyle(fontSize: 12, color: Colors.black87),
            ),
          ),
        ),
      ),
    );
  }
}
0
likes
160
points
64
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

MBTiles support for flutter_map_vector_tiles: render a local .mbtiles archive as a flutter_map vector or raster source, fully offline, with SQLite reads kept off the UI isolate.

Repository (GitHub)
View/report issues

Topics

#flutter-map #vector-tiles #mbtiles #offline #maplibre

License

BSD-3-Clause (license)

Dependencies

flutter, flutter_map, flutter_map_vector_tiles, latlong2, sqlite3

More

Packages that depend on flutter_map_vector_tiles_mbtiles