Skip to content

Commit

Permalink
Estimate offline map imagery download size and disable download for l…
Browse files Browse the repository at this point in the history
…arger areas (#1846)
  • Loading branch information
gino-m authored Sep 10, 2023
1 parent 012ccbe commit bbb40fd
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 15 deletions.
2 changes: 1 addition & 1 deletion ground/src/main/java/com/google/android/ground/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object Config {

// Local db settings.
// TODO(#128): Reset version to 1 before releasing.
const val DB_VERSION = 103
const val DB_VERSION = 104
const val DB_NAME = "ground.db"

// Firebase Cloud Firestore settings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,10 @@ constructor(
private fun getFirstTileSourceUrl() =
surveyRepository.activeSurvey?.tileSources?.firstOrNull()?.url
?: error("Survey has no tile sources")

suspend fun estimateSizeOnDisk(bounds: Bounds): Int {
val client = getMogClient()
val requests = client.buildTilesRequests(bounds.toGoogleMapsObject())
return requests.sumOf { it.totalBytes }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package com.google.android.ground.ui.offlinebasemap.selector

import android.content.res.Resources
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.android.ground.R
import com.google.android.ground.coroutines.IoDispatcher
import com.google.android.ground.model.imagery.TileSource
import com.google.android.ground.repository.LocationOfInterestRepository
Expand All @@ -30,11 +32,16 @@ import com.google.android.ground.ui.common.BaseMapViewModel
import com.google.android.ground.ui.common.Navigator
import com.google.android.ground.ui.common.SharedViewModel
import com.google.android.ground.ui.map.Bounds
import com.google.android.ground.ui.map.CameraPosition
import com.google.android.ground.ui.map.Map
import javax.inject.Inject
import kotlin.math.ceil
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.launch

private const val MIN_DOWNLOAD_ZOOM_LEVEL = 9
private const val MAX_AREA_DOWNLOAD_SIZE_MB = 50

/** States and behaviors of Map UI used to select areas for download and viewing offline. */
@SharedViewModel
class OfflineAreaSelectorViewModel
Expand All @@ -43,6 +50,7 @@ internal constructor(
private val offlineAreaRepository: OfflineAreaRepository,
private val navigator: Navigator,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val resources: Resources,
locationManager: LocationManager,
surveyRepository: SurveyRepository,
mapStateRepository: MapStateRepository,
Expand All @@ -60,16 +68,15 @@ internal constructor(
locationOfInterestRepository,
ioDispatcher
) {
enum class DownloadMessage {
STARTED,
FAILURE
}

val tileSources: List<TileSource>
private var viewport: Bounds? = null
val isDownloadProgressVisible = MutableLiveData(false)
val downloadProgressMax = MutableLiveData(0)
val downloadProgress = MutableLiveData(0)
val sizeOnDisk = MutableLiveData<String>(null)
val visibleBottomTextViewId = MutableLiveData<Int>(null)
val downloadButtonEnabled = MutableLiveData(false)

init {
tileSources = surveyRepository.activeSurvey!!.tileSources
Expand All @@ -84,11 +91,11 @@ internal constructor(
isDownloadProgressVisible.value = true
downloadProgress.value = 0
viewModelScope.launch(ioDispatcher) {
offlineAreaRepository.downloadTiles(viewport!!).collect { (byteDownloaded, totalBytes) ->
offlineAreaRepository.downloadTiles(viewport!!).collect { (bytesDownloaded, totalBytes) ->
// Set total bytes / max value on first iteration.
if (downloadProgressMax.value != totalBytes) downloadProgressMax.postValue(totalBytes)
// Add number of bytes downloaded to progress.
downloadProgress.postValue(byteDownloaded)
downloadProgress.postValue(bytesDownloaded)
}
isDownloadProgressVisible.postValue(false)
navigator.navigateUp()
Expand All @@ -103,4 +110,43 @@ internal constructor(
tileSources.forEach { map.addTileOverlay(it) }
disposeOnClear(cameraBoundUpdates.subscribe { viewport = it })
}

override fun onMapCameraMoved(newCameraPosition: CameraPosition) {
super.onMapCameraMoved(newCameraPosition)
val bounds = newCameraPosition.bounds
val zoomLevel = newCameraPosition.zoomLevel
if (bounds == null || zoomLevel == null) return
if (zoomLevel < MIN_DOWNLOAD_ZOOM_LEVEL) {
onLargeAreaSelected()
return
}

viewModelScope.launch(ioDispatcher) { updateDownloadSize(bounds) }
}

private fun onStartEstimatingDownloadSize() {
sizeOnDisk.postValue(resources.getString(R.string.offline_area_size_loading_symbol))
visibleBottomTextViewId.postValue(R.id.size_on_disk_text_view)
}

private suspend fun updateDownloadSize(bounds: Bounds) {
onStartEstimatingDownloadSize()
val sizeInMb = offlineAreaRepository.estimateSizeOnDisk(bounds) / (1024f * 1024f)
if (sizeInMb > MAX_AREA_DOWNLOAD_SIZE_MB) {
onLargeAreaSelected()
} else {
onDownloadableAreaSelected(sizeInMb)
}
}

private fun onDownloadableAreaSelected(sizeInMb: Float) {
val sizeString = if (sizeInMb < 1f) "<1" else ceil(sizeInMb).toInt().toString()
sizeOnDisk.postValue(sizeString)
downloadButtonEnabled.postValue(true)
}

private fun onLargeAreaSelected() {
visibleBottomTextViewId.postValue(R.id.area_too_large_text_view)
downloadButtonEnabled.postValue(false)
}
}
47 changes: 39 additions & 8 deletions ground/src/main/res/layout/offline_base_map_selector_frag.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>
<import type="com.google.android.ground.R" />
<import type="android.view.View" />
<variable
name="viewModel"
type="com.google.android.ground.ui.offlinebasemap.selector.OfflineAreaSelectorViewModel" />
Expand Down Expand Up @@ -71,14 +73,6 @@
android:alpha="0.4"
app:layout_constraintTop_toBottomOf="@+id/offline_area_selector_toolbar" />

<View
android:id="@+id/bottom_mask"
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@color/blackMapOverlay"
android:alpha="0.4"
app:layout_constraintBottom_toTopOf="@id/button_buttons" />

<View
android:id="@+id/right_mask"
android:layout_width="24dp"
Expand Down Expand Up @@ -109,6 +103,43 @@
app:layout_constraintLeft_toRightOf="@id/left_mask"
app:layout_constraintRight_toLeftOf="@id/right_mask"/>

<FrameLayout
android:id="@+id/bottom_mask"
android:layout_width="match_parent"
android:layout_height="80dp"
app:layout_constraintBottom_toTopOf="@id/button_buttons">

<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/blackMapOverlay"
android:alpha="0.4"/>

<TextView
android:id="@+id/size_on_disk_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAlignment="center"
android:textSize="14sp"
android:textColor="@color/textOverMap"
android:layout_marginHorizontal="64dp"
android:visibility="@{viewModel.visibleBottomTextViewId == R.id.size_on_disk_text_view ? View.VISIBLE : View.GONE}"
android:text="@{@string/selected_offline_area_size(viewModel.sizeOnDisk)}"/>

<TextView
android:id="@+id/area_too_large_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAlignment="center"
android:textSize="14sp"
android:textColor="@color/textOverMap"
android:layout_marginHorizontal="64dp"
android:visibility="@{viewModel.visibleBottomTextViewId == R.id.area_too_large_text_view ? View.VISIBLE : View.GONE}"
android:text="@string/selected_offline_area_too_large"/>
</FrameLayout>

<LinearLayout
android:id="@+id/button_buttons"
android:layout_width="match_parent"
Expand Down
1 change: 1 addition & 0 deletions ground/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<color name="colorMapAccent">#ff9131</color>
<color name="clusterColor">#6DDD81</color>
<color name="polyLineColor">#55ffffff</color>
<color name="textOverMap">#FCFDF7</color>
<color name="blackMapOverlay">#000000</color>
<color name="lightBackground">#EDEEE9</color>

Expand Down
3 changes: 3 additions & 0 deletions ground/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@
<string name="contact_survey_organizer_to_obtain_access">Contact your system administrator to request access</string>
<string name="close_app">Close app</string>
<string name="warning_sign_out">Warning: If you sign out, all unsaved data will be lost </string>
<string name="selected_offline_area_size">The selected area may take up to %s\u00A0MB of space on your device</string>
<string name="selected_offline_area_too_large">Zoom in and select a smaller area to download to your device</string>
<string name="offline_area_size_loading_symbol">…</string>
<string name="offline_area_select_cancel_button">Cancel</string>
<string name="offline_area_selector_title">Download this area?</string>
</resources>

0 comments on commit bbb40fd

Please sign in to comment.