Compare commits
No commits in common. "86a2dc3786183bb91eb24a15af0c5a35c3256945" and "49cd9c6154d92f2190bac4107b11a0d1f42a3107" have entirely different histories.
86a2dc3786
...
49cd9c6154
6 changed files with 85 additions and 246 deletions
2
.idea/deploymentTargetSelector.xml
generated
2
.idea/deploymentTargetSelector.xml
generated
|
|
@ -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-09-22T09:08:24.101225364Z">
|
<DropdownSelection timestamp="2025-08-26T11:06:03.805342749Z">
|
||||||
<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" />
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
package com.mirenkov.ktheightmap
|
package com.mirenkov.ktheightmap
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
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,
|
||||||
|
|
@ -17,116 +16,46 @@ 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 {
|
||||||
if (header == null) {
|
return with(dis) {
|
||||||
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)
|
||||||
val height = dis.readUnsignedShort().toUShort()
|
return dis.readUnsignedShort().toUShort()
|
||||||
return height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
|
||||||
fun getHeightsMul(ctx: Context, coords: Array<Pair<Float, Float>>): Pair<UShortArray, Array<Pair<Float, Float>>> {
|
|
||||||
if (coords.isEmpty()) return Pair(ushortArrayOf(), coords)
|
|
||||||
val dis = DataInputStream(ctx.openFileInput(HEIGHT_FILE))
|
|
||||||
dis.use {
|
|
||||||
val header = readHeader(dis)
|
|
||||||
|
|
||||||
val reversed = coords[0].first < coords[coords.size - 1].first
|
|
||||||
|
|
||||||
val glist: MutableMap<Pair<Float, Float>, Int> = mutableMapOf()
|
|
||||||
|
|
||||||
for (coord in coords) {
|
|
||||||
val x = ((coord.first - header.minLon) / header.lonPerValue).toInt()
|
|
||||||
val y = ((coord.second - header.minLat) / header.latPerValue).toInt()
|
|
||||||
val offset = getOffset(header, x, y)
|
|
||||||
glist.put(coord, offset)
|
|
||||||
}
|
|
||||||
val sortedGlist = glist.toList().sortedBy { (_, v) -> v }.toMap()
|
|
||||||
val glistKeys = sortedGlist.keys.toTypedArray()
|
|
||||||
|
|
||||||
val cutOffsets = IntArray(glist.size) {-1}
|
|
||||||
if (inBounds(header, coords[0].first, coords[0].second)) {
|
|
||||||
val key = glistKeys[0]
|
|
||||||
cutOffsets[0] = sortedGlist.getOrDefault(key, 0)
|
|
||||||
}
|
|
||||||
for (i in 1 until sortedGlist.size) {
|
|
||||||
if (inBounds(header, coords[i].first, coords[i].second) && cutOffsets[i-1] >= 0) {
|
|
||||||
val prevKey = glistKeys[i-1]
|
|
||||||
val key = glistKeys[i]
|
|
||||||
cutOffsets[i] = sortedGlist.getOrDefault(key, 0) - sortedGlist.getOrDefault(prevKey, 0) - 2
|
|
||||||
} else break
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pair(
|
|
||||||
UShortArray(coords.size) { i ->
|
|
||||||
if (cutOffsets[i] > 0) {
|
|
||||||
dis.skipBytes(cutOffsets[i])
|
|
||||||
dis.readUnsignedShort().toUShort()
|
|
||||||
} else 0u },
|
|
||||||
if (reversed) coords else coords.reversed().toTypedArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLonPerValue(): Float {
|
|
||||||
return header?.lonPerValue ?: 0F
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLatPerValue(): Float {
|
|
||||||
return header?.latPerValue ?: 0F
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ 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
|
||||||
|
|
@ -31,6 +32,7 @@ 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
|
||||||
|
|
@ -41,6 +43,7 @@ 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
|
||||||
|
|
@ -119,13 +122,12 @@ 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 pointRequested by remember{ viewModel.pointRequested }
|
var logRequested by remember{ viewModel.logRequested }
|
||||||
val dialogShown = rememberSaveable{ mutableStateOf(false) }
|
val dialogShown = rememberSaveable{ mutableStateOf(false) }
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
@ -145,9 +147,9 @@ fun ToolButton(viewModel: TileViewModel) {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Place point") },
|
text = { Text("Log") },
|
||||||
onClick = {
|
onClick = {
|
||||||
pointRequested = true
|
logRequested = true
|
||||||
expanded = false
|
expanded = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -173,6 +175,7 @@ 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 }
|
||||||
|
|
@ -207,8 +210,12 @@ 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())
|
||||||
} },
|
} },
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
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
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -17,7 +16,6 @@ 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
|
||||||
|
|
@ -26,11 +24,7 @@ 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.floor
|
|
||||||
import kotlin.math.pow
|
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MapCanvas(
|
fun MapCanvas(
|
||||||
viewModel: TileViewModel,
|
viewModel: TileViewModel,
|
||||||
|
|
@ -39,30 +33,18 @@ 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 }
|
||||||
val textMeasurer = rememberTextMeasurer()
|
val textMeasurer = rememberTextMeasurer()
|
||||||
val debug by rememberSaveable { viewModel.debug }
|
val debug by rememberSaveable { viewModel.debug }
|
||||||
var pointRequested by rememberSaveable { viewModel.pointRequested }
|
var logRequested by rememberSaveable { viewModel.logRequested }
|
||||||
var pointLat by rememberSaveable { viewModel.rememberedPointLat }
|
|
||||||
var pointLon by rememberSaveable { viewModel.rememberedPointLon }
|
|
||||||
var invokeHeightCalc by remember { mutableStateOf(false) }
|
|
||||||
Canvas(
|
Canvas(
|
||||||
modifier = modifier.fillMaxSize()
|
modifier = modifier.fillMaxSize()
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectDragGestures (
|
detectDragGestures { _, distance ->
|
||||||
onDragEnd = {
|
|
||||||
invokeHeightCalc = true
|
|
||||||
},
|
|
||||||
onDragCancel = {
|
|
||||||
invokeHeightCalc = true
|
|
||||||
}
|
|
||||||
) { _, distance ->
|
|
||||||
offsetX -= distance.x
|
offsetX -= distance.x
|
||||||
offsetY -= distance.y
|
offsetY -= distance.y
|
||||||
invokeHeightCalc = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
@ -73,6 +55,16 @@ 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
|
||||||
|
|
@ -92,27 +84,12 @@ 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
|
||||||
|
|
||||||
|
|
@ -131,13 +108,6 @@ 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
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tiles
|
|
||||||
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)
|
||||||
|
|
@ -149,7 +119,6 @@ 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(
|
||||||
|
|
@ -158,7 +127,6 @@ fun MapCanvas(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug grid
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
drawRect(
|
drawRect(
|
||||||
color = gridColor,
|
color = gridColor,
|
||||||
|
|
@ -182,63 +150,7 @@ fun MapCanvas(
|
||||||
size = Size(TILE_SIZE, TILE_SIZE)
|
size = Size(TILE_SIZE, TILE_SIZE)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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)
|
|
||||||
if (pointOffsetX >= 0 && pointOffsetY >= 0 && pointOffsetX < size.width && pointOffsetY < size.height)
|
|
||||||
drawText(
|
|
||||||
textMeasurer = textMeasurer,
|
|
||||||
text = buildAnnotatedString { withStyle(SpanStyle(color = Color.White)) {
|
|
||||||
append("${startHeight}m")
|
|
||||||
} },
|
|
||||||
topLeft = Offset(pointOffsetX, pointOffsetY - 32)
|
|
||||||
)
|
|
||||||
if (invokeHeightCalc) {
|
|
||||||
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()
|
|
||||||
val array: Array<Pair<Float, Float>> = Array(valuesCount) { step ->
|
|
||||||
val interCoef = 1F - step.toFloat() / valuesCount
|
|
||||||
Pair(lon - lonDiff * interCoef, lat - latDiff * interCoef)
|
|
||||||
}
|
|
||||||
val heightPair = KhmParser.getHeightsMul(ctx, array)
|
|
||||||
val heights = heightPair.first
|
|
||||||
val coords = heightPair.second
|
|
||||||
|
|
||||||
for (step in 0 until coords.size) {
|
|
||||||
val stepLat = coords[step].second
|
|
||||||
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)
|
|
||||||
drawRect(
|
|
||||||
color = if (heights[step] > 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)
|
||||||
|
|
@ -246,27 +158,26 @@ 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
|
||||||
)
|
)
|
||||||
|
|
||||||
// Info box content
|
|
||||||
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(lat, lon))
|
val lon = SphereMercator.mercateX(centerTileX, level)
|
||||||
|
val lat = SphereMercator.mercateY(centerTileY, level)
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
|
|
@ -276,16 +187,7 @@ fun MapCanvas(
|
||||||
size = latLonSize,
|
size = latLonSize,
|
||||||
topLeft = latLonOffset
|
topLeft = latLonOffset
|
||||||
)
|
)
|
||||||
|
}
|
||||||
// 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)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +89,10 @@ 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} },
|
} },
|
||||||
|
|
@ -121,6 +124,7 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class TileViewModel(application: Application): ViewModel() {
|
||||||
val repository: TileRepository
|
val repository: TileRepository
|
||||||
|
|
||||||
var debug = mutableStateOf(false)
|
var debug = mutableStateOf(false)
|
||||||
var pointRequested = mutableStateOf(false)
|
var logRequested = mutableStateOf(false)
|
||||||
|
|
||||||
var latitudeText = mutableStateOf(TextFieldValue(""))
|
var latitudeText = mutableStateOf(TextFieldValue(""))
|
||||||
var longitudeText = mutableStateOf(TextFieldValue(""))
|
var longitudeText = mutableStateOf(TextFieldValue(""))
|
||||||
|
|
@ -22,9 +22,6 @@ 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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue