Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use local mbtiles file #170

Closed
tiborrr opened this issue Aug 28, 2023 · 5 comments
Closed

Use local mbtiles file #170

tiborrr opened this issue Aug 28, 2023 · 5 comments

Comments

@tiborrr
Copy link

tiborrr commented Aug 28, 2023

@greensopinion, perhaps this is handy functionality to add. I have created a MBTilesTileProvider that you can use. If you want you can integrate it in your vector map libray. It uses sqflite to access the mbedtiles database.

The only thing you have to do is add a mbtiles file to your assets folder and then add it to the pubspec.yaml.

import 'dart:developer';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:outdoor_air_tracker/utils/constants.dart';
import 'package:vector_map_tiles/vector_map_tiles.dart';
import 'package:vector_map_tiles/src/provider_exception.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';

class MBTilesTileProvider extends VectorTileProvider {
  Future<Database>? _db;

  MBTilesTileProvider() {
    _db = _initDB();
  }

  Future<Database> _initDB() async {
    await copyTileDatabase();
    final dbPath =
        join(await getDatabasesPath(), Constants.tileDatabaseFileName);

    if (kIsWeb) {
      databaseFactory = databaseFactoryFfiWeb;
    }

    return await openDatabase(dbPath);
  }

  @override
  int get maximumZoom => 16;

  @override
  int get minimumZoom => 1;

  @override
  Future<Uint8List> provide(TileIdentity tile) async {
    _checkTile(tile);
    final x = tile.x;
    // Flip the y-coordinate if the database uses the TMS scheme
    final y = (1 << tile.z) - tile.y - 1;
    // const y = 1375;
    final z = tile.z;

    Database? db = await _db;
    List<Map> result = await db!.query(
      'tiles',
      columns: ['tile_data'],
      where: 'zoom_level = ? AND tile_column = ? AND tile_row = ?',
      whereArgs: [z, x, y],
    );

    if (result.isNotEmpty) {
      log('Returning tile [$x, $y, $z]');
      final uncompressedBytes = gzip.decode(result.first['tile_data']);
      return Uint8List.fromList(uncompressedBytes);
    } else {
      log('Tile not found');
      throw ProviderException(
          message: 'Cannot retrieve tile',
          statusCode: 404,
          retryable: Retryable.none);
    }
  }

  void _checkTile(TileIdentity tile) {
    if (tile.z > maximumZoom || tile.z < minimumZoom || !tile.isValid()) {
      log('Invalid tile coordinates: $tile');    
      throw ProviderException(
          message: 'Invalid tile coordinates $tile',
          retryable: Retryable.none,
          statusCode: 400);
    }
  }

  void dispose() async {
    Database? db = await _db;
    db?.close();
  }

  Future<void> copyTileDatabase() async {
    try {
      final dbPath =
          join(await getDatabasesPath(), Constants.tileDatabaseFileName);

      // Create the parent directory of the dbPath if it doesn't exist
      final dbDirectory = dirname(dbPath);
      final dir = Directory(dbDirectory);
      if (!dir.existsSync()) {
        await dir.create(recursive: true);
      }

      // Only copy if the database doesn't exist
      final exists = await databaseExists(dbPath);

      if (!exists) {
        // Load database from asset and copy
        ByteData data = await rootBundle
            .load(join('assets', 'databases', Constants.tileDatabaseFileName));
        List<int> bytes =
            data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);

        // Save copied asset to our path
        await File(dbPath).writeAsBytes(bytes, flush: true);
      }
    } catch (e) {
      throw Exception('Error while copying database: $e');
    }
  }
}
@dstudzinski
Copy link

Hi, I'm really curious of this use case. Do you have a full working example? Maybe some minimal repo?

I tried to use it but all I can get is "no such table: tiles" as copied database is empty (source database is ok but it's copied without tables, views etc).

When I copy it manually and run it on macos it looks like db is copied properly but i'm not sure how to provide theme/sprites needed by VectorTileLayer

@tiborrr
Copy link
Author

tiborrr commented Sep 1, 2023

This means that your mbtiles database does not adhere to the mapbox mbtiles spec as your mbtiles file should contain a tiles table or view. I used Tippecanoe to generate my mbtiles from a geojson. Perhaps you can do something similar.

@dstudzinski
Copy link

dstudzinski commented Sep 1, 2023

My mbtiles db is for sure ok (I use data from geofabric from which part is cutout with osmium and I use tilemaker to create mbtiles). I serve it with tileserver-gl (with style created with Maputnik) without problems.
I also checked that mbtiles sqlite db contains tiles view.
mbtiles file in assets is for sure ok. The problem is (I think) with db copy (which is done on load).

I was able to use my mbtiles file with https://github.com/psytwo1/vector_mbtiles plugin for flutter_map (I found it when I started dig deeper in reading mbtiles in flutter) so I think my case is closed.

@mdmm13
Copy link

mdmm13 commented Sep 4, 2023

Thank you so much for sharing, @tiborrr!

Not sure if it will become part of the core vector_map_tiles package in the end, but great building block and frequently requested: #9, #32, #85, #114, #151.

@greensopinion
Copy link
Owner

@tiborrr this looks great and very useful

VectorTileProvider is intended to be extensible to provide tiles from alternate data sources, either from within a private project or as an external library. I'd rather not take on the maintenance or dependencies of mbtiles, but it would be pretty easy to publish as another library on https://pub.dev - feel free to do that!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants