Skip to content

Commit a2299c3

Browse files
authored
Offline map support (#548)
* [codegen] Expose offline style pack API * Implement tilestore api * implement offline switch
1 parent 547d592 commit a2299c3

37 files changed

+6668
-2483
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
### main
22

33
* Add ModelLayer API.
4+
* Support for offline map, allowing users to download and store map data on their devices for use in environments with limited or no internet connectivity.
45
* Layer expressions support. Specify expressions when constructing a layer with all new expression support for layers.
56
*Before:*
67
```dart

README.md

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,39 @@ Contributions welcome!
88

99
## Supported API
1010

11-
| Feature | Android | iOS |
12-
| ------ | ------ | ----- |
13-
| Style | :white_check_mark: | :white_check_mark: |
14-
| Camera position | :white_check_mark: | :white_check_mark: |
15-
| Camera animations | :white_check_mark: | :white_check_mark: |
16-
| Events | :white_check_mark: | :white_check_mark: |
17-
| Gestures | :white_check_mark: | :white_check_mark: |
11+
| Feature | Android | iOS |
12+
| ------ |--------------------| -- |
13+
| Style | :white_check_mark: | :white_check_mark: |
14+
| Camera position | :white_check_mark: | :white_check_mark: |
15+
| Camera animations | :white_check_mark: | :white_check_mark: |
16+
| Events | :white_check_mark: | :white_check_mark: |
17+
| Gestures | :white_check_mark: | :white_check_mark: |
1818
| User Location | :white_check_mark: | :white_check_mark: |
19-
| Circle Layer | :white_check_mark: | :white_check_mark: |
20-
| Fill Layer | :white_check_mark: | :white_check_mark: |
21-
| Fill extrusion Layer | :white_check_mark: | :white_check_mark: |
22-
| Line Layer | :white_check_mark: | :white_check_mark: |
23-
| Circle Layer | :white_check_mark: | :white_check_mark: |
24-
| Raster Layer | :white_check_mark: | :white_check_mark: |
25-
| Symbol Layer | :white_check_mark: | :white_check_mark: |
26-
| Hillshade Layer | :white_check_mark: | :white_check_mark: |
27-
| Heatmap Layer | :white_check_mark: | :white_check_mark: |
28-
| Sky Layer | :white_check_mark: | :white_check_mark: |
29-
| GeoJson Source | :white_check_mark: | :white_check_mark: |
30-
| Image Source | :white_check_mark: | :white_check_mark: |
31-
| Vector Source | :white_check_mark: | :white_check_mark: |
32-
| Raster Source | :white_check_mark: | :white_check_mark: |
33-
| Rasterdem Source | :white_check_mark: | :white_check_mark: |
34-
| Circle Annotations | :white_check_mark: | :white_check_mark: |
35-
| Point Annotations | :white_check_mark: | :white_check_mark: |
36-
| Line Annotations | :white_check_mark: | :white_check_mark: |
37-
| Fill Annotations | :white_check_mark: | :white_check_mark: |
19+
| Circle Layer | :white_check_mark: | :white_check_mark: |
20+
| Fill Layer | :white_check_mark: | :white_check_mark: |
21+
| Fill extrusion Layer | :white_check_mark: | :white_check_mark: |
22+
| Line Layer | :white_check_mark: | :white_check_mark: |
23+
| Circle Layer | :white_check_mark: | :white_check_mark: |
24+
| Raster Layer | :white_check_mark: | :white_check_mark: |
25+
| Symbol Layer | :white_check_mark: | :white_check_mark: |
26+
| Hillshade Layer | :white_check_mark: | :white_check_mark: |
27+
| Heatmap Layer | :white_check_mark: | :white_check_mark: |
28+
| Sky Layer | :white_check_mark: | :white_check_mark: |
29+
| GeoJson Source | :white_check_mark: | :white_check_mark: |
30+
| Image Source | :white_check_mark: | :white_check_mark: |
31+
| Vector Source | :white_check_mark: | :white_check_mark: |
32+
| Raster Source | :white_check_mark: | :white_check_mark: |
33+
| Rasterdem Source | :white_check_mark: | :white_check_mark: |
34+
| Circle Annotations | :white_check_mark: | :white_check_mark: |
35+
| Point Annotations | :white_check_mark: | :white_check_mark: |
36+
| Line Annotations | :white_check_mark: | :white_check_mark: |
37+
| Fill Annotations | :white_check_mark: | :white_check_mark: |
3838
| Snapshotter | :white_check_mark: | :white_check_mark: |
39-
| Offline | :x: | :x: |
40-
| Viewport | :x: | :x: |
41-
| Style DSL | :x: | :x: |
42-
| Expression DSL | :x: | :x: |
43-
| View Annotations | :x: | :x: |
39+
| Offline | :white_check_mark: | :white_check_mark: |
40+
| Viewport | :x: | :x: |
41+
| Style DSL | :x: | :x: |
42+
| Expression DSL | :x: | :x: |
43+
| View Annotations | :x: | :x: |
4444

4545
## Requirements
4646

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/Extentions.kt

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ import android.graphics.Bitmap
55
import com.google.gson.Gson
66
import com.mapbox.bindgen.Expected
77
import com.mapbox.bindgen.None
8+
import com.mapbox.bindgen.Value
9+
import com.mapbox.common.TileRegionError
810
import com.mapbox.geojson.*
911
import com.mapbox.maps.EdgeInsets
12+
import com.mapbox.maps.StylePackError
13+
import com.mapbox.maps.extension.style.expressions.dsl.generated.min
1014
import com.mapbox.maps.extension.style.layers.properties.generated.ProjectionName
1115
import com.mapbox.maps.extension.style.light.LightPosition
1216
import com.mapbox.maps.extension.style.light.generated.ambientLight
1317
import com.mapbox.maps.extension.style.light.generated.directionalLight
1418
import com.mapbox.maps.extension.style.light.generated.flatLight
1519
import com.mapbox.maps.extension.style.projection.generated.Projection
1620
import com.mapbox.maps.extension.style.types.StyleTransition
21+
import com.mapbox.maps.logE
1722
import com.mapbox.maps.mapbox_maps.pigeons.*
1823
import org.json.JSONArray
1924
import org.json.JSONObject
@@ -28,6 +33,14 @@ fun GlyphsRasterizationMode.toGlyphsRasterizationMode(): com.mapbox.maps.GlyphsR
2833
GlyphsRasterizationMode.IDEOGRAPHS_RASTERIZED_LOCALLY -> com.mapbox.maps.GlyphsRasterizationMode.IDEOGRAPHS_RASTERIZED_LOCALLY
2934
}
3035
}
36+
37+
fun com.mapbox.maps.GlyphsRasterizationMode.toFLTGlyphsRasterizationMode(): GlyphsRasterizationMode {
38+
return when (this) {
39+
com.mapbox.maps.GlyphsRasterizationMode.NO_GLYPHS_RASTERIZED_LOCALLY -> GlyphsRasterizationMode.NO_GLYPHS_RASTERIZED_LOCALLY
40+
com.mapbox.maps.GlyphsRasterizationMode.IDEOGRAPHS_RASTERIZED_LOCALLY -> GlyphsRasterizationMode.IDEOGRAPHS_RASTERIZED_LOCALLY
41+
com.mapbox.maps.GlyphsRasterizationMode.ALL_GLYPHS_RASTERIZED_LOCALLY -> GlyphsRasterizationMode.ALL_GLYPHS_RASTERIZED_LOCALLY
42+
}
43+
}
3144
fun GlyphsRasterizationOptions.toGlyphsRasterizationOptions(): com.mapbox.maps.GlyphsRasterizationOptions {
3245
return com.mapbox.maps.GlyphsRasterizationOptions.Builder()
3346
.rasterizationMode(rasterizationMode.toGlyphsRasterizationMode())
@@ -528,10 +541,198 @@ fun Bitmap.toMbxImage(): MbxImage {
528541
return MbxImage(width.toLong(), height.toLong(), outputStream.toByteArray())
529542
}
530543

544+
fun StylePackLoadOptions.toStylePackLoadOptions(): com.mapbox.maps.StylePackLoadOptions {
545+
return com.mapbox.maps.StylePackLoadOptions.Builder()
546+
.glyphsRasterizationMode(glyphsRasterizationMode?.toGlyphsRasterizationMode())
547+
.metadata(metadata?.toValue())
548+
.acceptExpired(acceptExpired)
549+
.build()
550+
}
551+
552+
fun com.mapbox.maps.StylePack.toFLTStylePack(): StylePack {
553+
return StylePack(
554+
styleURI = this.styleURI,
555+
glyphsRasterizationMode = this.glyphsRasterizationMode.toFLTGlyphsRasterizationMode(),
556+
requiredResourceCount = this.requiredResourceCount,
557+
completedResourceSize = this.completedResourceCount,
558+
completedResourceCount = this.completedResourceCount,
559+
expires = this.expires?.time
560+
)
561+
}
562+
563+
fun com.mapbox.maps.StylePackLoadProgress.toFLTStylePackLoadProgress(): StylePackLoadProgress {
564+
return StylePackLoadProgress(
565+
this.completedResourceCount,
566+
this.completedResourceSize,
567+
this.erroredResourceCount,
568+
this.requiredResourceCount,
569+
this.loadedResourceCount,
570+
this.loadedResourceSize
571+
)
572+
}
573+
574+
fun TilesetDescriptorOptions.toTilesetDescriptorOptions(): com.mapbox.maps.TilesetDescriptorOptions {
575+
val builder = com.mapbox.maps.TilesetDescriptorOptions.Builder()
576+
.styleURI(styleURI)
577+
.minZoom(minZoom.toByte())
578+
.maxZoom(maxZoom.toByte())
579+
pixelRatio?.let { builder.pixelRatio(it.toFloat()) }
580+
tilesets?.let { builder.tilesets(it) }
581+
stylePackOptions?.let { builder.stylePackOptions(it.toStylePackLoadOptions()) }
582+
extraOptions?.let { builder.extraOptions(it.toValue()) }
583+
return builder.build()
584+
}
585+
586+
fun NetworkRestriction.toNetworkRestriction(): com.mapbox.common.NetworkRestriction {
587+
return when (this) {
588+
NetworkRestriction.DISALLOW_ALL -> com.mapbox.common.NetworkRestriction.DISALLOW_ALL
589+
NetworkRestriction.DISALLOW_EXPENSIVE -> com.mapbox.common.NetworkRestriction.DISALLOW_EXPENSIVE
590+
NetworkRestriction.NONE -> com.mapbox.common.NetworkRestriction.NONE
591+
}
592+
}
593+
594+
fun com.mapbox.common.TileRegion.toFLTTileRegion(): TileRegion {
595+
return TileRegion(
596+
this.id,
597+
this.requiredResourceCount,
598+
this.completedResourceCount,
599+
this.completedResourceSize
600+
)
601+
}
602+
603+
fun TileRegionEstimateOptions.toTileRegionEstimateOptions(): com.mapbox.common.TileRegionEstimateOptions {
604+
return com.mapbox.common.TileRegionEstimateOptions(
605+
this.errorMargin.toFloat(),
606+
this.preciseEstimationTimeout.toLong(),
607+
this.preciseEstimationTimeout.toLong(),
608+
this.extraOptions?.toValue()
609+
)
610+
}
611+
612+
fun com.mapbox.common.TileRegionEstimateResult.toFLTTileRegionEstimateResult(): TileRegionEstimateResult {
613+
return TileRegionEstimateResult(
614+
this.errorMargin, this.transferSize, this.storageSize,
615+
)
616+
}
617+
618+
fun com.mapbox.common.TileRegionLoadProgress.toFLTTileRegionLoadProgress(): TileRegionLoadProgress {
619+
return TileRegionLoadProgress(
620+
this.completedResourceCount,
621+
this.completedResourceSize,
622+
this.erroredResourceCount,
623+
this.requiredResourceCount,
624+
this.loadedResourceCount,
625+
this.loadedResourceSize
626+
)
627+
}
628+
629+
fun com.mapbox.common.TileRegionEstimateProgress.toFLTTileRegionEstimateProgress(): TileRegionEstimateProgress {
630+
return TileRegionEstimateProgress(
631+
this.requiredResourceCount, this.completedResourceCount, this.erroredResourceCount
632+
)
633+
}
634+
531635
fun Expected<String, None>.handleResult(callback: (Result<Unit>) -> Unit) {
532636
if (this.isError) {
533637
callback(Result.failure(Throwable(this.error)))
534638
} else {
535639
callback(Result.success(Unit))
536640
}
641+
}
642+
643+
@JvmName("toStylePackResult")
644+
fun <V : Any, NewValue> Expected<StylePackError, V>.toResult(valueTransform: (V) -> NewValue): Result<NewValue> {
645+
return fold(
646+
{
647+
Result.failure(Throwable(it.message))
648+
},
649+
{
650+
Result.success(valueTransform(it))
651+
}
652+
)
653+
}
654+
655+
@JvmName("toTileRegionResult")
656+
fun <V : Any, NewValue> Expected<TileRegionError, V>.toResult(valueTransform: (V) -> NewValue): Result<NewValue> {
657+
return fold(
658+
{
659+
Result.failure(Throwable(it.message))
660+
},
661+
{
662+
Result.success(valueTransform(it))
663+
}
664+
)
665+
}
666+
667+
fun Value.toFLTValue(): Any? {
668+
return when (contents) {
669+
is List<*> -> {
670+
(contents as List<*>).map { (it as? Value)?.toFLTValue() ?: it }
671+
}
672+
673+
is Map<*, *> -> {
674+
(contents as Map<*, *>)
675+
.mapKeys { (it.key as? Value)?.toFLTValue() ?: it.key }
676+
.mapValues { (it.value as? Value)?.toFLTValue() ?: it.value }
677+
}
678+
679+
else -> {
680+
contents
681+
}
682+
}
683+
}
684+
685+
fun Any.toValue(): Value {
686+
return if (this is String) {
687+
if (this.startsWith("{") || this.startsWith("[")) {
688+
Value.fromJson(this).value!!
689+
} else {
690+
val number = this.toDoubleOrNull()
691+
if (number != null) {
692+
Value.valueOf(number)
693+
} else {
694+
Value.valueOf(this)
695+
}
696+
}
697+
} else if (this is Double) {
698+
Value.valueOf(this)
699+
} else if (this is Long) {
700+
Value.valueOf(this)
701+
} else if (this is Int) {
702+
Value.valueOf(this.toLong())
703+
} else if (this is Boolean) {
704+
Value.valueOf(this)
705+
} else if (this is IntArray) {
706+
val valueArray = this.map { Value(it.toLong()) }
707+
Value(valueArray)
708+
} else if (this is BooleanArray) {
709+
val valueArray = this.map(::Value)
710+
Value(valueArray)
711+
} else if (this is DoubleArray) {
712+
val valueArray = this.map(::Value)
713+
Value(valueArray)
714+
} else if (this is FloatArray) {
715+
val valueArray = this.map { Value(it.toDouble()) }
716+
Value(valueArray)
717+
} else if (this is LongArray) {
718+
val valueArray = this.map(::Value)
719+
Value(valueArray)
720+
} else if (this is Array<*>) {
721+
val valueArray = this.map { it?.toValue() }
722+
Value(valueArray)
723+
} else if (this is List<*>) {
724+
val valueArray = this.map { it?.toValue() }
725+
Value(valueArray)
726+
} else if (this is HashMap<*, *>) {
727+
val valueMap = this
728+
.mapKeys { it.key as? String }
729+
.mapValues { it.value.toValue() }
730+
Value.valueOf(kotlin.collections.HashMap(valueMap))
731+
} else {
732+
logE(
733+
"StyleController",
734+
"Can not map value, type is not supported: ${this::class.java.canonicalName}"
735+
)
736+
Value.valueOf("")
737+
}
537738
}

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapsPlugin.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ package com.mapbox.maps.mapbox_maps
22

33
import android.content.Context
44
import androidx.lifecycle.Lifecycle
5+
import com.mapbox.maps.mapbox_maps.offline.OfflineMapInstanceManager
6+
import com.mapbox.maps.mapbox_maps.offline.OfflineSwitch
57
import com.mapbox.maps.mapbox_maps.pigeons._MapboxMapsOptions
68
import com.mapbox.maps.mapbox_maps.pigeons._MapboxOptions
9+
import com.mapbox.maps.mapbox_maps.pigeons._OfflineMapInstanceManager
10+
import com.mapbox.maps.mapbox_maps.pigeons._OfflineSwitch
711
import com.mapbox.maps.mapbox_maps.pigeons._SnapshotterInstanceManager
12+
import com.mapbox.maps.mapbox_maps.pigeons._TileStoreInstanceManager
813
import com.mapbox.maps.mapbox_maps.snapshot.SnapshotterInstanceManager
914
import io.flutter.embedding.engine.plugins.FlutterPlugin
1015
import io.flutter.embedding.engine.plugins.activity.ActivityAware
@@ -37,11 +42,16 @@ class MapboxMapsPlugin : FlutterPlugin, ActivityAware {
3742
private fun setupStaticChannels(context: Context, binaryMessenger: BinaryMessenger) {
3843
val optionsController = MapboxOptionsController()
3944
val snapshotterInstanceManager = SnapshotterInstanceManager(context, binaryMessenger)
45+
val offlineMapInstanceManager = OfflineMapInstanceManager(context, binaryMessenger)
46+
val offlineSwitch = OfflineSwitch()
4047
// static options handling should be setup upon attachment,
4148
// as options can before configured before the map view is setup
4249
_MapboxMapsOptions.setUp(binaryMessenger, optionsController)
4350
_MapboxOptions.setUp(binaryMessenger, optionsController)
4451
_SnapshotterInstanceManager.setUp(binaryMessenger, snapshotterInstanceManager)
52+
_OfflineMapInstanceManager.setUp(binaryMessenger, offlineMapInstanceManager)
53+
_TileStoreInstanceManager.setUp(binaryMessenger, offlineMapInstanceManager)
54+
_OfflineSwitch.setUp(binaryMessenger, offlineSwitch)
4555
LoggingController.setup(binaryMessenger)
4656
}
4757

0 commit comments

Comments
 (0)