Skip to content

Commit

Permalink
Update OfflineRegion/OfflineRegionDefinition interfaces, synchronize …
Browse files Browse the repository at this point in the history
…with iOS and Android (#545)

* Handled updated downloadOfflineRegion interface

The interface has changed to send region as a argument, as well as the
channelName. Without this change, the region download fails.

Fixes #534

* Generate offline region IDs and adapt interfaces

While the Android SDK generates a region ID in createOfflineRegion, the
iOS SDK does not have this feature. To maintain a parallel interface and
relieve the client of the need to generate IDs, this change generates an
ID before submitting the download request to the iOS SDK. As in the
Android implementation, the completed OfflineRegionData is immediately
returned through the open channel.

Fixes #533

The following changes suggested in #491 are also pursued here:

* Restructure OfflineRegionData(native)/OfflineRegion(dart) classes into
  OfflineRegionDefinition and OfflineRegion classes to mirror SDK class
  structure. This is basically equivalient to something like
  OfflineRegionOptions.
* Accept OfflineRegionDefinition as an arg to downloadOfflineRegion, and
  return OfflineRegion as a result

Without those changes, the client code is `@required` to submit an id
along with the original download request, even though the id will not be
used and will be replaced by the generated ID. Furthermore, the object
returned from `downloadOfflineRegion` is now decoded so that the client
can await it to extract the generated ID.

I also made the properties of the definition and data classes immutable
on the iOS side, and renamed values dealing with the downloaded pack
context from "metadata" to "context". This is because the context is no
longer just the user-supplied metadata; it is a structure separately
holding the metadata and generated ID.

This change is not backwards compatible with regions previously
downloaded on iOS, however, offline downloading is not functional in the
latest or any release of this library because of #534.

* OfflineRegion and OfflineRegionDefinition

To better harmonize with the Mapbox sdk data structures for offline
regions on iOS and Android, and to pave the way for polygonal offline
regions, the dart classes are revamped in 71186fb. This required changes
on the Android side which are also in this PR, and it will require changes
in any client code using the download functionality.
  • Loading branch information
n8han authored Feb 28, 2021
1 parent 7950192 commit 5df9342
Show file tree
Hide file tree
Showing 13 changed files with 425 additions and 457 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.mapbox.mapboxgl.models.OfflineRegionData;
import android.content.Context;

import androidx.annotation.NonNull;
Expand Down Expand Up @@ -74,21 +73,23 @@ public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
break;

case "downloadOfflineRegion":
// Get download region arguments from caller
OfflineRegionData regionData = new Gson().fromJson(methodCall.argument("region").toString(), OfflineRegionData.class);
// Prepare channel
// Get args from caller
Map<String, Object> definitionMap = (Map<String, Object>) methodCall.argument("definition");
Map<String, Object> metadataMap = (Map<String, Object>) methodCall.argument("metadata");
String channelName = methodCall.argument("channelName");

// Prepare args
OfflineChannelHandlerImpl channelHandler = new OfflineChannelHandlerImpl(messenger, channelName);

// Start downloading
OfflineManagerUtils.downloadRegion(result, context, regionData, channelHandler);
OfflineManagerUtils.downloadRegion(result, context, definitionMap, metadataMap, channelHandler);
break;
case "getListOfRegions":
OfflineManagerUtils.regionsList(result, context);
break;
case "updateOfflineRegionMetadata":
// Get download region arguments from caller
Map<String, Object> metadata = (Map<String, Object>) methodCall.<Map>argument("metadata");
Map<String, Object> metadata = (Map<String, Object>) methodCall.argument("metadata");
OfflineManagerUtils.updateRegionMetadata(result, context, methodCall.<Number>argument("id").longValue(), metadata);
break;
case "deleteOfflineRegion":
Expand Down
105 changes: 84 additions & 21 deletions android/src/main/java/com/mapbox/mapboxgl/OfflineManagerUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
import android.util.Log;

import com.google.gson.Gson;
import com.mapbox.mapboxgl.models.OfflineRegionData;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.offline.OfflineManager;
import com.mapbox.mapboxsdk.offline.OfflineRegion;
import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition;
import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition;
import com.mapbox.mapboxsdk.offline.OfflineRegionError;
import com.mapbox.mapboxsdk.offline.OfflineRegionStatus;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand All @@ -27,9 +29,9 @@ static void mergeRegions(MethodChannel.Result result, Context context, String pa
OfflineManager.getInstance(context).mergeOfflineRegions(path, new OfflineManager.MergeOfflineRegionsCallback() {
public void onMerge(OfflineRegion[] offlineRegions) {
if (result == null) return;
List<OfflineRegionData> regionsArgs = new ArrayList<>();
List<Map<String, Object>> regionsArgs = new ArrayList<>();
for (OfflineRegion offlineRegion : offlineRegions) {
regionsArgs.add(OfflineRegionData.fromOfflineRegion(offlineRegion));
regionsArgs.add(offlineRegionToMap(offlineRegion));
}
String json = new Gson().toJson(regionsArgs);
result.success(json);
Expand All @@ -50,24 +52,25 @@ static void setOfflineTileCountLimit(MethodChannel.Result result, Context contex
static void downloadRegion(
MethodChannel.Result result,
Context context,
OfflineRegionData offlineRegionData,
Map<String, Object> definitionMap,
Map<String, Object> metadataMap,
OfflineChannelHandlerImpl channelHandler
) {
// Define the offline region
float pixelDensity = context.getResources().getDisplayMetrics().density;
OfflineRegionDefinition definition = offlineRegionData.generateRegionDefinition(pixelDensity);
//Prepare metadata
byte[] metadata = offlineRegionData.prepareMetadata();
//Tracker of result
OfflineRegionDefinition definition = mapToRegionDefinition(definitionMap, pixelDensity);
String metadata = "{}";
if (metadataMap != null) {
metadata = new Gson().toJson(metadataMap);
}
AtomicBoolean isComplete = new AtomicBoolean(false);
//Download region
OfflineManager.getInstance(context).createOfflineRegion(definition, metadata, new OfflineManager.CreateOfflineRegionCallback() {
OfflineManager.getInstance(context).createOfflineRegion(definition, metadata.getBytes(), new OfflineManager.CreateOfflineRegionCallback() {
private OfflineRegion _offlineRegion;

@Override
public void onCreate(OfflineRegion offlineRegion) {
OfflineRegionData data = OfflineRegionData.fromOfflineRegion(offlineRegion);
result.success(new Gson().toJson(data));
Map<String, Object> regionData = offlineRegionToMap(offlineRegion);
result.success(new Gson().toJson(regionData));

_offlineRegion = offlineRegion;
//Start downloading region
Expand Down Expand Up @@ -112,7 +115,7 @@ public void mapboxTileCountLimitExceeded(long limit) {
isComplete.set(true);
channelHandler.onError("mapboxTileCountLimitExceeded", "Mapbox tile count limit exceeded: " + limit, null);
//Mapbox even after crash and not downloading fully region still keeps part of it in database, so we have to remove it
deleteRegion(null, context, offlineRegionData.getId());
deleteRegion(null, context, _offlineRegion.getID());
}
};
_offlineRegion.setObserver(observer);
Expand All @@ -137,9 +140,9 @@ static void regionsList(MethodChannel.Result result, Context context) {
OfflineManager.getInstance(context).listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
@Override
public void onList(OfflineRegion[] offlineRegions) {
List<OfflineRegionData> regionsArgs = new ArrayList<>();
List<Map<String, Object>> regionsArgs = new ArrayList<>();
for (OfflineRegion offlineRegion : offlineRegions) {
regionsArgs.add(OfflineRegionData.fromOfflineRegion(offlineRegion));
regionsArgs.add(offlineRegionToMap(offlineRegion));
}
result.success(new Gson().toJson(regionsArgs));
}
Expand All @@ -151,20 +154,23 @@ public void onError(String error) {
});
}

static void updateRegionMetadata(MethodChannel.Result result, Context context, long id, Map<String, Object> metadata) {
static void updateRegionMetadata(MethodChannel.Result result, Context context, long id, Map<String, Object> metadataMap) {
OfflineManager.getInstance(context).listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
@Override
public void onList(OfflineRegion[] offlineRegions) {
for (OfflineRegion offlineRegion : offlineRegions) {
if (offlineRegion.getID() != id) continue;

final OfflineRegionData regionData = OfflineRegionData.fromOfflineRegion(offlineRegion);
regionData.setMetadata(metadata);

offlineRegion.updateMetadata(regionData.prepareMetadata(), new OfflineRegion.OfflineRegionUpdateMetadataCallback() {
String metadata = "{}";
if (metadataMap != null) {
metadata = new Gson().toJson(metadataMap);
}
offlineRegion.updateMetadata(metadata.getBytes(), new OfflineRegion.OfflineRegionUpdateMetadataCallback() {
@Override
public void onUpdate(byte[] metadataBytes) {
regionData.setMetadataBytes(metadataBytes);
Map<String, Object> regionData = offlineRegionToMap(offlineRegion);
regionData.put("metadata", metadataBytesToMap(metadataBytes));

if (result == null) return;
result.success(new Gson().toJson(regionData));
}
Expand Down Expand Up @@ -228,4 +234,61 @@ private static double calculateDownloadingProgress(long requiredResourceCount, l
? (100.0 * completedResourceCount / requiredResourceCount) :
0.0;
}

private static OfflineRegionDefinition mapToRegionDefinition(Map<String, Object> map, float pixelDensity) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
Log.d(TAG, entry.getKey());
Log.d(TAG, entry.getValue().toString());
}
// Create a bounding box for the offline region
return new OfflineTilePyramidRegionDefinition(
(String) map.get("mapStyleUrl"),
listToBounds((List<List<Double>>) map.get("bounds")),
((Number) map.get("minZoom")).doubleValue(),
((Number) map.get("maxZoom")).doubleValue(),
pixelDensity,
(Boolean) map.get("includeIdeographs")
);
}

private static LatLngBounds listToBounds(List<List<Double>> bounds) {
return new LatLngBounds.Builder()
.include(new LatLng(bounds.get(1).get(0), bounds.get(1).get(1))) //Northeast
.include(new LatLng(bounds.get(0).get(0), bounds.get(0).get(1))) //Southwest
.build();
}

private static Map<String, Object> offlineRegionToMap(OfflineRegion region) {
Map<String, Object> result = new HashMap();
result.put("id", region.getID());
result.put("definition", offlineRegionDefinitionToMap(region.getDefinition()));
result.put("metadata", metadataBytesToMap(region.getMetadata()));
return result;
}

private static Map<String, Object> offlineRegionDefinitionToMap(OfflineRegionDefinition definition) {
Map<String, Object> result = new HashMap();
result.put("mapStyleUrl", definition.getStyleURL());
result.put("bounds", boundsToList(definition.getBounds()));
result.put("minZoom", definition.getMinZoom());
result.put("maxZoom", definition.getMaxZoom());
result.put("includeIdeographs", definition.getIncludeIdeographs());
return result;
}

private static List<List<Double>> boundsToList(LatLngBounds bounds) {
List<List<Double>> boundsList = new ArrayList<>();
List<Double> northeast = Arrays.asList(bounds.getLatNorth(), bounds.getLonEast());
List<Double> southwest = Arrays.asList(bounds.getLatSouth(), bounds.getLonWest());
boundsList.add(southwest);
boundsList.add(northeast);
return boundsList;
}

private static Map<String, Object> metadataBytesToMap(byte[] metadataBytes) {
if (metadataBytes != null) {
return new Gson().fromJson(new String(metadataBytes), HashMap.class);
}
return new HashMap();
}
}

This file was deleted.

12 changes: 6 additions & 6 deletions example/lib/offline_region_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,22 @@ class _OfflineRegionMapState extends State<OfflineRegionMap> {
body: MapboxMap(
initialCameraPosition: CameraPosition(
target: _center,
zoom: widget.item.offlineRegion.minZoom,
zoom: widget.item.offlineRegionDefinition.minZoom,
),
minMaxZoomPreference: MinMaxZoomPreference(
widget.item.offlineRegion.minZoom,
widget.item.offlineRegion.maxZoom,
widget.item.offlineRegionDefinition.minZoom,
widget.item.offlineRegionDefinition.maxZoom,
),
styleString: widget.item.offlineRegion.mapStyleUrl,
styleString: widget.item.offlineRegionDefinition.mapStyleUrl,
cameraTargetBounds: CameraTargetBounds(
widget.item.offlineRegion.bounds,
widget.item.offlineRegionDefinition.bounds,
),
),
);
}

LatLng get _center {
final bounds = widget.item.offlineRegion.bounds;
final bounds = widget.item.offlineRegionDefinition.bounds;
final lat = (bounds.southwest.latitude + bounds.northeast.latitude) / 2;
final lng = (bounds.southwest.longitude + bounds.northeast.longitude) / 2;
return LatLng(lat, lng);
Expand Down
Loading

0 comments on commit 5df9342

Please sign in to comment.