Height on distance

This commit is contained in:
Alexey 2025-09-23 12:51:23 +03:00
commit 470535cac8
5 changed files with 99 additions and 39 deletions

View file

@ -76,5 +76,13 @@ class KhmParser {
return height return height
} }
} }
fun getLonPerValue(): Float {
return header?.lonPerValue ?: 0F
}
fun getLatPerValue(): Float {
return header?.latPerValue ?: 0F
}
} }
} }

View file

@ -3,7 +3,6 @@ package com.mirenkov.ktheightmap
import android.app.Application import android.app.Application
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
@ -32,7 +31,6 @@ import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@ -43,7 +41,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.toUpperCase
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -122,12 +119,13 @@ fun Main(vm: TileViewModel = viewModel()) {
} }
} }
@Suppress("VariableNeverRead", "AssignedValueIsNeverRead")
@Composable @Composable
fun ToolButton(viewModel: TileViewModel) { fun ToolButton(viewModel: TileViewModel) {
val context = LocalContext.current val context = LocalContext.current
var expanded by rememberSaveable{ mutableStateOf(false) } var expanded by rememberSaveable{ mutableStateOf(false) }
var debug by rememberSaveable{ viewModel.debug } var debug by rememberSaveable{ viewModel.debug }
var logRequested by remember{ viewModel.logRequested } var pointRequested by remember{ viewModel.pointRequested }
val dialogShown = rememberSaveable{ mutableStateOf(false) } val dialogShown = rememberSaveable{ mutableStateOf(false) }
FloatingActionButton( FloatingActionButton(
onClick = { onClick = {
@ -147,9 +145,9 @@ fun ToolButton(viewModel: TileViewModel) {
} }
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text("Log") }, text = { Text("Place point") },
onClick = { onClick = {
logRequested = true pointRequested = true
expanded = false expanded = false
} }
) )
@ -175,7 +173,6 @@ fun ToolButton(viewModel: TileViewModel) {
@Suppress("VariableNeverRead", "AssignedValueIsNeverRead") @Suppress("VariableNeverRead", "AssignedValueIsNeverRead")
@Composable @Composable
fun SetLocationDialog(vm: TileViewModel, dialogShown: MutableState<Boolean>) { fun SetLocationDialog(vm: TileViewModel, dialogShown: MutableState<Boolean>) {
val ctx = LocalContext.current
var showLocationDialog by dialogShown var showLocationDialog by dialogShown
if (showLocationDialog) { if (showLocationDialog) {
var latitudeText by remember { vm.latitudeText } var latitudeText by remember { vm.latitudeText }
@ -210,12 +207,8 @@ fun SetLocationDialog(vm: TileViewModel, dialogShown: MutableState<Boolean>) {
confirmButton = { TextButton(onClick = { confirmButton = { TextButton(onClick = {
val latitude = latitudeText.text.toDoubleOrNull() ?: 0.0 val latitude = latitudeText.text.toDoubleOrNull() ?: 0.0
val longitude = longitudeText.text.toDoubleOrNull() ?: 0.0 val longitude = longitudeText.text.toDoubleOrNull() ?: 0.0
Log.i(TAG, "Lat: %.6f, Lon: %.6f".format(latitude, longitude))
Log.i(TAG, "X = %.6f".format(SphereMercator.mercateLon(longitude, vm.scale.floatValue.toInt(), -TILE_SIZE.toDouble())))
Log.i(TAG, "Y = %.6f".format(SphereMercator.mercateLat(latitude, vm.scale.floatValue.toInt(), -TILE_SIZE.toDouble())))
offsetX = SphereMercator.mercateLon(longitude, vm.scale.floatValue.toInt(), -vm.halvedOffsetX!!.toDouble() - TILE_SIZE).toFloat() offsetX = SphereMercator.mercateLon(longitude, vm.scale.floatValue.toInt(), -vm.halvedOffsetX!!.toDouble() - TILE_SIZE).toFloat()
offsetY = SphereMercator.mercateLat(latitude, vm.scale.floatValue.toInt(), -vm.halvedOffsetY!!.toDouble() - TILE_SIZE).toFloat() offsetY = SphereMercator.mercateLat(latitude, vm.scale.floatValue.toInt(), -vm.halvedOffsetY!!.toDouble() - TILE_SIZE).toFloat()
Log.i(TAG, "%d".format(KhmParser.getHeight(longitude.toFloat(), latitude.toFloat(), ctx).toInt()))
}) { }) {
Text("Confirm".uppercase()) Text("Confirm".uppercase())
} }, } },

View file

@ -1,6 +1,5 @@
package com.mirenkov.ktheightmap package com.mirenkov.ktheightmap
import android.util.Log
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -25,6 +24,10 @@ import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.pow
import kotlin.math.sqrt
@Composable @Composable
fun MapCanvas( fun MapCanvas(
@ -40,7 +43,9 @@ fun MapCanvas(
val scale by rememberSaveable { viewModel.scale } val scale by rememberSaveable { viewModel.scale }
val textMeasurer = rememberTextMeasurer() val textMeasurer = rememberTextMeasurer()
val debug by rememberSaveable { viewModel.debug } val debug by rememberSaveable { viewModel.debug }
var logRequested by rememberSaveable { viewModel.logRequested } var pointRequested by rememberSaveable { viewModel.pointRequested }
var pointLat by rememberSaveable { viewModel.rememberedPointLat }
var pointLon by rememberSaveable { viewModel.rememberedPointLon }
Canvas( Canvas(
modifier = modifier.fillMaxSize() modifier = modifier.fillMaxSize()
.pointerInput(Unit) { .pointerInput(Unit) {
@ -57,16 +62,6 @@ fun MapCanvas(
val halvedX = viewModel.halvedOffsetX!! val halvedX = viewModel.halvedOffsetX!!
val halvedY = viewModel.halvedOffsetY!! val halvedY = viewModel.halvedOffsetY!!
drawRect(
color = backColor,
size = size
)
if (logRequested) {
Log.i(TAG, "Offset: %.6f, %.6f".format(offsetX, offsetY))
logRequested = false
}
val oldLevel = tileContainer.getLevel() val oldLevel = tileContainer.getLevel()
val level = scale.toInt() val level = scale.toInt()
val levelDiff = level - oldLevel val levelDiff = level - oldLevel
@ -86,12 +81,27 @@ fun MapCanvas(
offsetY += halvedY + TILE_SIZE offsetY += halvedY + TILE_SIZE
} }
} }
val tileOffsetX = (offsetX / TILE_SIZE).toInt()
val tileOffsetY = (offsetY / TILE_SIZE).toInt()
val centerTileX = (1 + (offsetX + halvedX) / TILE_SIZE).toDouble() val centerTileX = (1 + (offsetX + halvedX) / TILE_SIZE).toDouble()
val centerTileY = (1 + (offsetY + halvedY) / TILE_SIZE).toDouble() val centerTileY = (1 + (offsetY + halvedY) / TILE_SIZE).toDouble()
val lon = SphereMercator.mercateX(centerTileX, level).toFloat()
val lat = SphereMercator.mercateY(centerTileY, level).toFloat()
if (pointRequested) {
if (pointLat == 0F) {
pointLat = lat
pointLon = lon
} else {
pointLat = 0F
pointLon = 0F
}
pointRequested = false
}
val tileOffsetX = (offsetX / TILE_SIZE).toInt()
val tileOffsetY = (offsetY / TILE_SIZE).toInt()
val strippedOffsetX = offsetX % TILE_SIZE val strippedOffsetX = offsetX % TILE_SIZE
val strippedOffsetY = offsetY % TILE_SIZE val strippedOffsetY = offsetY % TILE_SIZE
@ -110,6 +120,12 @@ fun MapCanvas(
val latLonSize = Size(216F, 96F + additionalSize) val latLonSize = Size(216F, 96F + additionalSize)
val latLonOffset = Offset(16F, 16F) val latLonOffset = Offset(16F, 16F)
// Background
drawRect(
color = backColor,
size = size
)
for (cellX in 0 .. gridWidth + 2) { for (cellX in 0 .. gridWidth + 2) {
val tileX = tileOffsetX + cellX val tileX = tileOffsetX + cellX
val localOffsetX = TILE_SIZE * (cellX - 1) val localOffsetX = TILE_SIZE * (cellX - 1)
@ -121,6 +137,7 @@ fun MapCanvas(
val bitmap = tile.toBitmap() val bitmap = tile.toBitmap()
val totalOffset = Offset(localOffsetX, localOffsetY) - offset val totalOffset = Offset(localOffsetX, localOffsetY) - offset
// Tile
bitmap?.let { bitmap?.let {
val imageBitmap = bitmap.asImageBitmap() val imageBitmap = bitmap.asImageBitmap()
drawImage( drawImage(
@ -129,6 +146,7 @@ fun MapCanvas(
) )
} }
// Debug grid
if (debug) { if (debug) {
drawRect( drawRect(
color = gridColor, color = gridColor,
@ -153,6 +171,51 @@ fun MapCanvas(
) )
} }
// Placed point and line to center
if (pointLat != 0F) {
val pointOffsetX = SphereMercator.mercateLon(pointLon.toDouble(), level, -TILE_SIZE.toDouble()).toFloat() - offsetX
val pointOffsetY = SphereMercator.mercateLat(pointLat.toDouble(), level, -TILE_SIZE.toDouble()).toFloat() - offsetY
drawRect(
color = gridColor,
size = Size(8F, 8F),
topLeft = Offset(pointOffsetX - 4, pointOffsetY - 4)
)
drawLine(
color = gridColor,
start = Offset(pointOffsetX, pointOffsetY),
end = Offset(halvedX, halvedY)
)
val startHeight = KhmParser.getHeight(pointLon, pointLat, ctx)
drawText(
textMeasurer = textMeasurer,
text = buildAnnotatedString { withStyle(SpanStyle(color = Color.White)) {
append("${startHeight}m")
} },
topLeft = Offset(pointOffsetX, pointOffsetY - 32)
)
val latPV = KhmParser.getLatPerValue()
val lonPV = KhmParser.getLonPerValue()
val valueDistance = sqrt(latPV.pow(2) + lonPV.pow(2))
val latDiff = lat - pointLat
val lonDiff = lon - pointLon
val distance = sqrt((latDiff).pow(2) + (lonDiff).pow(2))
val valuesCount = floor(distance / valueDistance).toInt()
for (step in 0 .. valuesCount) {
val interCoef = 1F - step.toFloat() / valuesCount
val stepLat = lat - latDiff * interCoef
val stepLon = lon - lonDiff * interCoef
val stepOffsetX = SphereMercator.mercateLon(stepLon.toDouble(), level, -TILE_SIZE.toDouble()).toFloat() - offsetX
val stepOffsetY = SphereMercator.mercateLat(stepLat.toDouble(), level, -TILE_SIZE.toDouble()).toFloat() - offsetY
val height = KhmParser.getHeight(stepLon, stepLat, ctx)
drawRect(
color = if (height > startHeight) Color.Red else Color.Green,
size = Size(8F, 8F),
topLeft = Offset(stepOffsetX - 4, stepOffsetY - 4)
)
}
}
// Cursor path
val path = Path() val path = Path()
path.moveTo(halvedX - crossRadius, halvedY) path.moveTo(halvedX - crossRadius, halvedY)
path.lineTo(halvedX + crossRadius, halvedY) path.lineTo(halvedX + crossRadius, halvedY)
@ -160,27 +223,27 @@ fun MapCanvas(
path.lineTo(halvedX, halvedY + crossRadius) path.lineTo(halvedX, halvedY + crossRadius)
path.close() path.close()
// Cursor
drawPath( drawPath(
path, path,
Color.White, Color.White,
style = Stroke(width = 6F) style = Stroke(width = 6F)
) )
// Info box
drawRect( drawRect(
color = backColor, color = backColor,
size = latLonSize, size = latLonSize,
topLeft = latLonOffset topLeft = latLonOffset
) )
val lon = SphereMercator.mercateX(centerTileX, level) // Info box content
val lat = SphereMercator.mercateY(centerTileY, level)
drawText( drawText(
textMeasurer = textMeasurer, textMeasurer = textMeasurer,
text = buildAnnotatedString { text = buildAnnotatedString {
withStyle(ParagraphStyle(textAlign = TextAlign.Center)) { withStyle(ParagraphStyle(textAlign = TextAlign.Center)) {
withStyle(SpanStyle(color = gridColor)) { withStyle(SpanStyle(color = gridColor)) {
append("%.6f\n%.6f".format(lon, lat)) append("%.6f\n%.6f".format(lat, lon))
if (debug) { if (debug) {
append("\n%.0f\n%.0f".format(offsetX, offsetY)) append("\n%.0f\n%.0f".format(offsetX, offsetY))
} }
@ -191,15 +254,12 @@ fun MapCanvas(
topLeft = latLonOffset topLeft = latLonOffset
) )
// Height under cursor
drawText( drawText(
textMeasurer = textMeasurer, textMeasurer = textMeasurer,
text = buildAnnotatedString { text = buildAnnotatedString {
withStyle(SpanStyle(color = Color.White)) { withStyle(SpanStyle(color = Color.White)) {
append( append("${KhmParser.getHeight(lon, lat, ctx)}m")
"%dm".format(
KhmParser.getHeight(lon.toFloat(), lat.toFloat(), ctx).toInt()
)
)
} }
}, },
topLeft = Offset(halvedX + crossRadius, halvedY + crossRadius) topLeft = Offset(halvedX + crossRadius, halvedY + crossRadius)

View file

@ -89,10 +89,7 @@ fun SettingsMain(vm: TileViewModel, launcher: ActivityResultLauncher<DocumentFil
val t = getTile(it.x, it.y, it.level) val t = getTile(it.x, it.y, it.level)
if (t == null) { if (t == null) {
pushTile(it) pushTile(it)
Log.i(TAG, "pushed to db: %d, %d, %d".format(it.level, it.x, it.y))
} }
else
Log.i(TAG, "found in db: %d, %d, %d".format(t.level, t.x, t.y))
} }
} }
} }, } },
@ -124,7 +121,6 @@ suspend fun processZip(filePath: String?): List<Tile> = coroutineScope {
if (entry == null) if (entry == null)
break break
} }
Log.i(TAG, "got list")
} }
} }
return@coroutineScope list return@coroutineScope list

View file

@ -10,7 +10,7 @@ class TileViewModel(application: Application): ViewModel() {
val repository: TileRepository val repository: TileRepository
var debug = mutableStateOf(false) var debug = mutableStateOf(false)
var logRequested = mutableStateOf(false) var pointRequested = mutableStateOf(false)
var latitudeText = mutableStateOf(TextFieldValue("")) var latitudeText = mutableStateOf(TextFieldValue(""))
var longitudeText = mutableStateOf(TextFieldValue("")) var longitudeText = mutableStateOf(TextFieldValue(""))
@ -22,6 +22,9 @@ class TileViewModel(application: Application): ViewModel() {
var halvedOffsetX: Float? = null var halvedOffsetX: Float? = null
var halvedOffsetY: Float? = null var halvedOffsetY: Float? = null
var rememberedPointLon = mutableFloatStateOf(0F)
var rememberedPointLat = mutableFloatStateOf(0F)
init { init {
val tileDb = TileDB.getInstance(application) val tileDb = TileDB.getInstance(application)
val tileDao = tileDb.tileDao() val tileDao = tileDb.tileDao()