Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package org.groundplatform.android.repository

import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.File
import java.io.IOException
import javax.inject.Inject
Expand All @@ -28,8 +30,6 @@ import org.groundplatform.android.data.uuid.OfflineUuidGenerator
import org.groundplatform.android.system.GeocodingManager
import org.groundplatform.android.ui.map.gms.mog.MogClient
import org.groundplatform.android.ui.map.gms.mog.MogTileDownloader
import org.groundplatform.android.ui.map.gms.mog.getTilePath
import org.groundplatform.android.ui.util.FileUtil
import org.groundplatform.android.util.deleteIfEmpty
import org.groundplatform.android.util.rangeOf
import org.groundplatform.domain.model.imagery.LocalTileSource
Expand All @@ -47,8 +47,8 @@ private const val AREA_NAME_SENSITIVITY = 0.5
class OfflineAreaRepository
@Inject
constructor(
@ApplicationContext private val context: Context,
private val localOfflineAreaStore: LocalOfflineAreaStore,
private val fileUtil: FileUtil,
private val geocodingManager: GeocodingManager,
private val mogClient: MogClient,
private val offlineUuidGenerator: OfflineUuidGenerator,
Expand Down Expand Up @@ -89,7 +89,7 @@ constructor(

// TODO: Generate local tiles path based on source base path.
// Issue URL: https://github.com/google/ground-android/issues/1730
private fun getLocalTileDirectory(): File = File(fileUtil.getFilesDir(), "tiles")
private fun getLocalTileDirectory(): File = File(context.filesDir, "tiles")

private fun getLocalTileSourcePath(): String = getLocalTileDirectory().path

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import com.google.android.gms.maps.model.LatLngBounds
import com.google.android.gms.maps.model.Tile
import com.google.android.gms.maps.model.TileProvider
import com.google.android.gms.maps.model.TileProvider.NO_TILE
import org.groundplatform.android.ui.map.gms.mog.ImageEditor
import org.groundplatform.android.ui.map.gms.mog.toPixelBounds
import org.groundplatform.android.util.image.TileImageTransformer
import org.groundplatform.domain.model.imagery.TileCoordinates
import org.groundplatform.domain.model.imagery.toPixelCoordinate

Expand All @@ -45,7 +45,7 @@ class ClippingTileProvider(
private fun clipToBounds(tileCoords: TileCoordinates, tile: Tile): Tile {
if (tile.data == null) return NO_TILE
val output =
ImageEditor.setTransparentIf(tile.data!!) { _, x, y ->
TileImageTransformer.setTransparentIf(tile.data!!) { _, x, y ->
val pixelCoords = tileCoords.toPixelCoordinate(x, y)
pixelBounds.none { it.contains(pixelCoords) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import org.groundplatform.android.ui.common.AbstractFragment
import org.groundplatform.android.ui.map.Feature
import org.groundplatform.android.ui.map.MapFragment
import org.groundplatform.android.ui.map.gms.features.FeatureManager
import org.groundplatform.android.ui.map.gms.mog.MogSourceProvider.DEFAULT_MOG_MAX_ZOOM
import org.groundplatform.android.ui.map.gms.mog.MogTileProvider
import org.groundplatform.android.util.invert
import org.groundplatform.android.util.systemInsets
Expand Down Expand Up @@ -289,7 +288,7 @@ class GoogleMapsFragment : SupportMapFragment(), MapFragment {
}

private fun addRemoteMogTileOverlay() {
val tileProvider = CachingUpscalingTileProvider(mogTileProvider, DEFAULT_MOG_MAX_ZOOM)
val tileProvider = CachingUpscalingTileProvider(mogTileProvider, mogTileProvider.maxZoom)
addTileOverlay(tileProvider)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import org.groundplatform.domain.model.imagery.MogCollection
import org.groundplatform.domain.model.imagery.MogImageMetadata
import org.groundplatform.domain.model.imagery.MogMetadata
import org.groundplatform.domain.model.imagery.MogSource
import org.groundplatform.domain.model.imagery.MogTile
import org.groundplatform.domain.model.imagery.MogTileMetadata
import org.groundplatform.domain.model.imagery.MogTilesRequest
import org.groundplatform.domain.model.imagery.TileCoordinates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import org.groundplatform.domain.model.imagery.MogSource
object MogSourceProvider {

private const val DEFAULT_MOG_MIN_ZOOM = 8
const val DEFAULT_MOG_MAX_ZOOM = 14
private const val DEFAULT_MOG_MAX_ZOOM = 14

// TODO: Make sub-paths configurable and stop hardcoding here.
// Issue URL: https://github.com/google/ground-android/issues/1730
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.flow
import org.groundplatform.domain.model.imagery.MogTilesRequest
import org.groundplatform.domain.model.imagery.TileCoordinates
import timber.log.Timber

/**
Expand All @@ -43,14 +42,11 @@ class MogTileDownloader(private val client: MogClient, private val outputBasePat
currentCoroutineContext().ensureActive()
val outFile = File(outputBasePath, tile.metadata.tileCoordinates.getTilePath())
outFile.parentFile?.mkdirs()
val gmsTile = tile.toGmsTile()
val data = gmsTile.data!!
val data = tile.getProcessedImageData()
outFile.writeBytes(data)
Timber.d("Saved ${data.size} bytes to ${outFile.path}")
emit(data.size)
}
}
.cancellable()
}

fun TileCoordinates.getTilePath() = "$zoom/$x/$y.jpg"
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.groundplatform.android.ui.map.gms.mog

import android.graphics.Color
import com.google.android.gms.maps.model.Tile
import org.groundplatform.android.util.image.TileImageTransformer
import org.groundplatform.domain.model.imagery.MogTile

/** Returns a Maps SDK [Tile] instance for this [MogTile]. */
fun MogTile.toGmsTile(): Tile = Tile(metadata.width, metadata.height, getProcessedImageData())

/**
* Returns the JFIF image data for this tile, with any pixels matching [metadata.noDataValue] masked
* as fully transparent.
*/
fun MogTile.getProcessedImageData(): ByteArray {
val noData = metadata.noDataValue ?: return buildJfifFile()
val noDataColor = Color.rgb(noData, noData, noData)
return TileImageTransformer.setTransparentIf(buildJfifFile()) { bitmap, x, y ->
bitmap.getPixel(x, y) == noDataColor
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class MogTileProvider(
private val coroutineDispatcher: CoroutineDispatcher,
) : TileProvider {

/** The maximum zoom level supported by the underlying MOG collection. */
val maxZoom: Int
get() = client.collection.maxZoom

override fun getTile(x: Int, y: Int, zoom: Int): Tile? =
runBlocking(coroutineDispatcher) {
val tileCoordinates = TileCoordinates(x, y, zoom)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package org.groundplatform.android.ui.map.gms.mog
import java.io.InputStream
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import org.groundplatform.domain.model.imagery.MogTile
import org.groundplatform.domain.model.imagery.MogTileMetadata
import timber.log.Timber

Expand Down
26 changes: 0 additions & 26 deletions app/src/main/java/org/groundplatform/android/ui/util/FileUtil.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Google LLC
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,19 +14,20 @@
* limitations under the License.
*/

package org.groundplatform.android.ui.map.gms.mog
package org.groundplatform.android.util.image

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.os.Build
import java.io.ByteArrayOutputStream

/** Utility for modifying images. */
object ImageEditor {
/** Utility for transforming map tile images. */
object TileImageTransformer {
/**
* Decodes the provided source image, making all pixels for which the provided lambda returns
* `true` fully transparent (alpha = 0). Returns the modified images as a byte array.
* Decodes the provided source tile image, making all pixels for which the provided lambda returns
* `true` fully transparent (alpha = 0). Returns the modified tile image compressed as a WEBP byte
* array for Maps SDK compatibility.
*/
fun setTransparentIf(
sourceImage: ByteArray,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.groundplatform.android.repository

import android.content.Context
import com.google.common.truth.Truth.assertThat
import java.io.File
import java.io.IOException
Expand All @@ -26,8 +27,6 @@ import org.groundplatform.android.data.local.stores.LocalOfflineAreaStore
import org.groundplatform.android.data.uuid.OfflineUuidGenerator
import org.groundplatform.android.system.GeocodingManager
import org.groundplatform.android.ui.map.gms.mog.MogClient
import org.groundplatform.android.ui.map.gms.mog.getTilePath
import org.groundplatform.android.ui.util.FileUtil
import org.groundplatform.domain.model.imagery.LocalTileSource
import org.groundplatform.domain.model.imagery.MogCollection
import org.groundplatform.domain.model.imagery.MogSource
Expand Down Expand Up @@ -56,8 +55,8 @@ class OfflineAreaRepositoryTest {

@get:Rule val tempFolder: TemporaryFolder = TemporaryFolder()

@Mock private lateinit var context: Context
@Mock private lateinit var localOfflineAreaStore: LocalOfflineAreaStore
@Mock private lateinit var fileUtil: FileUtil
@Mock private lateinit var geocodingManager: GeocodingManager
@Mock private lateinit var offlineUuidGenerator: OfflineUuidGenerator

Expand All @@ -66,12 +65,12 @@ class OfflineAreaRepositoryTest {

@Before
fun setUp() {
whenever(fileUtil.getFilesDir()).thenReturn(tempFolder.root)
whenever(context.filesDir).thenReturn(tempFolder.root)
mogClient = spy(MogClient(TEST_COLLECTION, mock()))
repository =
OfflineAreaRepository(
context,
localOfflineAreaStore,
fileUtil,
geocodingManager,
mogClient,
offlineUuidGenerator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.groundplatform.domain.model.imagery.MogCollection
import org.groundplatform.domain.model.imagery.MogImageMetadata
import org.groundplatform.domain.model.imagery.MogMetadata
import org.groundplatform.domain.model.imagery.MogSource
import org.groundplatform.domain.model.imagery.MogTile
import org.groundplatform.domain.model.imagery.MogTileMetadata
import org.groundplatform.domain.model.imagery.MogTilesRequest
import org.groundplatform.domain.model.imagery.TileCoordinates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.groundplatform.android.ui.map.gms.mog

import com.google.common.truth.Truth.assertThat
import org.groundplatform.domain.model.imagery.maxZoom
import org.junit.Test

class MogSourceProviderTest {
Expand All @@ -41,6 +42,6 @@ class MogSourceProviderTest {

@Test
fun defaultMogSources_maxZoomIsCorrect() {
assertThat(MogSourceProvider.DEFAULT_MOG_MAX_ZOOM).isEqualTo(14)
assertThat(MogSourceProvider.defaultMogSources.maxZoom()).isEqualTo(14)
}
}
Loading
Loading