From d8139f214052490d36e9697a2bc55d9bd8dbe8c1 Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Tue, 30 Sep 2025 12:08:46 +0300 Subject: [PATCH 1/6] hide zeroed values --- app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt index e7ae431..10378c6 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt @@ -228,7 +228,7 @@ fun MapCanvas( val stepLon = coords[step].first val stepOffsetX = SphereMercator.mercateLon(stepLon.toDouble(), level, -TILE_SIZE.toDouble()).toFloat() - offsetX val stepOffsetY = SphereMercator.mercateLat(stepLat.toDouble(), level, -TILE_SIZE.toDouble()).toFloat() - offsetY - if (stepOffsetX >= 0 && stepOffsetY >= 0 && stepOffsetX < size.width && stepOffsetY < size.height) + if (stepOffsetX >= 0 && stepOffsetY >= 0 && stepOffsetX < size.width && stepOffsetY < size.height && heights[step] > 0u) drawRect( color = if (heights[step] > startHeight) Color.Red else Color.Green, size = Size(8F, 8F), From 182f0b29b0aaaef51f578d76eb15fbcdfbee5b57 Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Tue, 30 Sep 2025 12:34:04 +0300 Subject: [PATCH 2/6] automated scale range setting --- app/src/main/java/com/mirenkov/ktheightmap/MainActivity.kt | 4 +++- app/src/main/java/com/mirenkov/ktheightmap/TileDao.kt | 3 +++ app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt | 4 ++++ app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt | 5 ++++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/mirenkov/ktheightmap/MainActivity.kt b/app/src/main/java/com/mirenkov/ktheightmap/MainActivity.kt index ce22994..3db792a 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/MainActivity.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/MainActivity.kt @@ -3,6 +3,7 @@ package com.mirenkov.ktheightmap import android.app.Application import android.content.Intent import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge @@ -84,6 +85,7 @@ class MainActivity : ComponentActivity() { @Composable fun Main(vm: TileViewModel = viewModel()) { var scale by rememberSaveable { vm.scale } + val maxScale by rememberSaveable { vm.maxLevel } val coroutineScope = rememberCoroutineScope() val tileContainer = TileContainer(vm, coroutineScope) KtHeightMapTheme { @@ -111,7 +113,7 @@ fun Main(vm: TileViewModel = viewModel()) { Slider( value = scale, onValueChange = { scale = it }, - valueRange = 1F..14F, + valueRange = 2F..maxScale.toFloat(), modifier = Modifier.align(Alignment.CenterStart) ) } diff --git a/app/src/main/java/com/mirenkov/ktheightmap/TileDao.kt b/app/src/main/java/com/mirenkov/ktheightmap/TileDao.kt index 60f38f2..cd7abfb 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/TileDao.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/TileDao.kt @@ -14,4 +14,7 @@ interface TileDao { @Query("delete from tiles") fun clearTiles() + + @Query("select max(level) from tiles") + fun maxLevel(): Int? } \ No newline at end of file diff --git a/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt b/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt index 5b07937..a5eaf79 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt @@ -21,4 +21,8 @@ class TileRepository(private val tileDao: TileDao) { fun clearTiles() { coroutineScope.launch(Dispatchers.IO) { tileDao.clearTiles() } } + + fun getMaxLevel(): Int { + return coroutineScope.future(Dispatchers.IO){ tileDao.maxLevel() }.join() ?: 1 + } } \ No newline at end of file diff --git a/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt b/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt index e915479..971e59b 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt @@ -2,6 +2,7 @@ package com.mirenkov.ktheightmap import android.app.Application import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel @@ -17,7 +18,8 @@ class TileViewModel(application: Application): ViewModel() { val mapOffsetX = mutableFloatStateOf(-646.65625F) val mapOffsetY = mutableFloatStateOf(-1157.2814F) - val scale = mutableFloatStateOf(1F) + val scale = mutableFloatStateOf(2F) + var maxLevel = mutableIntStateOf(1) var halvedOffsetX: Float? = null var halvedOffsetY: Float? = null @@ -29,5 +31,6 @@ class TileViewModel(application: Application): ViewModel() { val tileDb = TileDB.getInstance(application) val tileDao = tileDb.tileDao() repository = TileRepository(tileDao) + maxLevel.intValue = repository.getMaxLevel() } } \ No newline at end of file From 049311ec9b9abff15503a06506d529b32cde2673 Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Tue, 30 Sep 2025 13:27:56 +0300 Subject: [PATCH 3/6] distance measurer --- .../com/mirenkov/ktheightmap/MapCanvas.kt | 65 +++++++++++++++---- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt index 10378c6..1a3f31f 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt @@ -49,6 +49,7 @@ fun MapCanvas( var pointLat by rememberSaveable { viewModel.rememberedPointLat } var pointLon by rememberSaveable { viewModel.rememberedPointLon } var invokeHeightCalc by remember { mutableStateOf(false) } + val startTargetMeters = 64 * 1000 * 1000 Canvas( modifier = modifier.fillMaxSize() .pointerInput(Unit) { @@ -93,6 +94,10 @@ fun MapCanvas( } } + val targetMeters = startTargetMeters shr level + val targetPixels = (targetMeters).toFloat() / SphereMercator.scaledMetersPerPixel(level) + val measurerHeight = 24F + val centerTileX = (1 + (offsetX + halvedX) / TILE_SIZE).toDouble() val centerTileY = (1 + (offsetY + halvedY) / TILE_SIZE).toDouble() @@ -239,19 +244,31 @@ fun MapCanvas( } // Cursor path - val path = Path() - path.moveTo(halvedX - crossRadius, halvedY) - path.lineTo(halvedX + crossRadius, halvedY) - path.moveTo(halvedX, halvedY - crossRadius) - path.lineTo(halvedX, halvedY + crossRadius) - path.close() + val cursorPath = Path() + with(cursorPath) { + moveTo(halvedX - crossRadius, halvedY) + lineTo(halvedX + crossRadius, halvedY) + moveTo(halvedX, halvedY - crossRadius) + lineTo(halvedX, halvedY + crossRadius) + close() + } // Cursor drawPath( - path, + cursorPath, Color.White, style = Stroke(width = 6F) ) + // Height under cursor + drawText( + textMeasurer = textMeasurer, + text = buildAnnotatedString { + withStyle(SpanStyle(color = Color.White)) { + append("${KhmParser.getHeight(lon, lat, ctx)}m") + } + }, + topLeft = Offset(halvedX + crossRadius, halvedY + crossRadius) + ) // Info box drawRect( @@ -277,15 +294,41 @@ fun MapCanvas( topLeft = latLonOffset ) - // Height under cursor + // Distance measurer path + val measurerPath = Path() + with(measurerPath) { + moveTo((halvedX - targetPixels).toFloat(), 8F) + relativeLineTo(0F, measurerHeight) + relativeMoveTo(targetPixels.toFloat() * 2F, 0F) + relativeLineTo(0F, -measurerHeight) + relativeMoveTo(0F, measurerHeight / 2F) + relativeLineTo(-2F * targetPixels.toFloat(), 0F) + close() + } + + // Distance measurer + drawPath( + measurerPath, + Color.White, + style = Stroke(width = 6F) + ) + + // Distance measurer text drawText( textMeasurer = textMeasurer, text = buildAnnotatedString { - withStyle(SpanStyle(color = Color.White)) { - append("${KhmParser.getHeight(lon, lat, ctx)}m") + withStyle(ParagraphStyle(textAlign = TextAlign.Center)) { + withStyle(SpanStyle(color = Color.White)) { + val text = if (targetMeters >= 100000) + "${targetMeters / 1000}km" + else + "${targetMeters}m" + append(text) + } } }, - topLeft = Offset(halvedX + crossRadius, halvedY + crossRadius) + topLeft = Offset(targetPixels.toFloat() * 2F, measurerHeight), + size = Size(targetPixels.toFloat() * 2F, 48F) ) } } \ No newline at end of file From 004b498ee98fa9727b949a43cb433787ee1d96b8 Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Tue, 30 Sep 2025 14:15:09 +0300 Subject: [PATCH 4/6] distance to point measurer --- .../com/mirenkov/ktheightmap/MapCanvas.kt | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt index 1a3f31f..fd78007 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.ParagraphStyle import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -25,11 +26,27 @@ import androidx.compose.ui.text.drawText import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle +import kotlin.math.abs import kotlin.math.absoluteValue import kotlin.math.floor +import kotlin.math.max +import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt +fun distanceString(targetMeters: Int): AnnotatedString { + return buildAnnotatedString { + withStyle(ParagraphStyle(textAlign = TextAlign.Center)) { + withStyle(SpanStyle(color = Color.White)) { + val text = if (targetMeters >= 100000) + "${targetMeters / 1000}km" + else + "${targetMeters}m" + append(text) + } + } + } +} @OptIn(ExperimentalUnsignedTypes::class) @Composable fun MapCanvas( @@ -189,6 +206,7 @@ fun MapCanvas( } } } + // Placed point and line to center if (pointLat != 0F) { val pointOffsetX = SphereMercator.mercateLon(pointLon.toDouble(), level, -TILE_SIZE.toDouble()).toFloat() - offsetX @@ -203,6 +221,8 @@ fun MapCanvas( start = Offset(pointOffsetX, pointOffsetY), end = Offset(halvedX, halvedY) ) + + val startHeight = KhmParser.getHeight(pointLon, pointLat, ctx) if (pointOffsetX >= 0 && pointOffsetY >= 0 && pointOffsetX < size.width && pointOffsetY < size.height) drawText( @@ -241,6 +261,17 @@ fun MapCanvas( ) } } + val lineLength = sqrt((pointOffsetX - halvedX).pow(2) + (pointOffsetY - halvedY).pow(2)) + val lineMeters = (lineLength * SphereMercator.scaledMetersPerPixel(level + 1)).toInt() + drawText( + textMeasurer = textMeasurer, + text = distanceString(lineMeters), + size = Size(160F, 48F), + topLeft = Offset( + halvedX, + halvedY - 80F + ) + ) } // Cursor path @@ -316,17 +347,7 @@ fun MapCanvas( // Distance measurer text drawText( textMeasurer = textMeasurer, - text = buildAnnotatedString { - withStyle(ParagraphStyle(textAlign = TextAlign.Center)) { - withStyle(SpanStyle(color = Color.White)) { - val text = if (targetMeters >= 100000) - "${targetMeters / 1000}km" - else - "${targetMeters}m" - append(text) - } - } - }, + text = distanceString(targetMeters), topLeft = Offset(targetPixels.toFloat() * 2F, measurerHeight), size = Size(targetPixels.toFloat() * 2F, 48F) ) From 3510da0428e7f038b5da32fc267efd405bed7c38 Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Tue, 30 Sep 2025 14:27:50 +0300 Subject: [PATCH 5/6] prettifying --- .../com/mirenkov/ktheightmap/MapCanvas.kt | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt index fd78007..f8494bf 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt @@ -39,9 +39,9 @@ fun distanceString(targetMeters: Int): AnnotatedString { withStyle(ParagraphStyle(textAlign = TextAlign.Center)) { withStyle(SpanStyle(color = Color.White)) { val text = if (targetMeters >= 100000) - "${targetMeters / 1000}km" + "↔${targetMeters / 1000}km" else - "${targetMeters}m" + "↔${targetMeters}m" append(text) } } @@ -222,13 +222,12 @@ fun MapCanvas( end = Offset(halvedX, halvedY) ) - val startHeight = KhmParser.getHeight(pointLon, pointLat, ctx) if (pointOffsetX >= 0 && pointOffsetY >= 0 && pointOffsetX < size.width && pointOffsetY < size.height) drawText( textMeasurer = textMeasurer, text = buildAnnotatedString { withStyle(SpanStyle(color = Color.White)) { - append("${startHeight}m") + append("↑${startHeight}m") } }, topLeft = Offset(pointOffsetX, pointOffsetY - 32) ) @@ -268,7 +267,7 @@ fun MapCanvas( text = distanceString(lineMeters), size = Size(160F, 48F), topLeft = Offset( - halvedX, + halvedX - 80F, halvedY - 80F ) ) @@ -291,15 +290,19 @@ fun MapCanvas( style = Stroke(width = 6F) ) // Height under cursor - drawText( - textMeasurer = textMeasurer, - text = buildAnnotatedString { - withStyle(SpanStyle(color = Color.White)) { - append("${KhmParser.getHeight(lon, lat, ctx)}m") - } - }, - topLeft = Offset(halvedX + crossRadius, halvedY + crossRadius) - ) + KhmParser.getHeight(lon, lat, ctx).let { + if (it < 1u) return@let + drawText( + textMeasurer = textMeasurer, + text = buildAnnotatedString { + withStyle(SpanStyle(color = Color.White)) { + append("↑${it}m") + } + }, + topLeft = Offset(halvedX, halvedY + crossRadius) + ) + } + // Info box drawRect( @@ -307,7 +310,6 @@ fun MapCanvas( size = latLonSize, topLeft = latLonOffset ) - // Info box content drawText( textMeasurer = textMeasurer, @@ -336,14 +338,12 @@ fun MapCanvas( relativeLineTo(-2F * targetPixels.toFloat(), 0F) close() } - // Distance measurer drawPath( measurerPath, Color.White, style = Stroke(width = 6F) ) - // Distance measurer text drawText( textMeasurer = textMeasurer, From b70b98feb62d522866d782f8adcd1eb794137d9b Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Tue, 30 Sep 2025 14:44:28 +0300 Subject: [PATCH 6/6] fixed no data crashes --- app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt | 4 +++- app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt | 3 --- app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt | 2 +- app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt b/app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt index eae616e..6081679 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt @@ -63,6 +63,8 @@ class KhmParser { } fun getHeight(lon: Float, lat: Float, ctx: Context): UShort { + if (!ctx.getFileStreamPath(HEIGHT_FILE).exists()) + return 0u.toUShort() val dis = DataInputStream(ctx.openFileInput(HEIGHT_FILE)) dis.use { val header = readHeader(dis) @@ -80,7 +82,7 @@ class KhmParser { @OptIn(ExperimentalUnsignedTypes::class) fun getHeightsMul(ctx: Context, coords: Array>): Pair>> { - if (coords.isEmpty()) return Pair(ushortArrayOf(), coords) + if (coords.isEmpty() || !ctx.getFileStreamPath(HEIGHT_FILE).exists()) return Pair(ushortArrayOf(), coords) val dis = DataInputStream(ctx.openFileInput(HEIGHT_FILE)) dis.use { val header = readHeader(dis) diff --git a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt index f8494bf..22d8f9c 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt @@ -26,11 +26,8 @@ import androidx.compose.ui.text.drawText import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle -import kotlin.math.abs import kotlin.math.absoluteValue import kotlin.math.floor -import kotlin.math.max -import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt diff --git a/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt b/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt index a5eaf79..028d931 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt @@ -23,6 +23,6 @@ class TileRepository(private val tileDao: TileDao) { } fun getMaxLevel(): Int { - return coroutineScope.future(Dispatchers.IO){ tileDao.maxLevel() }.join() ?: 1 + return coroutineScope.future(Dispatchers.IO){ tileDao.maxLevel() }.join() ?: 2 } } \ No newline at end of file diff --git a/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt b/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt index 971e59b..b1dcaf5 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt @@ -19,7 +19,7 @@ class TileViewModel(application: Application): ViewModel() { val mapOffsetX = mutableFloatStateOf(-646.65625F) val mapOffsetY = mutableFloatStateOf(-1157.2814F) val scale = mutableFloatStateOf(2F) - var maxLevel = mutableIntStateOf(1) + var maxLevel = mutableIntStateOf(2) var halvedOffsetX: Float? = null var halvedOffsetY: Float? = null