Deducing height on the move

This commit is contained in:
Alexey 2025-09-22 12:36:51 +03:00
commit af7429fad7
3 changed files with 57 additions and 21 deletions

View file

@ -4,7 +4,7 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-08-26T11:06:03.805342749Z"> <DropdownSelection timestamp="2025-09-22T09:08:24.101225364Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/secondbeam/.config/.android/avd/Virtual_Pixel_3.avd" /> <DeviceId pluginId="LocalEmulator" identifier="path=/home/secondbeam/.config/.android/avd/Virtual_Pixel_3.avd" />

View file

@ -4,6 +4,8 @@ import android.content.Context
import android.util.Log import android.util.Log
import java.io.DataInputStream import java.io.DataInputStream
import java.io.FileInputStream import java.io.FileInputStream
import kotlin.math.max
import kotlin.math.min
private data class HeightInfo( private data class HeightInfo(
val minLon: Float, val minLon: Float,
@ -16,46 +18,63 @@ private data class HeightInfo(
class KhmParser { class KhmParser {
companion object { companion object {
private var header: HeightInfo? = null
const val HEIGHT_FILE: String = "height.khm" const val HEIGHT_FILE: String = "height.khm"
fun load(filePath: String, ctx: Context) { fun load(filePath: String, ctx: Context) {
if (ctx.getFileStreamPath(HEIGHT_FILE).exists()) { if (ctx.getFileStreamPath(HEIGHT_FILE).exists()) {
Log.i(TAG, "height.khm already exists")
return return
} }
val inp = FileInputStream(filePath) val inp = FileInputStream(filePath)
ctx.openFileOutput(HEIGHT_FILE, Context.MODE_PRIVATE).use { ctx.openFileOutput(HEIGHT_FILE, Context.MODE_PRIVATE).use {
it.write(inp.readBytes()) it.write(inp.readBytes())
Log.i(TAG, "Copied %s to height.khm".format(filePath))
} }
} }
private fun getOffset(header: HeightInfo, x: Int, y: Int): Int { private fun getOffset(header: HeightInfo, x: Int, y: Int): Int {
Log.i(TAG, "Offset for (%d, %d) = %d".format(x, y, y * header.height + x))
return (x * header.width + y) * 2 return (x * header.width + y) * 2
} }
private fun readHeader(dis: DataInputStream): HeightInfo { private fun readHeader(dis: DataInputStream): HeightInfo {
return with(dis) { if (header == null) {
header = with(dis) {
val minLon = readFloat() val minLon = readFloat()
val minLat = readFloat() val minLat = readFloat()
val lonPerValue = readFloat() val lonPerValue = readFloat()
val latPerValue = readFloat() val latPerValue = readFloat()
val width = readInt() val width = readInt()
val height = readInt() val height = readInt()
Log.i(TAG, "Header: %.6f, %.6f, %.6f, %.6f, %d, %d".format(minLon, minLat, lonPerValue, latPerValue, width, height))
HeightInfo(minLon, minLat, lonPerValue, latPerValue, width, height) HeightInfo(minLon, minLat, lonPerValue, latPerValue, width, height)
} }
} else {
dis.skipBytes(4 * 6)
}
return header!!
}
private fun inBounds(header: HeightInfo, lon: Float, lat: Float): Boolean {
val maxLon = header.minLon + (header.lonPerValue * header.width)
val maxLat = header.minLat + (header.latPerValue * header.height)
val miLon = min(header.minLon, maxLon)
val maLon = max(header.minLon, maxLon)
val miLat = min(header.minLat, maxLat)
val maLat = max(header.minLat, maxLat)
return lon > miLon && lat > miLat && lon < maLon && lat < maLat
} }
fun getHeight(lon: Float, lat: Float, ctx: Context): UShort { fun getHeight(lon: Float, lat: Float, ctx: Context): UShort {
val dis = DataInputStream(ctx.openFileInput(HEIGHT_FILE)) val dis = DataInputStream(ctx.openFileInput(HEIGHT_FILE))
dis.use {
val header = readHeader(dis) val header = readHeader(dis)
if (!inBounds(header, lon, lat))
return 0u
val x = ((lon - header.minLon) / header.lonPerValue).toInt() val x = ((lon - header.minLon) / header.lonPerValue).toInt()
val y = ((lat - header.minLat) / header.latPerValue).toInt() val y = ((lat - header.minLat) / header.latPerValue).toInt()
val offset = getOffset(header, x, y) val offset = getOffset(header, x, y)
dis.skipBytes(offset) dis.skipBytes(offset)
return dis.readUnsignedShort().toUShort() val height = dis.readUnsignedShort().toUShort()
return height
}
} }
} }
} }

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.ParagraphStyle import androidx.compose.ui.text.ParagraphStyle
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
@ -33,6 +34,7 @@ fun MapCanvas(
tileContainer: TileContainer, tileContainer: TileContainer,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val ctx = LocalContext.current
var offsetX by rememberSaveable { viewModel.mapOffsetX } var offsetX by rememberSaveable { viewModel.mapOffsetX }
var offsetY by rememberSaveable { viewModel.mapOffsetY } var offsetY by rememberSaveable { viewModel.mapOffsetY }
val scale by rememberSaveable { viewModel.scale } val scale by rememberSaveable { viewModel.scale }
@ -170,13 +172,14 @@ fun MapCanvas(
topLeft = latLonOffset topLeft = latLonOffset
) )
val lon = SphereMercator.mercateX(centerTileX, level)
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)) {
val lon = SphereMercator.mercateX(centerTileX, level)
val lat = SphereMercator.mercateY(centerTileY, level)
append("%.6f\n%.6f".format(lon, lat)) append("%.6f\n%.6f".format(lon, lat))
if (debug) { if (debug) {
append("\n%.0f\n%.0f".format(offsetX, offsetY)) append("\n%.0f\n%.0f".format(offsetX, offsetY))
@ -187,6 +190,20 @@ fun MapCanvas(
size = latLonSize, size = latLonSize,
topLeft = latLonOffset topLeft = latLonOffset
) )
drawText(
textMeasurer = textMeasurer,
text = buildAnnotatedString {
withStyle(SpanStyle(color = Color.White)) {
append(
"%dm".format(
KhmParser.getHeight(lon.toFloat(), lat.toFloat(), ctx).toInt()
)
)
}
},
topLeft = Offset(halvedX + crossRadius, halvedY + crossRadius)
)
} }
} }
} }