@rnmapbox/maps 10.0.0-rc.5 → 10.0.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +1 -1
  2. package/android/rctmgl/build.gradle +1 -1
  3. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/camera/RCTMGLCamera.kt +2 -2
  4. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/location/LocationComponentManager.kt +3 -2
  5. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.kt +59 -17
  6. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapViewManager.kt +8 -4
  7. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLModule.kt +4 -3
  8. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLOfflineModule.kt +474 -405
  9. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/extensions/FeatureCollection.kt +10 -0
  10. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/extensions/Geometry.kt +22 -0
  11. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/extensions/JSONObject.kt +78 -0
  12. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/extensions/ReadableArray.kt +1 -1
  13. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/extensions/Value.kt +9 -0
  14. package/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/writeableMapArrayOf.kt +41 -0
  15. package/ios/RCTMGL-v10/RCTMGLOfflineModule.swift +283 -307
  16. package/lib/commonjs/components/MapView.js +9 -0
  17. package/lib/commonjs/components/MapView.js.map +1 -1
  18. package/lib/commonjs/components/Terrain.js +1 -1
  19. package/lib/commonjs/components/VectorSource.js +2 -0
  20. package/lib/commonjs/components/VectorSource.js.map +1 -1
  21. package/lib/commonjs/modules/location/locationManager.js +4 -0
  22. package/lib/commonjs/modules/location/locationManager.js.map +1 -1
  23. package/lib/module/components/MapView.js +9 -0
  24. package/lib/module/components/MapView.js.map +1 -1
  25. package/lib/module/components/Terrain.js +1 -1
  26. package/lib/module/components/VectorSource.js +3 -0
  27. package/lib/module/components/VectorSource.js.map +1 -1
  28. package/lib/module/modules/location/locationManager.js +4 -0
  29. package/lib/module/modules/location/locationManager.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/components/Terrain.tsx +1 -1
@@ -1,49 +1,92 @@
1
1
  package com.mapbox.rctmgl.modules
2
2
 
3
+ import android.R.array
4
+ import android.os.Build
3
5
  import android.util.Log
4
6
  import com.facebook.react.bridge.*
5
7
  import com.facebook.react.module.annotations.ReactModule
6
- import com.mapbox.rctmgl.modules.RCTMGLOfflineModule
7
- import com.mapbox.rctmgl.modules.TileRegionPack
8
- import com.mapbox.maps.OfflineManager
9
- import com.mapbox.rctmgl.utils.LatLngBounds
10
- import com.mapbox.maps.TilesetDescriptorOptions
11
- import com.mapbox.rctmgl.utils.GeoJSONUtils
12
- import com.mapbox.bindgen.Expected
13
- import com.mapbox.geojson.Geometry
14
- import com.mapbox.rctmgl.events.IEvent
15
8
  import com.facebook.react.modules.core.RCTNativeAppEventEmitter
9
+ import com.mapbox.bindgen.Expected
16
10
  import com.mapbox.bindgen.Value
17
- import com.mapbox.rctmgl.events.OfflineEvent
18
11
  import com.mapbox.common.*
12
+ import com.mapbox.geojson.*
13
+ import com.mapbox.maps.*
14
+ import com.mapbox.rctmgl.events.IEvent
15
+ import com.mapbox.rctmgl.events.OfflineEvent
19
16
  import com.mapbox.rctmgl.events.constants.EventTypes
20
- import com.mapbox.geojson.FeatureCollection
21
- import com.mapbox.maps.OfflineRegionManager
22
- import com.mapbox.turf.TurfMeasurement
23
- import com.mapbox.maps.ResourceOptions
24
- import com.mapbox.rctmgl.modules.RCTMGLModule
25
- import com.mapbox.rctmgl.utils.ConvertUtils
17
+ import com.mapbox.rctmgl.utils.*
26
18
  import com.mapbox.rctmgl.utils.Logger
19
+ import com.mapbox.rctmgl.utils.extensions.*
20
+ import com.mapbox.turf.TurfMeasurement
21
+ import org.json.JSONArray
27
22
  import org.json.JSONException
28
23
  import org.json.JSONObject
29
24
  import java.io.File
30
25
  import java.io.UnsupportedEncodingException
31
- import java.lang.Error
32
26
  import java.nio.file.Files
33
27
  import java.nio.file.Paths
34
28
  import java.nio.file.StandardCopyOption
35
- import java.util.ArrayList
36
- import java.util.HashMap
37
29
  import java.util.concurrent.CountDownLatch
38
30
 
39
- class TileRegionPack(var name: String, var progress: TileRegionLoadProgress?, var state: String) {
31
+ data class ZoomRange(val minZoom: Byte, val maxZoom: Byte) {
32
+
33
+ }
34
+
35
+ const val RNMapboxInfoMetadataKey = "_rnmapbox"
36
+
37
+ enum class TileRegionPackState(val rawValue: String) {
38
+ INVALID("invalid"),
39
+ INACTIVE("inactive"),
40
+ ACTIVE("active"),
41
+ COMPLETE("complete"),
42
+ UNKNOWN("unkown")
43
+ }
44
+ class TileRegionPack(var name: String, var progress: TileRegionLoadProgress?, var state: TileRegionPackState, var metadata: JSONObject) {
40
45
  var cancelable: Cancelable? = null
41
46
  var loadOptions: TileRegionLoadOptions? = null
42
47
 
43
- companion object {
44
- val ACTIVE = "active"
45
- val INACTIVE = "inactive"
46
- val COMPLETE = "complete"
48
+ // stored in metadata for resume functionality
49
+ var styleURI: String? = null
50
+ var bounds: Geometry? = null
51
+ var zoomRange: ZoomRange? = null
52
+
53
+ init {
54
+ val rnMetadata = metadata.optJSONObject(RNMapboxInfoMetadataKey)
55
+ if (rnMetadata != null) {
56
+ val styleURI = rnMetadata.optString("styleURI")
57
+ if (styleURI != null) {
58
+ this.styleURI = styleURI
59
+ }
60
+
61
+ val bounds = rnMetadata.optJSONObject("bounds")
62
+ if (bounds != null) {
63
+ this.bounds = bounds.toGeometry()
64
+ }
65
+
66
+ val zoomRange = rnMetadata.optJSONArray("zoomRange")
67
+ if (zoomRange != null) {
68
+ this.zoomRange =
69
+ ZoomRange(zoomRange.getInt(0).toByte(), zoomRange.getInt(1).toByte())
70
+ }
71
+ }
72
+ }
73
+
74
+ public constructor(
75
+ name: String,
76
+ state: TileRegionPackState = TileRegionPackState.UNKNOWN,
77
+ styleURI: String,
78
+ bounds: Geometry,
79
+ zoomRange: ZoomRange,
80
+ metadata: JSONObject
81
+ ) : this(name, null, state, metadata) {
82
+ val rnmeta = JSONObject()
83
+ rnmeta.put("styleURI", styleURI)
84
+ this.styleURI = styleURI
85
+ rnmeta.put("bounds", bounds.toJSONObject())
86
+ this.bounds = bounds
87
+ rnmeta.put("zoomRange", JSONArray(arrayOf(zoomRange.minZoom, zoomRange.maxZoom)))
88
+ this.zoomRange = zoomRange
89
+ this.metadata.put(RNMapboxInfoMetadataKey, rnmeta);
47
90
  }
48
91
  }
49
92
 
@@ -54,6 +97,21 @@ class RCTMGLOfflineModule(private val mReactContext: ReactApplicationContext) :
54
97
  ) {
55
98
  var tileRegionPacks = HashMap<String, TileRegionPack>()
56
99
  private var mProgressEventThrottle = 300.0
100
+
101
+
102
+ val tileStore: TileStore by lazy {
103
+ TileStore.create()
104
+ }
105
+
106
+ val offlineManager: OfflineManager by lazy {
107
+ OfflineManager(
108
+ ResourceOptions.Builder()
109
+ .accessToken(RCTMGLModule.getAccessToken(mReactContext)).tileStore(
110
+ tileStore
111
+ ).build()
112
+ )
113
+ }
114
+
57
115
  override fun getName(): String {
58
116
  return REACT_CLASS
59
117
  }
@@ -68,52 +126,401 @@ class RCTMGLOfflineModule(private val mReactContext: ReactApplicationContext) :
68
126
  // Remove upstream listeners, stop unnecessary background tasks
69
127
  }
70
128
 
129
+ // region React methods
71
130
  @ReactMethod
72
131
  @Throws(JSONException::class)
73
132
  fun createPack(options: ReadableMap, promise: Promise) {
74
- val name = ConvertUtils.getString("name", options, "")
75
- val offlineManager = getOfflineManager(mReactContext)
76
- val latLngBounds = getBoundsFromOptions(options)
77
- val descriptorOptions = TilesetDescriptorOptions.Builder().styleURI(
78
- (options.getString("styleURL"))!!
79
- ).minZoom(options.getInt("minZoom").toByte()).maxZoom(options.getInt("maxZoom").toByte())
133
+ try {
134
+ val metadataStr = options.getString("metadata")!!
135
+ val metadata = JSONObject(metadataStr)
136
+ val metadataValue = Value.valueOf(metadataStr)
137
+ val id = metadata.getString("name")!!
138
+
139
+ val boundsStr = options.getString("bounds")!!
140
+ val boundsFC = FeatureCollection.fromJson(boundsStr)
141
+ val bounds = convertPointPairToBounds(boundsFC)
142
+
143
+ val actPack = TileRegionPack(
144
+ name = id,
145
+ styleURI = options.getString("styleURL")!!,
146
+ bounds = bounds,
147
+ zoomRange = ZoomRange(
148
+ minZoom = options.getInt("minZoom").toByte(),
149
+ maxZoom = options.getInt("maxZoom").toByte()
150
+ ),
151
+ metadata = metadata
152
+ )
153
+ tileRegionPacks[id] = actPack
154
+ startLoading(pack = actPack)
155
+ promise.resolve(
156
+ writableMapOf("bounds" to boundsStr, "metadata" to metadataStr)
157
+ )
158
+ } catch (e: Throwable) {
159
+ promise.reject("createPack", e)
160
+ }
161
+ }
162
+
163
+ @ReactMethod
164
+ fun getPackStatus(name: String, promise: Promise) {
165
+ val pack = tileRegionPacks[name]
166
+ if (pack != null) {
167
+ promise.resolve(makeRegionStatus(name, pack.progress, pack))
168
+ } else {
169
+ promise.reject(Error("Pack not found"))
170
+ Logger.w(REACT_CLASS, "getPackStatus - Unknown offline region")
171
+ }
172
+ }
173
+
174
+ @ReactMethod
175
+ fun resumePackDownload(name: String, promise: Promise) {
176
+ val pack = tileRegionPacks[name]
177
+ if (pack != null) {
178
+ startLoading(pack)
179
+ promise.resolve(null)
180
+ } else {
181
+ promise.reject("resumePackDownload", "Unknown offline pack: $name")
182
+ }
183
+ }
184
+
185
+ @ReactMethod
186
+ fun pausePackDownload(name: String, promise: Promise) {
187
+ val pack = tileRegionPacks[name]
188
+ if (pack != null) {
189
+ if (pack.cancelable != null) {
190
+ pack.cancelable?.cancel()
191
+ pack.cancelable = null
192
+ promise.resolve(null)
193
+ } else {
194
+ promise.reject("resumeRegionDownload", "Offline pack: $name already cancelled")
195
+ }
196
+ } else {
197
+ promise.reject("resumeRegionDownload", "Unknown offline region")
198
+ }
199
+ }
200
+
201
+ @ReactMethod
202
+ fun setTileCountLimit(tileCountLimit: Int) {
203
+ val offlineRegionManager = OfflineRegionManager(ResourceOptions.Builder().accessToken(RCTMGLModule.getAccessToken(mReactContext)).build())
204
+ offlineRegionManager.setOfflineMapboxTileCountLimit(tileCountLimit.toLong())
205
+ }
206
+
207
+ @ReactMethod
208
+ fun deletePack(name: String, promise: Promise) {
209
+ val pack = tileRegionPacks[name]
210
+
211
+ if (pack == null) {
212
+ promise.resolve(null)
213
+ return
214
+ }
215
+
216
+ if (pack.state == TileRegionPackState.INVALID) {
217
+ promise.reject("deletePack", "Pack: $name has already been deleted")
218
+ return
219
+ }
220
+
221
+ tileStore.removeTileRegion(name, object: TileRegionCallback {
222
+ override fun run(expected: Expected<TileRegionError, TileRegion>) {
223
+ expected.value.also {
224
+ tileRegionPacks[name]!!.state = TileRegionPackState.INVALID
225
+ promise.resolve(null);
226
+ } ?: run {
227
+ promise.reject("deletePack", expected.error?.message ?: "n/a")
228
+ }
229
+ }
230
+ })
231
+ }
232
+
233
+ @ReactMethod
234
+ fun getPacks(promise: Promise) {
235
+ tileStore.getAllTileRegions(object : TileRegionsCallback {
236
+ override fun run(expected: Expected<TileRegionError, List<TileRegion>>) {
237
+ UiThreadUtil.runOnUiThread(object : Runnable {
238
+ override fun run() {
239
+ expected.value?.also { regions ->
240
+ convertRegionsToJSON(regions, promise)
241
+ } ?: run {
242
+ promise.reject("getPacks", expected.error!!.message)
243
+ }
244
+ }
245
+ })
246
+ }
247
+ })
248
+ }
249
+
250
+ // endregion
251
+
252
+ fun startLoading(pack: TileRegionPack) {
253
+ val id = pack.name
254
+ val bounds = pack.bounds
255
+ if (bounds == null) {
256
+ throw IllegalArgumentException("startLoading failed as there are no bounds in pack")
257
+ }
258
+ val zoomRange = pack.zoomRange
259
+ if (zoomRange == null) {
260
+ throw IllegalArgumentException("startLoading failed as there is no zoomRange in pack")
261
+ }
262
+ val styleURI = pack.styleURI
263
+ if (styleURI == null) {
264
+ throw IllegalArgumentException("startLoading failed as there is no styleURI in pack")
265
+ }
266
+ val metadata = pack.metadata
267
+ if (metadata == null) {
268
+ throw IllegalArgumentException("startLoading failed as there is no metadata in pack")
269
+ }
270
+ val stylePackOptions = StylePackLoadOptions.Builder()
271
+ .glyphsRasterizationMode(GlyphsRasterizationMode.IDEOGRAPHS_RASTERIZED_LOCALLY)
272
+ .metadata(metadata.toMapboxValue())
273
+ .build()
274
+
275
+ val descriptorOptions = TilesetDescriptorOptions.Builder()
276
+ .styleURI(styleURI)
277
+ .minZoom(zoomRange.minZoom)
278
+ .maxZoom(zoomRange.maxZoom)
279
+ .stylePackOptions(stylePackOptions)
80
280
  .build()
81
281
  val tilesetDescriptor = offlineManager.createTilesetDescriptor(descriptorOptions)
82
- val descriptors = ArrayList<TilesetDescriptor>()
83
- descriptors.add(tilesetDescriptor)
282
+
84
283
  val loadOptions = TileRegionLoadOptions.Builder()
85
- .geometry(GeoJSONUtils.fromLatLngBoundsToPolygon(latLngBounds))
86
- .descriptors(descriptors)
87
- .metadata(Value.valueOf((options.getString("metadata"))!!))
284
+ .geometry(bounds)
285
+ .descriptors(arrayListOf(tilesetDescriptor))
286
+ .metadata(metadata.toMapboxValue())
88
287
  .acceptExpired(true)
89
288
  .networkRestriction(NetworkRestriction.NONE)
289
+ .averageBytesPerSecond(null)
90
290
  .build()
91
- val metadataStr = options.getString("metadata")
92
- val metadata = JSONObject(metadataStr)
93
- val id = metadata.getString("name")
94
- val pack = TileRegionPack(id, null, TileRegionPack.INACTIVE)
95
- pack.loadOptions = loadOptions
96
- tileRegionPacks[id] = pack
97
- promise.resolve(fromOfflineRegion(latLngBounds, metadataStr))
98
- startPackDownload(pack)
291
+
292
+ var lastProgress: TileRegionLoadProgress? = null
293
+ val task = this.tileStore.loadTileRegion(
294
+ id, loadOptions,
295
+ { progress ->
296
+ lastProgress = progress
297
+ tileRegionPacks[id]!!.progress = progress
298
+ tileRegionPacks[id]!!.state = TileRegionPackState.ACTIVE
299
+
300
+ offlinePackProgressDidChange(progress, metadata, TileRegionPackState.ACTIVE)
301
+ }, { expected ->
302
+ expected.value?.also {
303
+ val progress = lastProgress
304
+ if (progress != null) {
305
+ offlinePackProgressDidChange(progress, metadata, TileRegionPackState.COMPLETE)
306
+ } else {
307
+ Logger.w(LOG_TAG, "startLoading: tile region completed, but got no progress information")
308
+ }
309
+ tileRegionPacks[id]!!.state = TileRegionPackState.COMPLETE
310
+ } ?: run {
311
+ val error = expected.error ?: TileRegionError(TileRegionErrorType.OTHER, "$LOG_TAG neither value nor error in expected")
312
+
313
+ tileRegionPacks[id]!!.state = TileRegionPackState.INACTIVE
314
+ offlinePackDidReceiveError(name=id, error=error)
315
+ }
316
+ },
317
+ )
318
+ tileRegionPacks[id]!!.cancelable = task
319
+ }
320
+
321
+ private fun convertRegionsToJSON(tileRegions: List<TileRegion>, promise: Promise) {
322
+ val countDownLatch = CountDownLatch(tileRegions.size * 2)
323
+ val foo = mutableMapOf<String,Pair<Expected<TileRegionError,Geometry>, TileRegion>>()
324
+ val geometryResults = mutableMapOf<String, Pair<Expected<TileRegionError, Geometry>,TileRegion>>();
325
+ val metadataResults = mutableMapOf<String, Expected<TileRegionError, Value>>()
326
+ val errors = ArrayList<TileRegionError?>()
327
+ try {
328
+ for (region: TileRegion in tileRegions) {
329
+ tileStore.getTileRegionGeometry(region.id
330
+ ) { result ->
331
+ geometryResults[region.id] = Pair(result, region)
332
+ countDownLatch.countDown()
333
+ }
334
+ tileStore.getTileRegionMetadata(region.id) { result ->
335
+ metadataResults[region.id] = result
336
+ countDownLatch.countDown()
337
+ }
338
+ }
339
+ } catch (error: Error) {
340
+ Logger.e(LOG_TAG, "convertRegionsToJSON. failed to iterate regions")
341
+ }
342
+
343
+ try {
344
+ countDownLatch.await()
345
+
346
+ val firstError = geometryResults.firstNotNullOfOrNull { (id,pair) -> pair.first.error }
347
+ if (firstError != null) {
348
+ promise.reject("convertRegionsToJSON", firstError.message)
349
+ return
350
+ }
351
+
352
+ val results = geometryResults.map { (id, pair) ->
353
+ val (expected, region) = pair
354
+ val geometry = expected.value!!
355
+ return@map Pair(id, Triple(geometry, region, metadataResults[id]?.value))
356
+ }
357
+
358
+ promise.resolve(
359
+ writableArrayOf(
360
+ *results.map { (id,geometry_region_metadata) ->
361
+ val (geometry, region, metadata) = geometry_region_metadata
362
+ val metadataJSON = metadata?.toJSONObject()
363
+ val ret = convertRegionToJSON(region, geometry, metadataJSON)
364
+ val pack = tileRegionPacks[region.id] ?: TileRegionPack(
365
+ name= region.id,
366
+ state= TileRegionPackState.UNKNOWN,
367
+ progress= toProgress(region),
368
+ metadata= metadataJSON ?: JSONObject()
369
+ )
370
+
371
+ if (region.completedResourceCount == region.requiredResourceCount) {
372
+ pack.state = TileRegionPackState.COMPLETE
373
+ }
374
+ tileRegionPacks[region.id] = pack
375
+ return@map ret
376
+ }.toTypedArray())
377
+ )
378
+ } catch (interruptedException: InterruptedException) {
379
+ promise.reject(interruptedException)
380
+ }
381
+ }
382
+
383
+ private fun convertRegionToJSON(region: TileRegion, geometry: Geometry, metadata: JSONObject?): ReadableMap {
384
+ val bb = geometry.calculateBoundingBox()
385
+
386
+ val jsonBounds = writableArrayOf(
387
+ bb.northeast().longitude(),
388
+ bb.northeast().latitude(),
389
+ bb.southwest().longitude(),
390
+ bb.southwest().latitude()
391
+ )
392
+ val completed = (region.completedResourceCount == region.requiredResourceCount)
393
+
394
+ val metadataOrEmpty = metadata ?: JSONObject()
395
+ val metadataWithName = metadataOrEmpty.put("name", region.id)
396
+
397
+ var result = writableMapOf(
398
+ "requiredResourceCount" to region.requiredResourceCount,
399
+ "completedResourceCount" to region.completedResourceCount,
400
+ "completedResourceSize" to region.completedResourceSize,
401
+ "state" to (if (completed) TileRegionPackState.COMPLETE.rawValue else TileRegionPackState.UNKNOWN ),
402
+ "metadata" to metadataWithName.toString(),
403
+ "bounds" to jsonBounds,
404
+ );
405
+
406
+ if (region.requiredResourceCount > 0) {
407
+ val percentage = 100.0 * region.completedResourceCount.toDouble() / region.requiredResourceCount.toDouble()
408
+ result.putDouble("percentage", percentage)
409
+ } else {
410
+ result.putNull("percentage")
411
+ }
412
+
413
+ val expires = region.expires
414
+ if (expires != null) {
415
+ result.putString("expires", expires.toString())
416
+ }
417
+
418
+ return result
419
+ }
420
+
421
+ private fun toProgress(region: TileRegion): TileRegionLoadProgress {
422
+ return TileRegionLoadProgress(
423
+ region.completedResourceCount,
424
+ region.completedResourceSize,
425
+ 0,
426
+ region.requiredResourceCount,
427
+ 0,
428
+ 0
429
+ )
430
+ }
431
+
432
+ private fun _makeRegionStatusPayload(name:String, progress: TileRegionLoadProgress?,state: TileRegionPackState, metadata: JSONObject?) : WritableMap {
433
+ var result = Arguments.createMap()
434
+ if (progress != null) {
435
+ val progressPercentage = progress.completedResourceCount.toDouble() / progress.requiredResourceCount.toDouble()
436
+ result = writableMapOf(
437
+ "state" to (if (progress.completedResourceCount == progress.requiredResourceCount) TileRegionPackState.COMPLETE.rawValue else state.rawValue),
438
+ "name" to name,
439
+ "perentage" to progressPercentage * 100.0,
440
+ "completedResourceCount" to progress.completedResourceCount,
441
+ "completedResourceSize" to progress.completedResourceSize,
442
+ "erroredResourceCount" to progress.erroredResourceCount,
443
+ "loadedResourceSize" to progress.loadedResourceSize,
444
+ "loadedResourceCount" to progress.loadedResourceCount,
445
+ "requiredResourceCount" to progress.requiredResourceCount
446
+ )
447
+ } else {
448
+ result = writableMapOf(
449
+ "state" to state.rawValue,
450
+ "name" to name,
451
+ "perentage" to null,
452
+ )
453
+ }
454
+ if (metadata != null) {
455
+ result.putMap("metadata", metadata.toReadableMap())
456
+ }
457
+ return result
458
+ }
459
+ private fun makeProgresEvent(name: String, progress: TileRegionLoadProgress, state: TileRegionPackState): OfflineEvent {
460
+ return OfflineEvent(
461
+ OFFLINE_PROGRESS,
462
+ EventTypes.OFFLINE_STATUS,
463
+ _makeRegionStatusPayload(name, progress, state, null)
464
+ )
465
+ }
466
+ private fun offlinePackProgressDidChange(progress: TileRegionLoadProgress, metadata: JSONObject, state: TileRegionPackState) {
467
+ // TODO throttle
468
+ sendEvent(this.makeProgresEvent(metadata.getString("name"), progress, state))
469
+ }
470
+
471
+ private fun offlinePackDidReceiveError(name: String, error: TileRegionError) {
472
+ val event = OfflineEvent(
473
+ OFFLINE_ERROR,
474
+ EventTypes.OFFLINE_ERROR,
475
+ writableMapOf(
476
+ "name" to name,
477
+ "message" to error.message
478
+ )
479
+ )
480
+ sendEvent(event)
481
+ }
482
+
483
+ private fun convertPointPairToBounds(boundsFC: FeatureCollection): Geometry {
484
+ val geometryCollection = boundsFC.toGeometryCollection()
485
+ val geometries = geometryCollection.geometries()
486
+ if (geometries.size != 2) {
487
+ return geometryCollection
488
+ }
489
+ val g0 = geometries.get(0) as Point?
490
+ val g1 = geometries.get(1) as Point?
491
+ if (g0 == null || g1 == null) {
492
+ return geometryCollection
493
+ }
494
+ val pt0 = g0
495
+ val pt1 = g1
496
+ return Polygon.fromLngLats(
497
+ listOf(
498
+ listOf(
499
+ pt0,
500
+ Point.fromLngLat(pt1.longitude(), pt0.latitude()),
501
+ pt1,
502
+ Point.fromLngLat(pt0.longitude(), pt1.latitude()),
503
+ pt0
504
+ ))
505
+ )
99
506
  }
100
507
 
101
508
  fun startPackDownload(pack: TileRegionPack) {
102
509
  val _this = this
103
- pack.cancelable = getTileStore()
510
+ pack.cancelable = tileStore
104
511
  .loadTileRegion(
105
512
  pack.name,
106
513
  (pack.loadOptions)!!,
107
514
  TileRegionLoadProgressCallback { progress ->
108
515
  pack.progress = progress
109
- pack.state = TileRegionPack.ACTIVE
516
+ pack.state = TileRegionPackState.ACTIVE
110
517
  _this.sendEvent(_this.makeStatusEvent(pack.name, progress, pack))
111
518
  },
112
519
  object : TileRegionCallback {
113
520
  override fun run(region: Expected<TileRegionError, TileRegion>) {
114
521
  pack.cancelable = null
115
522
  if (region.isError) {
116
- pack.state = TileRegionPack.INACTIVE
523
+ pack.state = TileRegionPackState.INACTIVE
117
524
  _this.sendEvent(
118
525
  _this.makeErrorEvent(
119
526
  pack.name, "TileRegionError", region.error!!
@@ -121,82 +528,21 @@ class RCTMGLOfflineModule(private val mReactContext: ReactApplicationContext) :
121
528
  )
122
529
  )
123
530
  } else {
124
- pack.state = TileRegionPack.COMPLETE
531
+ pack.state = TileRegionPackState.COMPLETE
125
532
  _this.sendEvent(_this.makeStatusEvent(pack.name, pack.progress, pack))
126
533
  }
127
534
  }
128
535
  })
129
536
  }
130
537
 
131
- @ReactMethod
132
- fun getPacks(promise: Promise) {
133
- getTileStore().getAllTileRegions(object : TileRegionsCallback {
134
- override fun run(regions: Expected<TileRegionError, List<TileRegion>>) {
135
- UiThreadUtil.runOnUiThread(object : Runnable {
136
- override fun run() {
137
- if (regions.isValue) {
138
- convertRegionsToJSON((regions.value)!!, promise)
139
- } else {
140
- promise.reject("getPacks", regions.error!!.message)
141
- }
142
- }
143
- })
144
- }
145
- })
146
- }
147
-
148
- private fun convertRegionsToJSON(tileRegions: List<TileRegion>, promise: Promise) {
149
- val countDownLatch = CountDownLatch(tileRegions.size)
150
- val errors = ArrayList<TileRegionError?>()
151
- val geometries = ArrayList<Geometry>()
152
- try {
153
- for (region: TileRegion in tileRegions) {
154
- getTileStore()
155
- .getTileRegionGeometry(region.id, object : TileRegionGeometryCallback {
156
- override fun run(result: Expected<TileRegionError, Geometry>) {
157
- if (result.isValue) {
158
- geometries.add(result.value!!)
159
- } else {
160
- errors.add(result.error)
161
- }
162
- countDownLatch.countDown()
163
- }
164
- })
165
- }
166
- } catch (error: Error) {
167
- Logger.e("OS", "a")
168
- }
169
- try {
170
- countDownLatch.await()
171
- val result = Arguments.createArray()
172
- for (geometry: Geometry in geometries) {
173
- result.pushMap(fromOfflineRegion(geometry))
174
- }
175
- for (error: TileRegionError? in errors) {
176
- val errorMap = Arguments.createMap()
177
- errorMap.putString("type", "error")
178
- errorMap.putString("message", error!!.message)
179
- errorMap.putString("errorType", error.type.toString())
180
- result.pushMap(errorMap)
181
- }
182
- promise.resolve(
183
- result
184
- )
185
- } catch (interruptedException: InterruptedException) {
186
- promise.reject(interruptedException)
187
- }
188
- }
189
-
190
538
  @ReactMethod
191
539
  fun resetDatabase(promise: Promise) {
192
- val tileStore = getTileStore()
193
540
  tileStore.getAllTileRegions { expected ->
194
541
  expected.value?.also { tileRegions ->
195
542
  tileRegions.forEach { tileRegion ->
196
543
  tileStore.removeTileRegion(tileRegion.id);
197
544
  }
198
545
 
199
- val offlineManager = getOfflineManager(mReactContext)
200
546
  offlineManager.getAllStylePacks { expected ->
201
547
  expected.value?.also { stylePacks ->
202
548
  stylePacks.forEach { stylePack ->
@@ -218,173 +564,32 @@ class RCTMGLOfflineModule(private val mReactContext: ReactApplicationContext) :
218
564
 
219
565
  }
220
566
 
221
- @ReactMethod
222
- fun getPackStatus(name: String, promise: Promise) {
223
- val pack = tileRegionPacks[name]
224
- if (pack != null) {
225
- promise.resolve(makeRegionStatus(name, pack.progress, pack))
226
- } else {
227
- promise.reject(Error("Pack not found"))
228
- Logger.w(REACT_CLASS, "getPackStatus - Unknown offline region")
229
- }
230
- }
231
-
232
- /*
233
- @ReactMethod
234
- public void setPackObserver(final String name, final Promise promise) {
235
- activateFileSource();
236
-
237
- final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext);
238
-
239
- offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
240
- @Override
241
- public void onList(OfflineRegion[] offlineRegions) {
242
- OfflineRegion region = getRegionByName(name, offlineRegions);
243
- boolean hasRegion = region != null;
244
-
245
- if (hasRegion) {
246
- setOfflineRegionObserver(name, region);
247
- }
248
-
249
- promise.resolve(hasRegion);
250
- }
251
-
252
- @Override
253
- public void onError(String error) {
254
- promise.reject("setPackObserver", error);
255
- }
256
- });
257
- }*/
258
- /*
259
- @ReactMethod
260
- public void invalidatePack(final String name, final Promise promise) {
261
- activateFileSource();
262
-
263
- final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext);
264
-
265
- offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
266
- @Override
267
- public void onList(OfflineRegion[] offlineRegions) {
268
- OfflineRegion region = getRegionByName(name, offlineRegions);
269
-
270
- if (region == null) {
271
- promise.resolve(null);
272
- Log.w(REACT_CLASS, "invalidateRegion - Unknown offline region");
273
- return;
274
- }
275
-
276
- region.invalidate(new OfflineRegion.OfflineRegionInvalidateCallback() {
277
- @Override
278
- public void onInvalidate() {
279
- promise.resolve(null);
280
- }
281
-
282
- @Override
283
- public void onError(String error) {
284
- promise.reject("invalidateRegion", error);
285
- }
286
- });
287
- }
288
-
289
- @Override
290
- public void onError(String error) {
291
- promise.reject("invalidateRegion", error);
292
- }
293
- });
294
- }*/
295
-
296
- @ReactMethod
297
- fun deletePack(name: String, promise: Promise) {
298
- getTileStore().getAllTileRegions{ expected ->
299
- if (expected.isValue) {
300
- expected.value?.let { tileRegionList ->
301
- var downloadedRegionExists = false;
302
- for (tileRegion in tileRegionList) {
303
- if (tileRegion.id == name) {
304
- downloadedRegionExists = true;
305
- getTileStore()!!.removeTileRegion(name, object : TileRegionCallback {
306
- override fun run(region: Expected<TileRegionError, TileRegion>) {
307
- promise.resolve(null);
308
- }
309
- })
310
- }
311
- }
312
- if (!downloadedRegionExists) {
313
- promise.resolve(null);
314
- }
315
- }
316
- }
317
- expected.error?.let { tileRegionError ->
318
- promise.reject("deletePack", "TileRegionError: $tileRegionError")
319
- }
320
- }
321
- }
322
-
323
567
  @ReactMethod
324
568
  fun migrateOfflineCache() {
569
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
570
+ // Old and new cache file paths
571
+ val targetPathName = mReactContext.filesDir.absolutePath + "/.mapbox/map_data"
572
+ val sourcePath = Paths.get(mReactContext.filesDir.absolutePath + "/mbgl-offline.db")
573
+ val targetPath = Paths.get(targetPathName + "/map_data.db")
325
574
 
326
- // Old and new cache file paths
327
- val targetPathName = mReactContext.filesDir.absolutePath + "/.mapbox/map_data"
328
- val sourcePath = Paths.get(mReactContext.filesDir.absolutePath + "/mbgl-offline.db")
329
- val targetPath = Paths.get(targetPathName + "/map_data.db")
330
-
331
- try {
332
- val directory = File(targetPathName)
333
- directory.mkdirs()
334
- Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING)
335
- Log.d("TAG","v10 cache directory created successfully")
336
- } catch (e: Exception) {
337
- Log.d("TAG", "${e}... file move unsuccessful")
338
- }
339
- }
340
-
341
- @ReactMethod
342
- fun pausePackDownload(name: String, promise: Promise) {
343
- val pack = tileRegionPacks[name]
344
- if (pack != null) {
345
- if (pack.cancelable != null) {
346
- pack.cancelable!!.cancel()
347
- pack.cancelable = null
348
- promise.resolve(null)
349
- } else {
350
- promise.reject("resumeRegionDownload", "Offline region cancelled already")
575
+ try {
576
+ val directory = File(targetPathName)
577
+ directory.mkdirs()
578
+ Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING)
579
+ Log.d(LOG_TAG, "v10 cache directory created successfully")
580
+ } catch (e: Exception) {
581
+ Log.d(LOG_TAG, "${e}... file move unsuccessful")
351
582
  }
352
583
  } else {
353
- promise.reject("resumeRegionDownload", "Unknown offline region")
584
+ Logger.w(LOG_TAG, "migrateOfflineCache only supported on api level 26 or later")
354
585
  }
355
586
  }
356
587
 
357
- @ReactMethod
358
- fun resumePackDownload(name: String, promise: Promise) {
359
- val pack = tileRegionPacks[name]
360
- if (pack != null) {
361
- startPackDownload(pack)
362
- promise.resolve(null)
363
- } else {
364
- promise.reject("resumeRegionDownload", "Unknown offline region")
365
- }
366
- }
367
-
368
- @ReactMethod
369
- fun setTileCountLimit(tileCountLimit: Int) {
370
- val offlineRegionManager = OfflineRegionManager(ResourceOptions.Builder().accessToken(RCTMGLModule.getAccessToken(mReactContext)).build())
371
- offlineRegionManager.setOfflineMapboxTileCountLimit(tileCountLimit.toLong())
372
- }
373
-
374
588
  @ReactMethod
375
589
  fun setProgressEventThrottle(eventThrottle: Double) {
376
590
  mProgressEventThrottle = eventThrottle
377
591
  }
378
592
 
379
- /*
380
- private OfflineRegionDefinition makeDefinition(LatLngBounds latLngBounds, ReadableMap options) {
381
- return new OfflineTilePyramidRegionDefinition(
382
- ConvertUtils.getString("styleURL", options, DEFAULT_STYLE_URL),
383
- latLngBounds,
384
- ConvertUtils.getDouble("minZoom", options, DEFAULT_MIN_ZOOM_LEVEL),
385
- ConvertUtils.getDouble("maxZoom", options, DEFAULT_MAX_ZOOM_LEVEL),
386
- mReactContext.getResources().getDisplayMetrics().density);
387
- }*/
388
593
  private fun getMetadataBytes(metadata: String?): ByteArray? {
389
594
  var metadataBytes: ByteArray? = null
390
595
  if (metadata == null || metadata.isEmpty()) {
@@ -398,51 +603,6 @@ class RCTMGLOfflineModule(private val mReactContext: ReactApplicationContext) :
398
603
  return metadataBytes
399
604
  }
400
605
 
401
- /*
402
- private void setOfflineRegionObserver(final String name, final OfflineRegion region) {
403
- region.setObserver(new OfflineRegion.OfflineRegionObserver() {
404
- OfflineRegionStatus prevStatus = null;
405
- long timestamp = System.currentTimeMillis();
406
-
407
- @Override
408
- public void onStatusChanged(OfflineRegionStatus status) {
409
- if (shouldSendUpdate(System.currentTimeMillis(), status)) {
410
- sendEvent(makeStatusEvent(name, status));
411
- timestamp = System.currentTimeMillis();
412
- }
413
- prevStatus = status;
414
- }
415
-
416
- @Override
417
- public void onError(OfflineRegionError error) {
418
- sendEvent(makeErrorEvent(name, EventTypes.OFFLINE_ERROR, error.getMessage()));
419
- }
420
-
421
- @Override
422
- public void mapboxTileCountLimitExceeded(long limit) {
423
- String message = String.format(Locale.getDefault(), "Mapbox tile limit exceeded %d", limit);
424
- sendEvent(makeErrorEvent(name, EventTypes.OFFLINE_TILE_LIMIT, message));
425
- }
426
-
427
- private boolean shouldSendUpdate (long currentTimestamp, OfflineRegionStatus curStatus) {
428
- if (prevStatus == null) {
429
- return false;
430
- }
431
-
432
- if (prevStatus.getDownloadState() != curStatus.getDownloadState()) {
433
- return true;
434
- }
435
-
436
- if (currentTimestamp - timestamp > mProgressEventThrottle) {
437
- return true;
438
- }
439
-
440
- return false;
441
- }
442
- });
443
-
444
- region.setDownloadState(ACTIVE_REGION_DOWNLOAD_STATE);
445
- }*/
446
606
  private fun sendEvent(event: IEvent) {
447
607
  val eventEmitter = eventEmitter
448
608
  eventEmitter.emit(event.key, event.toJSON())
@@ -483,7 +643,7 @@ class RCTMGLOfflineModule(private val mReactContext: ReactApplicationContext) :
483
643
  val progressPercentage =
484
644
  (status!!.completedResourceCount.toDouble() * 100.0) / (status.requiredResourceCount.toDouble())
485
645
  map.putString("name", regionName)
486
- map.putString("state", pack.state)
646
+ map.putString("state", pack.state.rawValue)
487
647
  map.putDouble("percentage", progressPercentage)
488
648
  map.putInt("completedResourceCount", status.completedResourceCount.toInt())
489
649
  map.putInt("completedResourceSize", status.completedResourceSize.toInt())
@@ -494,107 +654,16 @@ class RCTMGLOfflineModule(private val mReactContext: ReactApplicationContext) :
494
654
  return map
495
655
  }
496
656
 
497
- private fun getBoundsFromOptions(options: ReadableMap): LatLngBounds {
498
- val featureCollectionJSONStr = ConvertUtils.getString("bounds", options, "{}")
499
- val featureCollection = FeatureCollection.fromJson(featureCollectionJSONStr)
500
- return GeoJSONUtils.toLatLngBounds(featureCollection)
501
- }
657
+ companion object {
658
+ const val REACT_CLASS = "RCTMGLOfflineModule"
659
+ const val LOG_TAG = REACT_CLASS
660
+ const val OFFLINE_ERROR = "MapboxOfflineRegionError"
661
+ const val OFFLINE_PROGRESS = "MapboxOfflineRegionProgress"
502
662
 
503
- private fun fromOfflineRegion(bounds: LatLngBounds, metadataStr: String?): WritableMap {
504
- val map = Arguments.createMap()
505
- map.putArray("bounds", GeoJSONUtils.fromLatLngBounds(bounds))
506
- map.putString("metadata", metadataStr)
507
- return map
508
663
  }
664
+ }
509
665
 
510
- private fun fromOfflineRegion(region: Geometry): WritableMap {
511
- val map = Arguments.createMap()
512
- val bbox = TurfMeasurement.bbox(region)
513
- val bounds = Arguments.createArray()
514
- for (d: Double in bbox) {
515
- bounds.pushDouble(d)
516
- }
517
- map.putArray("bounds", bounds)
518
- map.putMap("geometry", GeoJSONUtils.fromGeometry(region))
519
-
520
- //map.putString("metadata", new String(region.getMetadata()));
521
- return map
522
- } /*
523
- private OfflineRegion getRegionByName(String name, OfflineRegion[] offlineRegions) {
524
- if (name == null || name.isEmpty()) {
525
- return null;
526
- }
527
-
528
- for (OfflineRegion region : offlineRegions) {
529
- boolean isRegion = false;
530
-
531
- try {
532
- byte[] byteMetadata = region.getMetadata();
533
-
534
- if (byteMetadata != null) {
535
- JSONObject metadata = new JSONObject(new String(byteMetadata));
536
- isRegion = name.equals(metadata.getString("name"));
537
- }
538
- } catch (JSONException e) {
539
- Log.w(REACT_CLASS, e.getLocalizedMessage());
540
- }
541
666
 
542
- if (isRegion) {
543
- return region;
544
- }
545
- }
546
667
 
547
- return null;
548
- }*/
549
668
 
550
- /*
551
- private void activateFileSource() {
552
- FileSource fileSource = FileSource.getInstance(mReactContext);
553
- fileSource.activate();
554
- }*/
555
- companion object {
556
- const val REACT_CLASS = "RCTMGLOfflineModule"
557
- const val LOG_TAG = REACT_CLASS
558
- @JvmField
559
- val INACTIVE_REGION_DOWNLOAD_STATE = TileRegionPack.INACTIVE
560
- @JvmField
561
- val ACTIVE_REGION_DOWNLOAD_STATE = TileRegionPack.ACTIVE
562
- @JvmField
563
- val COMPLETE_REGION_DOWNLOAD_STATE = TileRegionPack.COMPLETE
564
- @JvmField
565
- val OFFLINE_ERROR = "MapboxOfflineRegionError"
566
- @JvmField
567
- val OFFLINE_PROGRESS = "MapboxOfflineRegionProgress"
568
-
569
- // public static final String DEFAULT_STYLE_URL = Style.MAPBOX_STREETS;
570
- val DEFAULT_MIN_ZOOM_LEVEL = 10.0
571
- val DEFAULT_MAX_ZOOM_LEVEL = 20.0
572
- var offlineManager: OfflineManager? = null
573
- var _tileStore: TileStore? = null
574
- fun getOfflineManager(mReactContext: ReactApplicationContext?): OfflineManager {
575
- val manager = offlineManager
576
- if (manager == null) {
577
- val result = OfflineManager(
578
- ResourceOptions.Builder()
579
- .accessToken(RCTMGLModule.getAccessToken(mReactContext)).tileStore(
580
- getTileStore()
581
- ).build()
582
- )
583
- offlineManager = result
584
- return result
585
- } else {
586
- return manager
587
- }
588
- }
589
669
 
590
- fun getTileStore(): TileStore {
591
- val store = _tileStore
592
- if (store == null) {
593
- val result = TileStore.create()
594
- _tileStore = result
595
- return result
596
- }
597
- return store
598
- }
599
- }
600
- }