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

Added support for sideloading map tiles #73

Merged
merged 2 commits into from
Jun 9, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,28 @@ Add these lines to your Info.plist
| Line | :white_check_mark: | |
| Fill | | |

## Offline Sideloading

Support for offline maps is available by *"side loading"* the required map tiles and including them in your `assets` folder.

* Create your tiles package by following the guide available [here](https://docs.mapbox.com/ios/maps/overview/offline/).

* Place the tiles.db file generated in step one in your assets directory and add a reference to it in your `pubspec.yml` file.

```
assets:
- assets/cache.db
```

* Call `installOfflineMapTiles` when your application starts to copy your tiles into the location where Mapbox can access them. **NOTE:** This method should be called **before** the Map widget is loaded to prevent collisions when copying the files into place.

```
try {
await installOfflineMapTiles(join("assets", "cache.db"));
} catch (err) {
print(err);
}
```

## Documentation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
import android.app.Application;
import android.os.Bundle;

import io.flutter.plugin.common.PluginRegistry.Registrar;

import java.util.concurrent.atomic.AtomicInteger;

import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry.Registrar;

/**
* Plugin for controlling a set of MapboxMap views to be shown as overlays on top of the Flutter
* view. The overlay should be hidden during transformations or while Flutter is rendering on top of
Expand All @@ -35,6 +36,10 @@ public static void registerWith(Registrar registrar) {
.platformViewRegistry()
.registerViewFactory(
"plugins.flutter.io/mapbox_gl", new MapboxMapFactory(plugin.state, registrar));

MethodChannel methodChannel =
new MethodChannel(registrar.messenger(), "plugins.flutter.io/mapbox_gl");
methodChannel.setMethodCallHandler(new StaticMethodHandler(registrar));
}

@Override
Expand Down
80 changes: 80 additions & 0 deletions android/src/main/java/com/mapbox/mapboxgl/StaticMethodHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.mapbox.mapboxgl;

import android.content.Context;
import android.util.Log;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;

class StaticMethodHandler implements MethodChannel.MethodCallHandler {
ayvazj marked this conversation as resolved.
Show resolved Hide resolved
private static final String TAG = StaticMethodHandler.class.getSimpleName();
private static final String DATABASE_NAME = "mbgl-offline.db";
private static final int BUFFER_SIZE = 1024 * 2;
private final PluginRegistry.Registrar registrar;

StaticMethodHandler(PluginRegistry.Registrar registrar) {
this.registrar = registrar;
}

@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
switch (methodCall.method) {
case "installOfflineMapTiles":
String tilesDb = methodCall.argument("tilesdb");
String assetKey = registrar.lookupKeyForAsset(tilesDb);
installOfflineMapTiles(assetKey);
result.success(null);
break;
default:
result.notImplemented();
break;
}
}

private void installOfflineMapTiles(String assetKey) {
final Context context = registrar.activeContext();
try {
File dest = new File(context.getFilesDir(), DATABASE_NAME);
copy(context.getAssets().open(assetKey),
new FileOutputStream(dest));
} catch (IOException e) {
e.printStackTrace();
}
}

private static int copy(InputStream input, OutputStream output) throws IOException {
final byte[] buffer = new byte[BUFFER_SIZE];
final BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE);
final BufferedOutputStream out = new BufferedOutputStream(output, BUFFER_SIZE);
int count = 0;
int n = 0;
try {
while ((n = in.read(buffer, 0, BUFFER_SIZE)) != -1) {
out.write(buffer, 0, n);
count += n;
}
out.flush();
} finally {
try {
out.close();
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
}
try {
in.close();
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
}
}
return count;
}
}
46 changes: 46 additions & 0 deletions ios/Classes/SwiftMapboxGlFlutterPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,51 @@ public class SwiftMapboxGlFlutterPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = MapboxMapFactory(withMessenger: registrar.messenger())
registrar.register(instance, withId: "plugins.flutter.io/mapbox_gl")

let channel = FlutterMethodChannel(name: "plugins.flutter.io/mapbox_gl", binaryMessenger: registrar.messenger())

channel.setMethodCallHandler { (methodCall, result) in
switch(methodCall.method) {
case "installOfflineMapTiles":
guard let arguments = methodCall.arguments as? [String: String] else { return }
let tilesdb = arguments["tilesdb"]
let assetkey = registrar.lookupKey(forAsset: tilesdb!)
installOfflineMapTiles(key: assetkey)
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
}

private static func getTilesUrl() -> URL {
guard var cachesUrl = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first,
let bundleId = Bundle.main.object(forInfoDictionaryKey: kCFBundleIdentifierKey as String) as? String else {
fatalError("Could not get map tiles directory")
}
cachesUrl.appendPathComponent(bundleId)
cachesUrl.appendPathComponent(".mapbox")
cachesUrl.appendPathComponent("cache.db")
return cachesUrl
}

// Copies the "offline" tiles to where Mapbox expects them
private static func installOfflineMapTiles(key: String) {
var tilesUrl = getTilesUrl()
let bundlePath = Bundle.main.path(forResource: key, ofType: nil)
NSLog("Cached tiles not found, copying from bundle... \(String(describing: bundlePath)) ==> \(tilesUrl)")
do {
let parentDir = tilesUrl.deletingLastPathComponent()
try FileManager.default.createDirectory(at: parentDir, withIntermediateDirectories: true, attributes: nil)
if FileManager.default.fileExists(atPath: tilesUrl.path) {
try FileManager.default.removeItem(atPath: tilesUrl.path)
}
try FileManager.default.copyItem(atPath: bundlePath!, toPath: tilesUrl.path)
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try tilesUrl.setResourceValues(resourceValues)
} catch let error {
NSLog("Error copying bundled tiles: \(error)")
}
}
}
11 changes: 11 additions & 0 deletions lib/mapbox_gl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,14 @@ part 'src/symbol.dart';
part 'src/line.dart';
part 'src/circle.dart';
part 'src/ui.dart';

final MethodChannel _staticChannel =
MethodChannel('plugins.flutter.io/mapbox_gl');

Future<void> installOfflineMapTiles(String tilesDb) async {
await _staticChannel.invokeMethod('installOfflineMapTiles',
<String, dynamic>{
'tilesdb': tilesDb,
},
);
}
ayvazj marked this conversation as resolved.
Show resolved Hide resolved