diff --git a/app/build.gradle b/app/build.gradle index 431c45d..5b906fe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,7 +78,7 @@ android { } lint { - disable 'GradleDependency' + disable 'GradleDependency', 'AndroidGradlePluginVersion' absolutePaths = false abortOnError = true ignoreWarnings = false diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5739b85..99947f6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,9 +16,12 @@ android:supportsRtl="true" android:theme="@style/AppTheme"> + diff --git a/app/src/main/java/ru/dgis/sdk/demo/MainActivity.kt b/app/src/main/java/ru/dgis/sdk/demo/MainActivity.kt index 6af9687..93fce1d 100644 --- a/app/src/main/java/ru/dgis/sdk/demo/MainActivity.kt +++ b/app/src/main/java/ru/dgis/sdk/demo/MainActivity.kt @@ -73,6 +73,10 @@ class MainActivity : AppCompatActivity() { Page("Simulate navigation") { val intent = Intent(this@MainActivity, SimulateNavigationActivity::class.java) startActivity(intent) + }, + Page("Map background") { + val intent = Intent(this@MainActivity, MapBackgroundActivity::class.java) + startActivity(intent) } ) diff --git a/app/src/main/java/ru/dgis/sdk/demo/MapBackgroundActivity.kt b/app/src/main/java/ru/dgis/sdk/demo/MapBackgroundActivity.kt new file mode 100644 index 0000000..65cbb86 --- /dev/null +++ b/app/src/main/java/ru/dgis/sdk/demo/MapBackgroundActivity.kt @@ -0,0 +1,126 @@ +package ru.dgis.sdk.demo + +import android.os.Bundle +import android.util.Log +import android.view.Gravity +import android.widget.LinearLayout +import android.widget.PopupWindow +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull +import ru.dgis.sdk.Duration +import ru.dgis.sdk.coordinates.GeoPoint +import ru.dgis.sdk.demo.databinding.ActivityMapBackgroundBinding +import ru.dgis.sdk.map.BearingSource +import ru.dgis.sdk.map.CameraAnimatedMoveResult +import ru.dgis.sdk.map.CameraPosition +import ru.dgis.sdk.map.Map +import ru.dgis.sdk.map.MapView +import ru.dgis.sdk.map.MyLocationController +import ru.dgis.sdk.map.MyLocationMapObjectSource +import ru.dgis.sdk.map.Zoom +import ru.dgis.sdk.map.toBitmap +import ru.dgis.sdk.positioning.DesiredAccuracy + +/** + * Demonstrates how to create a snapshot of a map drawn offscreen. This is useful in scenarios + * where a visual representation of a map is required without displaying the MapView in the UI hierarchy. + * + * Prerequisites: + * - Location permissions must be granted at the start of the application. + * - For emulator testing, ensure that a realistic geographical location is simulated. + */ +class MapBackgroundActivity : AppCompatActivity() { + private val binding by lazy { ActivityMapBackgroundBinding.inflate(layoutInflater) } + private val locationSource by lazy { + MyLocationMapObjectSource( + application.sdkContext, + MyLocationController(BearingSource.AUTO) + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContentView(binding.root) + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) + insets + } + + (application as Application).locationSource.setDesiredAccuracy(DesiredAccuracy.HIGH) + + lifecycleScope.launch { + withContext(Dispatchers.Default) { + val screenHeight = resources.displayMetrics.heightPixels + val screenWidth = resources.displayMetrics.widthPixels + + // Prepare an offscreen MapView inside a PopupWindow to capture the map snapshot. + val mapView = MapView(this@MapBackgroundActivity).apply { + layoutParams = LinearLayout.LayoutParams( + screenWidth, + (screenHeight * 0.25).toInt() + ) + } + val popup = PopupWindow( + mapView, + screenWidth, + (screenHeight * 0.25).toInt() + ).also { + it.isClippingEnabled = false // this is crucial, allows overflow outside screen bounds + } + + // Changing dispatcher here since it's mandatory to work with UI from main thread + withContext(Dispatchers.Main.immediate) { + popup.showAtLocation(binding.main, Gravity.NO_GRAVITY, -screenWidth, -screenHeight) + mapView.getMapAsync { map -> + map.addSource(locationSource) + lifecycleScope.launch { + withContext(Dispatchers.Default) { + takeSnapshotWithGeo(map, mapView, popup) + } + } + } + } + } + } + } + + /** + * Takes a snapshot of the map centered on the last known location. If successful, + * the image is set to an ImageView and the PopupWindow is dismissed. + */ + private suspend fun takeSnapshotWithGeo(map: Map, mapView: MapView, popup: PopupWindow) { + val timeoutMillis = 20000L + val snapshotResult = withTimeoutOrNull(timeoutMillis) { + var snapshotTaken = false + while (!snapshotTaken) { + application.locationService.lastLocation?.let { location -> + val cameraPosition = CameraPosition(GeoPoint(location.latitude, location.longitude), Zoom(16.2f)) + map.camera.move(cameraPosition, Duration.ZERO).onResult { result -> + if (result == CameraAnimatedMoveResult.FINISHED) { + mapView.takeSnapshot().onResult { imgData -> + binding.mapSnapshotContainer.setImageBitmap(imgData.toBitmap()) + popup.dismiss() + snapshotTaken = true + } + } + } + } + if (!snapshotTaken) delay(200) + } + } + + if (snapshotResult == null) { + Log.w("MAP", "Failed to take a snapshot within the timeout period.") + } + } +} diff --git a/app/src/main/res/layout/activity_map_background.xml b/app/src/main/res/layout/activity_map_background.xml new file mode 100644 index 0000000..815f56b --- /dev/null +++ b/app/src/main/res/layout/activity_map_background.xml @@ -0,0 +1,20 @@ + + + + + +