Android long running activity (#3512)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,7 @@
|
|||||||
/cmake-build-debug
|
/cmake-build-debug
|
||||||
/platform/android/build
|
/platform/android/build
|
||||||
/platform/android/MapLibreAndroid/src/main/assets/sdk_versions/com.mapbox.mapboxsdk
|
/platform/android/MapLibreAndroid/src/main/assets/sdk_versions/com.mapbox.mapboxsdk
|
||||||
|
/platform/android/MapLibreAndroid/android-binaries-*.zip
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
|
||||||
local.properties
|
local.properties
|
||||||
|
|||||||
@@ -103,6 +103,11 @@ kotlin {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":MapLibreAndroid"))
|
implementation(project(":MapLibreAndroid"))
|
||||||
|
|
||||||
|
implementation(libs.maplibreNavigation) {
|
||||||
|
exclude(group = "org.maplibre.gl", module = "android-sdk")
|
||||||
|
}
|
||||||
|
|
||||||
implementation(libs.maplibreJavaTurf)
|
implementation(libs.maplibreJavaTurf)
|
||||||
|
|
||||||
implementation(libs.supportRecyclerView)
|
implementation(libs.supportRecyclerView)
|
||||||
|
|||||||
@@ -10,9 +10,12 @@
|
|||||||
|
|
||||||
# GMS
|
# GMS
|
||||||
-dontnote com.google.android.gms.**
|
-dontnote com.google.android.gms.**
|
||||||
|
-dontwarn com.google.android.gms.**
|
||||||
|
-keep class com.google.android.gms.** { *; }
|
||||||
|
|
||||||
-keep class org.maplibre.android.testapp.model.customlayer.ExampleCustomLayer { *; }
|
-keep class org.maplibre.android.testapp.model.customlayer.ExampleCustomLayer { *; }
|
||||||
|
|
||||||
|
|
||||||
# okhttp
|
# okhttp
|
||||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MapLibreApplication"
|
android:name=".MapLibreApplication"
|
||||||
@@ -212,6 +213,31 @@
|
|||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".activity.FeatureOverviewActivity" />
|
android:value=".activity.FeatureOverviewActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".activity.stability.LongRunningActivity"
|
||||||
|
android:description="@string/description_long_running_maps"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/activity_long_running_maps"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:launchMode="singleInstance">
|
||||||
|
<meta-data
|
||||||
|
android:name="@string/category"
|
||||||
|
android:value="@string/category_integration" />
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value=".activity.FeatureOverviewActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".activity.stability.UserMapActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:launchMode="singleInstance">
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".activity.stability.NavigationMapActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:launchMode="singleInstance">
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".activity.fragment.MultiMapActivity"
|
android:name=".activity.fragment.MultiMapActivity"
|
||||||
android:description="@string/description_multimap"
|
android:description="@string/description_multimap"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -35,54 +35,15 @@ import org.maplibre.android.testapp.utils.BenchmarkResult
|
|||||||
import org.maplibre.android.testapp.utils.BenchmarkRun
|
import org.maplibre.android.testapp.utils.BenchmarkRun
|
||||||
import org.maplibre.android.testapp.utils.BenchmarkRunResult
|
import org.maplibre.android.testapp.utils.BenchmarkRunResult
|
||||||
import org.maplibre.android.testapp.utils.FrameTimeStore
|
import org.maplibre.android.testapp.utils.FrameTimeStore
|
||||||
|
import org.maplibre.android.testapp.utils.animateCameraSuspend
|
||||||
import org.maplibre.android.testapp.utils.jsonPayload
|
import org.maplibre.android.testapp.utils.jsonPayload
|
||||||
|
import org.maplibre.android.testapp.utils.setStyleSuspend
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import kotlin.collections.flatMap
|
import kotlin.collections.flatMap
|
||||||
import kotlin.collections.toTypedArray
|
import kotlin.collections.toTypedArray
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
suspend fun MapLibreMap.animateCameraSuspend(
|
|
||||||
cameraUpdate: CameraUpdate,
|
|
||||||
durationMs: Int
|
|
||||||
): Unit = suspendCancellableCoroutine { continuation ->
|
|
||||||
animateCamera(cameraUpdate, durationMs, object : CancelableCallback {
|
|
||||||
var resumed = false
|
|
||||||
|
|
||||||
override fun onCancel() {
|
|
||||||
continuation.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFinish() {
|
|
||||||
if (!resumed) {
|
|
||||||
resumed = true
|
|
||||||
continuation.resume(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun MapView.setStyleSuspend(styleUrl: String): Unit =
|
|
||||||
suspendCancellableCoroutine { continuation ->
|
|
||||||
var listener: MapView.OnDidFinishLoadingStyleListener? = null
|
|
||||||
|
|
||||||
var resumed = false
|
|
||||||
listener = MapView.OnDidFinishLoadingStyleListener {
|
|
||||||
if (!resumed) {
|
|
||||||
resumed = true
|
|
||||||
listener?.let { removeOnDidFinishLoadingStyleListener(it) }
|
|
||||||
continuation.resume(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addOnDidFinishLoadingStyleListener(listener)
|
|
||||||
getMapAsync { map -> map.setStyle(styleUrl) }
|
|
||||||
|
|
||||||
continuation.invokeOnCancellation {
|
|
||||||
removeOnDidFinishLoadingStyleListener(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Benchmark using a [android.view.TextureView]
|
* Benchmark using a [android.view.TextureView]
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package org.maplibre.android.testapp.activity.stability
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.app.ActivityOptions
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.hardware.display.DisplayManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.maplibre.android.testapp.R
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
class LongRunningActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
// config
|
||||||
|
companion object {
|
||||||
|
private val LOG = Logger.getLogger(LongRunningActivity::class.java.name)
|
||||||
|
|
||||||
|
// activity lifetime (seconds)
|
||||||
|
private const val DURATION = 10 * 60 * 60
|
||||||
|
// start one activity/view per display if available
|
||||||
|
private const val USE_SECONDARY_DISPLAY = true
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
fun printStats(context: Context) {
|
||||||
|
val activityManager = context.getSystemService(ACTIVITY_SERVICE) as ActivityManager
|
||||||
|
val sysMemInfo = ActivityManager.MemoryInfo()
|
||||||
|
activityManager.getMemoryInfo(sysMemInfo)
|
||||||
|
|
||||||
|
LOG.info("System: \n" +
|
||||||
|
"\tavailable memory - ${sysMemInfo.availMem / 1048576} MB\n" +
|
||||||
|
"\ttotal memory - ${sysMemInfo.totalMem / 1048576} MB\n" +
|
||||||
|
"\tlow memory threshold - ${sysMemInfo.threshold / 1048576} MB\n" +
|
||||||
|
"\tlow memory - ${sysMemInfo.lowMemory}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
val appMemInfo = activityManager.getProcessMemoryInfo(intArrayOf(android.os.Process.myPid())).first()
|
||||||
|
|
||||||
|
LOG.info("Application memory: \n" +
|
||||||
|
appMemInfo.memoryStats.map { "\t${it.key} - ${it.value} KB" }.joinToString("\n")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_long_running_maps)
|
||||||
|
|
||||||
|
if (USE_SECONDARY_DISPLAY && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager
|
||||||
|
val displays = displayManager.displays
|
||||||
|
|
||||||
|
if (displays.size > 1) {
|
||||||
|
// remove navigation map from layout
|
||||||
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.remove(supportFragmentManager.findFragmentById(R.id.navigation_map)!!)
|
||||||
|
.commit()
|
||||||
|
|
||||||
|
// and move it to it's own activity
|
||||||
|
val activityOptions = ActivityOptions.makeBasic()
|
||||||
|
activityOptions.launchDisplayId = displays[1].displayId
|
||||||
|
|
||||||
|
startActivity(Intent(this, NavigationMapActivity::class.java), activityOptions.toBundle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Activity running for $DURATION seconds")
|
||||||
|
|
||||||
|
val context = this
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
delay(DURATION * 1000L)
|
||||||
|
|
||||||
|
printStats(context)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
package org.maplibre.android.testapp.activity.stability
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.location.Location
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.PersistableBundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import org.maplibre.android.geometry.LatLng
|
||||||
|
import org.maplibre.android.location.LocationComponentActivationOptions
|
||||||
|
import org.maplibre.android.location.OnLocationCameraTransitionListener
|
||||||
|
import org.maplibre.android.location.modes.CameraMode
|
||||||
|
import org.maplibre.android.location.modes.RenderMode
|
||||||
|
import org.maplibre.android.maps.MapLibreMap
|
||||||
|
import org.maplibre.android.maps.MapView
|
||||||
|
import org.maplibre.android.maps.SupportMapFragment
|
||||||
|
import org.maplibre.android.testapp.activity.stability.LongRunningActivity.Companion.printStats
|
||||||
|
import org.maplibre.android.testapp.styles.TestStyles
|
||||||
|
import org.maplibre.android.testapp.utils.GeoParseUtil
|
||||||
|
import org.maplibre.android.testapp.utils.setStyleSuspend
|
||||||
|
import org.maplibre.geojson.Point
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.location.replay.ReplayRouteLocationEngine
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.milestone.BannerInstructionMilestone
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.milestone.Milestone
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.milestone.MilestoneEventListener
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.models.DirectionsResponse
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.models.DirectionsRoute
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.models.RouteOptions
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.navigation.MapLibreNavigation
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.navigation.MapLibreNavigationOptions
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.navigation.NavigationMapRoute
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.routeprogress.ProgressChangeListener
|
||||||
|
import org.maplibre.navigation.android.navigation.v5.routeprogress.RouteProgress
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.logging.Logger
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class NavigationMap : SupportMapFragment(), ProgressChangeListener, MilestoneEventListener {
|
||||||
|
private lateinit var map: MapLibreMap
|
||||||
|
private lateinit var mapView: MapView
|
||||||
|
|
||||||
|
private lateinit var navigation: MapLibreNavigation
|
||||||
|
private lateinit var navigationMapRoute: NavigationMapRoute
|
||||||
|
private val replayRouteLocationEngine = ReplayRouteLocationEngine()
|
||||||
|
private var routeUpdateTimer = 0.0
|
||||||
|
|
||||||
|
// config
|
||||||
|
companion object {
|
||||||
|
private val LOG = Logger.getLogger(NavigationMap::class.java.name)
|
||||||
|
private const val RANDOM_SEED = 42
|
||||||
|
private val RANDOM = Random(RANDOM_SEED)
|
||||||
|
|
||||||
|
private val STYLES = arrayListOf(
|
||||||
|
TestStyles.AMERICANA,
|
||||||
|
TestStyles.OPENFREEMAP_LIBERTY,
|
||||||
|
TestStyles.OPENFREEMAP_BRIGHT,
|
||||||
|
TestStyles.AWS_OPEN_DATA_STANDARD_LIGHT,
|
||||||
|
TestStyles.PROTOMAPS_LIGHT,
|
||||||
|
TestStyles.PROTOMAPS_DARK,
|
||||||
|
TestStyles.PROTOMAPS_GRAYSCALE,
|
||||||
|
TestStyles.PROTOMAPS_WHITE,
|
||||||
|
TestStyles.PROTOMAPS_BLACK,
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RouteProvider {
|
||||||
|
Local,
|
||||||
|
OSRM,
|
||||||
|
Valhalla,
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ROUTE_PROVIDER = RouteProvider.Local
|
||||||
|
private const val ROUTE_UPDATE_INTERVAL = 10.0
|
||||||
|
|
||||||
|
// used by remote providers
|
||||||
|
private val ROUTES = arrayOf(
|
||||||
|
Pair(LatLng(52.521487, 13.409961), LatLng(52.502501, 13.423342)), // BER short
|
||||||
|
Pair(LatLng(52.46314, 13.339116), LatLng(52.545929, 13.457635)), // BER long
|
||||||
|
Pair(LatLng(38.903727, -77.000668), LatLng(38.890509, -77.025958)), // DC short
|
||||||
|
Pair(LatLng(38.876664, -77.206788), LatLng(38.957716, -77.027674)), // DC long
|
||||||
|
Pair(LatLng(37.781832, -122.401477), LatLng(37.771035, -122.410592)), // SF short
|
||||||
|
Pair(LatLng(37.736431, -122.504263), LatLng(37.785594, -122.401209)), // SF long
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun random(min: Double, max: Double): Double = min + RANDOM.nextDouble() * (max - min)
|
||||||
|
private fun random(min: Int, max: Int): Int = RANDOM.nextInt(min, max)
|
||||||
|
|
||||||
|
private fun randomZoom(): Double = random(13.0, 18.0)
|
||||||
|
private fun randomTilt(): Double = random(30.0, 60.0)
|
||||||
|
private fun randomSpeed(): Int = random(30, 130) // in km/h
|
||||||
|
private fun randomWaitTime(): Long = random(5000, 10000).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMapReady(maplibreMap: MapLibreMap) {
|
||||||
|
super.onMapReady(maplibreMap)
|
||||||
|
|
||||||
|
this.map = maplibreMap
|
||||||
|
this.mapView = view as MapView
|
||||||
|
|
||||||
|
LOG.info("NavigationMap seed $RANDOM_SEED")
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun run() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
mapView.setStyleSuspend(STYLES.random(RANDOM))
|
||||||
|
enableLocation()
|
||||||
|
|
||||||
|
navigation = MapLibreNavigation(requireContext(), MapLibreNavigationOptions
|
||||||
|
.builder()
|
||||||
|
.snapToRoute(true)
|
||||||
|
.build()
|
||||||
|
).apply {
|
||||||
|
snapEngine
|
||||||
|
addProgressChangeListener(this@NavigationMap)
|
||||||
|
addMilestoneEventListener(this@NavigationMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
navigationMapRoute = NavigationMapRoute(navigation, mapView, map)
|
||||||
|
navigation.locationEngine = replayRouteLocationEngine
|
||||||
|
|
||||||
|
startNewRoute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startNewRoute() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
delay(randomWaitTime())
|
||||||
|
|
||||||
|
printStats(requireContext())
|
||||||
|
|
||||||
|
navigation.stopNavigation()
|
||||||
|
navigationMapRoute.removeRoute()
|
||||||
|
|
||||||
|
mapView.setStyleSuspend(STYLES.random(RANDOM))
|
||||||
|
|
||||||
|
val route = getRoute() ?: return@launch
|
||||||
|
|
||||||
|
// display route
|
||||||
|
navigationMapRoute = NavigationMapRoute(navigation, mapView, map)
|
||||||
|
navigationMapRoute.addRoute(route)
|
||||||
|
|
||||||
|
// force tile load at starting position
|
||||||
|
val startingPoint = route.routeOptions()?.coordinates()?.firstOrNull()
|
||||||
|
if (startingPoint != null) {
|
||||||
|
map.locationComponent.forceLocationUpdate(
|
||||||
|
Location("StartingLocation").apply {
|
||||||
|
latitude = startingPoint.latitude()
|
||||||
|
longitude = startingPoint.longitude()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(randomWaitTime())
|
||||||
|
|
||||||
|
replayRouteLocationEngine.assign(route)
|
||||||
|
routeUpdateTimer = route.duration()
|
||||||
|
navigation.startNavigation(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
private fun enableLocation() {
|
||||||
|
map.locationComponent.activateLocationComponent(
|
||||||
|
LocationComponentActivationOptions
|
||||||
|
.builder(requireContext(), map.style!!)
|
||||||
|
.useDefaultLocationEngine(false)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
map.locationComponent.isLocationComponentEnabled = true
|
||||||
|
map.locationComponent.renderMode = RenderMode.GPS
|
||||||
|
map.locationComponent.setCameraMode(
|
||||||
|
CameraMode.TRACKING_GPS,
|
||||||
|
object :
|
||||||
|
OnLocationCameraTransitionListener {
|
||||||
|
override fun onLocationCameraTransitionFinished(cameraMode: Int) {
|
||||||
|
map.locationComponent.zoomWhileTracking(randomZoom())
|
||||||
|
map.locationComponent.tiltWhileTracking(randomTilt())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLocationCameraTransitionCanceled(cameraMode: Int) {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRoute(): DirectionsRoute? {
|
||||||
|
var location: LatLng? = null
|
||||||
|
var destination: LatLng? = null
|
||||||
|
|
||||||
|
val routeString: String? = when (ROUTE_PROVIDER) {
|
||||||
|
RouteProvider.Local -> {
|
||||||
|
val context = requireContext()
|
||||||
|
val routeFile = context.assets.list("routes/")!!.random(RANDOM)
|
||||||
|
LOG.info("Navigation - local route: $routeFile")
|
||||||
|
|
||||||
|
val routeStr = GeoParseUtil.loadStringFromAssets(context, "routes/$routeFile")
|
||||||
|
|
||||||
|
// quick parse to get the waypoints before starting the route
|
||||||
|
val json = Json.parseToJsonElement(routeStr).jsonObject
|
||||||
|
|
||||||
|
val waypoints = json["waypoints"]?.jsonArray
|
||||||
|
val startWaypoint = waypoints?.first()?.jsonObject?.get("location")?.jsonArray
|
||||||
|
val endWaypoint = waypoints?.last()?.jsonObject?.get("location")?.jsonArray
|
||||||
|
|
||||||
|
if (startWaypoint != null) {
|
||||||
|
location = LatLng(
|
||||||
|
Json.decodeFromJsonElement<Double>(startWaypoint.last()),
|
||||||
|
Json.decodeFromJsonElement<Double>(startWaypoint.first()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endWaypoint != null) {
|
||||||
|
destination = LatLng(
|
||||||
|
Json.decodeFromJsonElement<Double>(endWaypoint.last()),
|
||||||
|
Json.decodeFromJsonElement<Double>(endWaypoint.first()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
routeStr
|
||||||
|
}
|
||||||
|
|
||||||
|
RouteProvider.OSRM -> {
|
||||||
|
val routePoints = ROUTES.random(RANDOM)
|
||||||
|
location = routePoints.first
|
||||||
|
destination = routePoints.second
|
||||||
|
|
||||||
|
val get = "${location.longitude},${location.latitude};" +
|
||||||
|
"${destination.longitude},${destination.latitude}" +
|
||||||
|
"?steps=true"
|
||||||
|
|
||||||
|
val request = Request
|
||||||
|
.Builder()
|
||||||
|
.header("User-Agent", "MapLibre Android")
|
||||||
|
.url("https://router.project-osrm.org/route/v1/driving/$get")
|
||||||
|
.get()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
LOG.info("Navigation - OSRM route request " +
|
||||||
|
"(${location.longitude},${location.latitude}) -> " +
|
||||||
|
"(${destination.longitude},${destination.latitude})"
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = OkHttpClient().newCall(request).execute()
|
||||||
|
response.body?.string()
|
||||||
|
}
|
||||||
|
|
||||||
|
RouteProvider.Valhalla -> {
|
||||||
|
val routePoints = ROUTES.random(RANDOM)
|
||||||
|
location = routePoints.first
|
||||||
|
destination = routePoints.second
|
||||||
|
|
||||||
|
val requestBody = Gson().toJson(
|
||||||
|
mapOf(
|
||||||
|
"format" to "osrm",
|
||||||
|
"costing" to "auto",
|
||||||
|
"banner_instructions" to true,
|
||||||
|
"voice_instructions" to true,
|
||||||
|
"language" to Locale.getDefault().language,
|
||||||
|
"directions_options" to mapOf(
|
||||||
|
"units" to "kilometers"
|
||||||
|
),
|
||||||
|
"costing_options" to mapOf(
|
||||||
|
"auto" to mapOf(
|
||||||
|
"top_speed" to 130
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"locations" to listOf(
|
||||||
|
mapOf(
|
||||||
|
"lon" to location.longitude,
|
||||||
|
"lat" to location.latitude,
|
||||||
|
"type" to "break"
|
||||||
|
),
|
||||||
|
mapOf(
|
||||||
|
"lon" to destination.longitude,
|
||||||
|
"lat" to destination.latitude,
|
||||||
|
"type" to "break"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).toRequestBody("application/json; charset=utf-8".toMediaType())
|
||||||
|
|
||||||
|
val request = Request
|
||||||
|
.Builder()
|
||||||
|
.header("User-Agent", "MapLibre Android")
|
||||||
|
.url("https://valhalla1.openstreetmap.de/route")
|
||||||
|
.post(requestBody)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
LOG.info("Navigation - Valhalla route request " +
|
||||||
|
"(${location.longitude},${location.latitude}) -> " +
|
||||||
|
"(${destination.longitude},${destination.latitude})"
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = OkHttpClient().newCall(request).execute()
|
||||||
|
response.body?.string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeString == null) {
|
||||||
|
LOG.warning("Failed to get route data")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val directionsResponse = DirectionsResponse.fromJson(routeString)
|
||||||
|
val route = directionsResponse.routes().first()
|
||||||
|
|
||||||
|
val routeOptions = RouteOptions
|
||||||
|
.builder()
|
||||||
|
.baseUrl("https://maplibre.org")
|
||||||
|
.profile("maplibre")
|
||||||
|
.user("maplibre")
|
||||||
|
.accessToken("maplibre")
|
||||||
|
.voiceInstructions(true)
|
||||||
|
.bannerInstructions(true)
|
||||||
|
.language(Locale.getDefault().language)
|
||||||
|
.coordinates(mutableListOf(
|
||||||
|
location?.let { Point.fromLngLat(it.longitude, location.latitude) },
|
||||||
|
destination?.let { Point.fromLngLat(it.longitude, destination.latitude) },
|
||||||
|
))
|
||||||
|
.requestUuid("0000-0000-0000-0000")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return route.toBuilder().routeOptions(routeOptions).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProgressChange(location: Location, progress: RouteProgress) {
|
||||||
|
map.locationComponent.forceLocationUpdate(location)
|
||||||
|
|
||||||
|
if (routeUpdateTimer - progress.durationRemaining() > ROUTE_UPDATE_INTERVAL) {
|
||||||
|
map.locationComponent.zoomWhileTracking(randomZoom())
|
||||||
|
map.locationComponent.tiltWhileTracking(randomTilt())
|
||||||
|
|
||||||
|
replayRouteLocationEngine.updateSpeed(randomSpeed())
|
||||||
|
|
||||||
|
routeUpdateTimer = progress.durationRemaining()
|
||||||
|
|
||||||
|
LOG.info("Navigation - remaining duration ${progress.durationRemaining()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMilestoneEvent(routeProgress: RouteProgress, instruction: String, milestone: Milestone) {
|
||||||
|
if (milestone !is BannerInstructionMilestone) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (milestone.bannerInstructions.primary().type() == "arrive") {
|
||||||
|
startNewRoute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NavigationMapActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(android.R.id.content, NavigationMap())
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
package org.maplibre.android.testapp.activity.stability
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.maplibre.android.annotations.IconFactory
|
||||||
|
import org.maplibre.android.annotations.MarkerOptions
|
||||||
|
import org.maplibre.android.annotations.PolygonOptions
|
||||||
|
import org.maplibre.android.annotations.PolylineOptions
|
||||||
|
import org.maplibre.android.camera.CameraPosition
|
||||||
|
import org.maplibre.android.camera.CameraUpdateFactory
|
||||||
|
import org.maplibre.android.geometry.LatLng
|
||||||
|
import org.maplibre.android.geometry.LatLngBounds
|
||||||
|
import org.maplibre.android.maps.MapLibreMap
|
||||||
|
import org.maplibre.android.maps.MapView
|
||||||
|
import org.maplibre.android.maps.SupportMapFragment
|
||||||
|
import org.maplibre.android.testapp.styles.TestStyles
|
||||||
|
import org.maplibre.android.testapp.utils.animateCameraSuspend
|
||||||
|
import org.maplibre.android.testapp.utils.setStyleSuspend
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.util.logging.Logger
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class UserMap : SupportMapFragment() {
|
||||||
|
private lateinit var map: MapLibreMap
|
||||||
|
private lateinit var mapView: MapView
|
||||||
|
private lateinit var bitmapDrawables: List<Drawable>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = Logger.getLogger(UserMap::class.java.name)
|
||||||
|
private const val RANDOM_SEED = 42
|
||||||
|
private val RANDOM = Random(RANDOM_SEED)
|
||||||
|
|
||||||
|
private val STYLES = arrayListOf(
|
||||||
|
TestStyles.DEMOTILES,
|
||||||
|
TestStyles.AMERICANA,
|
||||||
|
TestStyles.OPENFREEMAP_LIBERTY,
|
||||||
|
TestStyles.OPENFREEMAP_BRIGHT,
|
||||||
|
TestStyles.AWS_OPEN_DATA_STANDARD_LIGHT,
|
||||||
|
TestStyles.PROTOMAPS_LIGHT,
|
||||||
|
TestStyles.PROTOMAPS_DARK,
|
||||||
|
TestStyles.PROTOMAPS_GRAYSCALE,
|
||||||
|
TestStyles.PROTOMAPS_WHITE,
|
||||||
|
TestStyles.PROTOMAPS_BLACK,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val PLACES = arrayListOf(
|
||||||
|
LatLng(37.7749, -122.4194), // SF
|
||||||
|
LatLng(38.9072, -77.0369), // DC
|
||||||
|
LatLng(52.3702, 4.8952), // AMS
|
||||||
|
LatLng(60.1699, 24.9384), // HEL
|
||||||
|
LatLng(-13.1639, -74.2236), // AYA
|
||||||
|
LatLng(52.5200, 13.4050), // BER
|
||||||
|
LatLng(12.9716, 77.5946), // BAN
|
||||||
|
LatLng(31.2304, 121.4737), // SHA
|
||||||
|
)
|
||||||
|
|
||||||
|
// controls the list of icons available
|
||||||
|
// false -> use `ICONS` list
|
||||||
|
// true -> use all bitmap drawables in `org.maplibre.android.R.drawable.*`
|
||||||
|
private const val USE_ALL_DRAWABLES = false
|
||||||
|
private val ICONS = arrayListOf(
|
||||||
|
org.maplibre.android.R.drawable.maplibre_info_icon_default,
|
||||||
|
org.maplibre.android.R.drawable.maplibre_user_icon,
|
||||||
|
org.maplibre.android.R.drawable.maplibre_marker_icon_default,
|
||||||
|
org.maplibre.android.R.drawable.maplibre_compass_icon,
|
||||||
|
org.maplibre.android.R.drawable.maplibre_user_puck_icon,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun random(min: Double, max: Double): Double = min + RANDOM.nextDouble() * (max - min)
|
||||||
|
private fun random(min: Int, max: Int): Int = RANDOM.nextInt(min, max)
|
||||||
|
|
||||||
|
private fun <T> weightedRandom(values: List<Pair<T, Double>>): T {
|
||||||
|
val cumulativeWeights = values.scan(0.0) { acc, value -> acc + value.second }
|
||||||
|
var index =
|
||||||
|
cumulativeWeights.binarySearch(random(0.00001, cumulativeWeights.last()))
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
index = -index - 1
|
||||||
|
|
||||||
|
return values[index - 1].first
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun randomSlowDuration(): Int = random(3000, 5000)
|
||||||
|
private fun randomFastDuration(): Int = random(500, 1000)
|
||||||
|
|
||||||
|
private fun randomPlacePoints(): Int = random(10, 20)
|
||||||
|
private fun randomPlaceActions(): Int = random(10, 20)
|
||||||
|
|
||||||
|
private fun randomLatLng(bounds: LatLngBounds): LatLng =
|
||||||
|
LatLng(
|
||||||
|
random(bounds.latitudeSouth, bounds.latitudeNorth),
|
||||||
|
random(bounds.longitudeWest, bounds.longitudeEast)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun randomLatLng(map: MapLibreMap): LatLng =
|
||||||
|
randomLatLng(map.projection.visibleRegion.latLngBounds)
|
||||||
|
|
||||||
|
private fun randomZoom(): Double = random(14.0, 20.0)
|
||||||
|
private fun randomTilt(): Double = random(0.0, 60.0)
|
||||||
|
private fun randomBearing(): Double = random(0.0, 360.0)
|
||||||
|
|
||||||
|
private fun randomAnnotationRemove(): Int = random(4, 8)
|
||||||
|
private fun randomAnnotationAdd(): Int = random(2, 4)
|
||||||
|
|
||||||
|
private fun randomPolyPoints(bounds: LatLngBounds): List<LatLng> {
|
||||||
|
val boundMultiplier = 0.25
|
||||||
|
val lat = random(bounds.latitudeSouth, bounds.latitudeNorth)
|
||||||
|
val lng = random(bounds.longitudeWest, bounds.longitudeEast)
|
||||||
|
val latRange = bounds.latitudeSpan * boundMultiplier / 2.0
|
||||||
|
val lngRange = bounds.longitudeSpan * boundMultiplier / 2.0
|
||||||
|
|
||||||
|
return List(random(3, 15)) {
|
||||||
|
LatLng(
|
||||||
|
lat + random(-latRange, latRange),
|
||||||
|
lng + random(-lngRange, lngRange)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun randomLineWidth(): Float = random(1.0, 4.0).toFloat()
|
||||||
|
|
||||||
|
private fun randomColor(): Int =
|
||||||
|
Color.argb(
|
||||||
|
RANDOM.nextInt(127, 256),
|
||||||
|
RANDOM.nextInt(256),
|
||||||
|
RANDOM.nextInt(256),
|
||||||
|
RANDOM.nextInt(256)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMapReady(maplibreMap: MapLibreMap) {
|
||||||
|
super.onMapReady(maplibreMap)
|
||||||
|
|
||||||
|
this.map = maplibreMap
|
||||||
|
this.mapView = view as MapView
|
||||||
|
|
||||||
|
LOG.info("UserMap seed $RANDOM_SEED")
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun run() {
|
||||||
|
if (USE_ALL_DRAWABLES) {
|
||||||
|
bitmapDrawables =
|
||||||
|
org.maplibre.android.R.drawable::class.java.fields.map { field: Field ->
|
||||||
|
ResourcesCompat.getDrawable(
|
||||||
|
this.resources,
|
||||||
|
field.getInt(null),
|
||||||
|
requireActivity().theme
|
||||||
|
)
|
||||||
|
}.filterIsInstance<BitmapDrawable>()
|
||||||
|
} else {
|
||||||
|
bitmapDrawables = ICONS.map { id ->
|
||||||
|
ResourcesCompat.getDrawable(
|
||||||
|
this.resources,
|
||||||
|
id,
|
||||||
|
requireActivity().theme
|
||||||
|
)!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
// since the random generator was not reset each run will have different values
|
||||||
|
while (true) {
|
||||||
|
runStyleActions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun runStyleActions() {
|
||||||
|
for (style in STYLES.shuffled(RANDOM)) {
|
||||||
|
mapView.setStyleSuspend(style)
|
||||||
|
runCameraActions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun runCameraActions() {
|
||||||
|
// camera actions with different weights
|
||||||
|
// position updates are more frequent
|
||||||
|
val actions = listOf(
|
||||||
|
Pair({ CameraUpdateFactory.newLatLng(randomLatLng(map)) }, 2.0),
|
||||||
|
Pair({ CameraUpdateFactory.zoomTo(randomZoom()) }, 1.0),
|
||||||
|
Pair({ CameraUpdateFactory.tiltTo(randomTilt()) }, 1.0),
|
||||||
|
Pair({ CameraUpdateFactory.bearingTo(randomBearing()) }, 1.0),
|
||||||
|
)
|
||||||
|
|
||||||
|
for (placeCenter in PLACES.shuffled(RANDOM)) {
|
||||||
|
// update all values to simulate a long jump
|
||||||
|
// (generated by the app, searching for a city/street, etc)
|
||||||
|
val cameraPosition = CameraPosition
|
||||||
|
.Builder()
|
||||||
|
.target(placeCenter)
|
||||||
|
.zoom(randomZoom())
|
||||||
|
.tilt(randomTilt())
|
||||||
|
.bearing(randomBearing())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
map.animateCameraSuspend(
|
||||||
|
CameraUpdateFactory.newCameraPosition(cameraPosition),
|
||||||
|
randomSlowDuration()
|
||||||
|
)
|
||||||
|
|
||||||
|
repeat(randomPlacePoints()) {
|
||||||
|
// perform a series of fast camera actions
|
||||||
|
repeat(randomPlaceActions()) {
|
||||||
|
// update each value individually to simulate user interaction
|
||||||
|
map.animateCameraSuspend(weightedRandom(actions)(), randomFastDuration())
|
||||||
|
}
|
||||||
|
|
||||||
|
runAnnotationActions()
|
||||||
|
}
|
||||||
|
|
||||||
|
map.removeAnnotations()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runAnnotationActions() {
|
||||||
|
// remove some annotations
|
||||||
|
repeat(min(map.annotations.size, randomAnnotationRemove())) {
|
||||||
|
map.removeAnnotation(map.annotations.random(RANDOM))
|
||||||
|
}
|
||||||
|
|
||||||
|
// add some annotations
|
||||||
|
val bounds = map.projection.visibleRegion.latLngBounds
|
||||||
|
|
||||||
|
// markers
|
||||||
|
repeat(randomAnnotationAdd()) {
|
||||||
|
// get a random drawable
|
||||||
|
map.addMarker(
|
||||||
|
MarkerOptions()
|
||||||
|
.position(randomLatLng(bounds))
|
||||||
|
.icon(
|
||||||
|
IconFactory.getInstance(requireContext()).fromBitmap(
|
||||||
|
bitmapDrawables
|
||||||
|
.random(RANDOM)
|
||||||
|
.mutate()
|
||||||
|
.apply { setTint(randomColor()) }
|
||||||
|
.toBitmap(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// polylines
|
||||||
|
repeat(randomAnnotationAdd()) {
|
||||||
|
map.addPolyline(
|
||||||
|
PolylineOptions()
|
||||||
|
.color(randomColor())
|
||||||
|
.width(randomLineWidth())
|
||||||
|
.addAll(randomPolyPoints(bounds)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// polygons
|
||||||
|
repeat(randomAnnotationAdd()) {
|
||||||
|
map.addPolygon(
|
||||||
|
PolygonOptions()
|
||||||
|
.fillColor(randomColor())
|
||||||
|
.strokeColor(randomColor())
|
||||||
|
.addAll(randomPolyPoints(bounds)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserMapActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(android.R.id.content, UserMap())
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.maplibre.android.testapp.utils
|
||||||
|
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import org.maplibre.android.camera.CameraUpdate
|
||||||
|
import org.maplibre.android.maps.MapLibreMap
|
||||||
|
import org.maplibre.android.maps.MapLibreMap.CancelableCallback
|
||||||
|
import org.maplibre.android.maps.MapView
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
|
suspend fun MapView.setStyleSuspend(styleUrl: String): Unit =
|
||||||
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
var listener: MapView.OnDidFinishLoadingStyleListener? = null
|
||||||
|
|
||||||
|
var resumed = false
|
||||||
|
listener = MapView.OnDidFinishLoadingStyleListener {
|
||||||
|
if (!resumed) {
|
||||||
|
resumed = true
|
||||||
|
listener?.let { removeOnDidFinishLoadingStyleListener(it) }
|
||||||
|
continuation.resume(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addOnDidFinishLoadingStyleListener(listener)
|
||||||
|
getMapAsync { map -> map.setStyle(styleUrl) }
|
||||||
|
|
||||||
|
continuation.invokeOnCancellation {
|
||||||
|
removeOnDidFinishLoadingStyleListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun MapLibreMap.animateCameraSuspend(cameraUpdate: CameraUpdate, durationMs: Int): Unit =
|
||||||
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
animateCamera(cameraUpdate, durationMs, object : CancelableCallback {
|
||||||
|
var resumed = false
|
||||||
|
|
||||||
|
override fun onCancel() {
|
||||||
|
continuation.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinish() {
|
||||||
|
if (!resumed) {
|
||||||
|
resumed = true
|
||||||
|
continuation.resume(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:ignore="NestedWeights">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/user_map"
|
||||||
|
class="org.maplibre.android.testapp.activity.stability.UserMap"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="0.5" />
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/navigation_map"
|
||||||
|
class="org.maplibre.android.testapp.activity.stability.NavigationMap"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="0.5" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
<string name="description_map_fragment">Showcase MapFragment</string>
|
<string name="description_map_fragment">Showcase MapFragment</string>
|
||||||
<string name="description_map_fragment_support">Showcase SupportMapFragment</string>
|
<string name="description_map_fragment_support">Showcase SupportMapFragment</string>
|
||||||
<string name="description_map_fragment_backstack">Showcase using a Map Fragment with a fragment backstack</string>
|
<string name="description_map_fragment_backstack">Showcase using a Map Fragment with a fragment backstack</string>
|
||||||
|
<string name="description_long_running_maps">Showcase a long running scenario</string>
|
||||||
<string name="description_multimap">Activity with multiple maps on screen</string>
|
<string name="description_multimap">Activity with multiple maps on screen</string>
|
||||||
<string name="description_press_for_marker">Add marker to map on long press</string>
|
<string name="description_press_for_marker">Add marker to map on long press</string>
|
||||||
<string name="description_add_makers_by_json_api">Use JSON API to add markers</string>
|
<string name="description_add_makers_by_json_api">Use JSON API to add markers</string>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<string name="activity_map_fragment">Map Fragment</string>
|
<string name="activity_map_fragment">Map Fragment</string>
|
||||||
<string name="activity_map_fragment_backstack">Backstack Map Fragment</string>
|
<string name="activity_map_fragment_backstack">Backstack Map Fragment</string>
|
||||||
<string name="activity_multimap">Multiple Maps on Screen</string>
|
<string name="activity_multimap">Multiple Maps on Screen</string>
|
||||||
|
<string name="activity_long_running_maps">Long running Maps</string>
|
||||||
<string name="activity_add_bulk_markers">Add Markers In Bulk</string>
|
<string name="activity_add_bulk_markers">Add Markers In Bulk</string>
|
||||||
<string name="activity_animated_symbollayer">Animated SymbolLayer</string>
|
<string name="activity_animated_symbollayer">Animated SymbolLayer</string>
|
||||||
<string name="activity_dynamic_marker">Dynamic Marker</string>
|
<string name="activity_dynamic_marker">Dynamic Marker</string>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ kotlinxCoroutinesAndroid = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-
|
|||||||
maplibreJavaGeoJSON = { group = "org.maplibre.gl", name = "android-sdk-geojson", version.ref = "mapLibreServices" }
|
maplibreJavaGeoJSON = { group = "org.maplibre.gl", name = "android-sdk-geojson", version.ref = "mapLibreServices" }
|
||||||
maplibreGestures = { group = "org.maplibre.gl", name = "maplibre-android-gestures", version = "0.0.4" }
|
maplibreGestures = { group = "org.maplibre.gl", name = "maplibre-android-gestures", version = "0.0.4" }
|
||||||
maplibreJavaTurf = { group = "org.maplibre.gl", name = "android-sdk-turf", version.ref = "mapLibreServices" }
|
maplibreJavaTurf = { group = "org.maplibre.gl", name = "android-sdk-turf", version.ref = "mapLibreServices" }
|
||||||
|
maplibreNavigation = { group = "com.github.maplibre.maplibre-navigation-android", name = "libandroid-navigation", version = "4.0.0" }
|
||||||
|
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
mockito = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" }
|
mockito = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" }
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user