This commit is contained in:
Alexey 2025-08-25 15:15:51 +03:00
commit 8a530d9d48
4 changed files with 84 additions and 25 deletions

View file

@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-08-08T10:50:02.571721922Z"> <DropdownSelection timestamp="2025-08-21T11:17:37.943341883Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/secondbeam/.android/avd/Pixel_3.avd" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=96DX21GPR" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View file

@ -100,7 +100,7 @@ fun Main(vm: TileViewModel = viewModel()) {
Slider( Slider(
value = sliderValue.floatValue, value = sliderValue.floatValue,
onValueChange = { sliderValue.floatValue = it }, onValueChange = { sliderValue.floatValue = it },
valueRange = 1F..14F, valueRange = 2F..14F,
modifier = Modifier.align(Alignment.CenterStart) modifier = Modifier.align(Alignment.CenterStart)
) )
} }

View file

@ -5,8 +5,10 @@ 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.MutableFloatState import androidx.compose.runtime.MutableFloatState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
@ -22,6 +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.sqrt
@Composable @Composable
fun MapCanvas( fun MapCanvas(
@ -31,15 +34,15 @@ fun MapCanvas(
tileContainer: TileContainer, tileContainer: TileContainer,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val offsetX = rememberSaveable { mutableFloatStateOf(-TILE_SIZE) } var offsetX by rememberSaveable { mutableFloatStateOf(-TILE_SIZE) }
val offsetY = rememberSaveable { mutableFloatStateOf(-TILE_SIZE) } var offsetY by rememberSaveable { mutableFloatStateOf(-TILE_SIZE) }
val textMeasurer = rememberTextMeasurer() val textMeasurer = rememberTextMeasurer()
Canvas( Canvas(
modifier = modifier.fillMaxSize() modifier = modifier.fillMaxSize()
.pointerInput(Unit) { .pointerInput(Unit) {
detectDragGestures { _, distance -> detectDragGestures { _, distance ->
offsetX.floatValue -= distance.x offsetX -= distance.x
offsetY.floatValue -= distance.y offsetY -= distance.y
} }
} }
) { ) {
@ -53,24 +56,24 @@ fun MapCanvas(
if (levelDiff < 0) { if (levelDiff < 0) {
repeat (levelDiff.absoluteValue) { repeat (levelDiff.absoluteValue) {
offsetX.floatValue -= size.width / 2F + TILE_SIZE offsetX -= size.width / 2F + TILE_SIZE
offsetY.floatValue -= size.height / 2F + TILE_SIZE offsetY -= size.height / 2F + TILE_SIZE
offsetX.floatValue /= 2F offsetX /= 2F
offsetY.floatValue /= 2F offsetY /= 2F
} }
} else if (levelDiff > 0) { } else if (levelDiff > 0) {
repeat (levelDiff) { repeat (levelDiff) {
offsetX.floatValue *= 2F offsetX *= 2F
offsetY.floatValue *= 2F offsetY *= 2F
offsetX.floatValue += size.width / 2F + TILE_SIZE offsetX += size.width / 2F + TILE_SIZE
offsetY.floatValue += size.height / 2F + TILE_SIZE offsetY += size.height / 2F + TILE_SIZE
} }
} }
val tileOffsetX = (offsetX.floatValue / TILE_SIZE).toInt() val tileOffsetX = (offsetX / TILE_SIZE).toInt()
val tileOffsetY = (offsetY.floatValue / TILE_SIZE).toInt() val tileOffsetY = (offsetY / TILE_SIZE).toInt()
val strippedOffsetX = offsetX.floatValue % TILE_SIZE val strippedOffsetX = offsetX % TILE_SIZE
val strippedOffsetY = offsetY.floatValue % TILE_SIZE val strippedOffsetY = offsetY % TILE_SIZE
val offset = Offset(strippedOffsetX, strippedOffsetY) val offset = Offset(strippedOffsetX, strippedOffsetY)
@ -89,19 +92,20 @@ fun MapCanvas(
val localOffsetY = TILE_SIZE * (cellY - 1) val localOffsetY = TILE_SIZE * (cellY - 1)
val bitmap = tiles.find { it.x == tileX && it.y == tileY && it.level == level }?.toBitmap() val bitmap = tiles.find { it.x == tileX && it.y == tileY && it.level == level }?.toBitmap()
val totalOffset = Offset(localOffsetX, localOffsetY) - offset
bitmap?.let { bitmap?.let {
val imageBitmap = bitmap.asImageBitmap() val imageBitmap = bitmap.asImageBitmap()
drawImage( drawImage(
image = imageBitmap, image = imageBitmap,
topLeft = Offset(localOffsetX, localOffsetY) - offset topLeft = totalOffset
) )
} }
/*
drawRect( drawRect(
color = gridColor, color = gridColor,
size = Size(TILE_SIZE, TILE_SIZE), size = Size(TILE_SIZE, TILE_SIZE),
topLeft = Offset(localOffsetX, localOffsetY) - offset, topLeft = totalOffset,
style = Stroke(width = 4F) style = Stroke(width = 4F)
) )
@ -110,14 +114,17 @@ fun MapCanvas(
text = buildAnnotatedString { text = buildAnnotatedString {
withStyle(ParagraphStyle(textAlign = TextAlign.Center)) { withStyle(ParagraphStyle(textAlign = TextAlign.Center)) {
withStyle(SpanStyle(color = gridColor)) { withStyle(SpanStyle(color = gridColor)) {
append("x:%d, y:%d".format(tileX, tileY)) val mapSize = (1 shl (level - 1)).toDouble()
val mappedY = (tileY.toDouble() - (mapSize / 2.0))
val mercX = SphereMercator.sx2lon((tileX * TILE_SIZE).toDouble(), level) - 180.0
val mercY = -SphereMercator.sy2lat(mappedY * TILE_SIZE, level - 1)
append("%.6f, %.6f,\n%d, %d".format(mercX, mercY, tileX, tileY))
} }
} }
}, },
topLeft = Offset(localOffsetX, localOffsetY) - offset, topLeft = totalOffset,
size = Size(TILE_SIZE, TILE_SIZE) size = Size(TILE_SIZE, TILE_SIZE)
) )
*/
} }
} }
} }

View file

@ -0,0 +1,52 @@
package com.mirenkov.ktheightmap
import java.lang.Math.toRadians
import java.lang.Math.toDegrees
import kotlin.math.atan
import kotlin.math.exp
import kotlin.math.ln
import kotlin.math.tan
import kotlin.math.PI
class SphereMercator {
companion object {
const val RADIUS = 6378137
const val METERS_PER_PIXEL = 156543.0339
fun x2lon(x: Double): Double {
return toDegrees(x / RADIUS)
}
fun y2lat(y: Double): Double {
return toDegrees(atan(exp(y / RADIUS / 2)) * 2 - PI / 2)
}
fun lon2x(longitude: Double): Double {
return toRadians(longitude) * RADIUS
}
fun lat2y(latitude: Double): Double {
return ln(tan(PI / 4 + toRadians(latitude) / 2)) * RADIUS
}
fun scaledMetersPerPixel(level: Int): Double {
return METERS_PER_PIXEL / ( 1 shl (level - 1) )
}
fun sx2lon(x: Double, level: Int): Double {
return x2lon(x * scaledMetersPerPixel(level))
}
fun sy2lat(y: Double, level: Int): Double {
return y2lat( y * scaledMetersPerPixel(level))
}
fun lon2sx(longitude: Double, level: Int) : Double {
return lon2x(longitude) / scaledMetersPerPixel(level)
}
fun lat2sy(latitude: Double, level: Int): Double {
return lat2y(latitude) / scaledMetersPerPixel(level)
}
}
}