MapCanvas refactoring
This commit is contained in:
parent
b70b98feb6
commit
46c831b473
4 changed files with 229 additions and 203 deletions
13
.gitignore
vendored
13
.gitignore
vendored
|
|
@ -1,15 +1,10 @@
|
||||||
*.iml
|
*.iml
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
local.properties
|
||||||
/.idea/caches
|
.idea
|
||||||
/.idea/libraries
|
|
||||||
/.idea/modules.xml
|
|
||||||
/.idea/workspace.xml
|
|
||||||
/.idea/navEditor.xml
|
|
||||||
/.idea/assetWizardSettings.xml
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
build
|
||||||
/captures
|
captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
|
|
|
||||||
3
.idea/vcs.xml
generated
3
.idea/vcs.xml
generated
|
|
@ -1,5 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
<component name="VcsProjectSettings">
|
<component name="VcsProjectSettings">
|
||||||
<option name="detectVcsMappingsAutomatically" value="false" />
|
<option name="detectVcsMappingsAutomatically" value="false" />
|
||||||
</component>
|
</component>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package com.mirenkov.ktheightmap
|
package com.mirenkov.ktheightmap
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
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.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
|
@ -15,12 +15,14 @@ import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Path
|
import androidx.compose.ui.graphics.Path
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
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.platform.LocalContext
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
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.TextMeasurer
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.drawText
|
import androidx.compose.ui.text.drawText
|
||||||
import androidx.compose.ui.text.rememberTextMeasurer
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
|
|
@ -31,19 +33,6 @@ import kotlin.math.floor
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.sqrt
|
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)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MapCanvas(
|
fun MapCanvas(
|
||||||
|
|
@ -62,22 +51,13 @@ fun MapCanvas(
|
||||||
var pointRequested by rememberSaveable { viewModel.pointRequested }
|
var pointRequested by rememberSaveable { viewModel.pointRequested }
|
||||||
var pointLat by rememberSaveable { viewModel.rememberedPointLat }
|
var pointLat by rememberSaveable { viewModel.rememberedPointLat }
|
||||||
var pointLon by rememberSaveable { viewModel.rememberedPointLon }
|
var pointLon by rememberSaveable { viewModel.rememberedPointLon }
|
||||||
var invokeHeightCalc by remember { mutableStateOf(false) }
|
var pointHeight by rememberSaveable { viewModel.rememberedPointHeight }
|
||||||
val startTargetMeters = 64 * 1000 * 1000
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
@ -108,23 +88,22 @@ 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 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 lon = SphereMercator.mercateX(centerTileX, level).toFloat()
|
||||||
val lat = SphereMercator.mercateY(centerTileY, level).toFloat()
|
val lat = SphereMercator.mercateY(centerTileY, level).toFloat()
|
||||||
|
val height = KhmParser.getHeight(lon, lat, ctx)
|
||||||
|
|
||||||
if (pointRequested) {
|
if (pointRequested) {
|
||||||
if (pointLat == 0F) {
|
if (pointLat == 0F) {
|
||||||
pointLat = lat
|
pointLat = lat
|
||||||
pointLon = lon
|
pointLon = lon
|
||||||
|
pointHeight = height.toInt()
|
||||||
} else {
|
} else {
|
||||||
pointLat = 0F
|
pointLat = 0F
|
||||||
pointLon = 0F
|
pointLon = 0F
|
||||||
|
pointHeight = 0
|
||||||
}
|
}
|
||||||
pointRequested = false
|
pointRequested = false
|
||||||
}
|
}
|
||||||
|
|
@ -137,25 +116,17 @@ fun MapCanvas(
|
||||||
|
|
||||||
val offset = Offset(strippedOffsetX, strippedOffsetY)
|
val offset = Offset(strippedOffsetX, strippedOffsetY)
|
||||||
|
|
||||||
val grid = size / TILE_SIZE
|
|
||||||
|
|
||||||
val gridWidth = grid.width.toInt()
|
|
||||||
val gridHeight = grid.height.toInt()
|
|
||||||
|
|
||||||
val tiles = tileContainer.getTiles(tileOffsetX, tileOffsetY, tileOffsetX + gridWidth + 2, tileOffsetY + gridHeight + 2, level)
|
|
||||||
|
|
||||||
val crossRadius = 24F
|
|
||||||
|
|
||||||
val additionalSize = if (debug) 96F else 0F
|
|
||||||
val latLonSize = Size(216F, 96F + additionalSize)
|
|
||||||
val latLonOffset = Offset(16F, 16F)
|
|
||||||
|
|
||||||
// Background
|
// Background
|
||||||
drawRect(
|
drawRect(
|
||||||
color = backColor,
|
color = backColor,
|
||||||
size = size
|
size = size
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val grid = size / TILE_SIZE
|
||||||
|
val gridWidth = grid.width.toInt()
|
||||||
|
val gridHeight = grid.height.toInt()
|
||||||
|
|
||||||
|
val tiles = tileContainer.getTiles(tileOffsetX, tileOffsetY, tileOffsetX + gridWidth + 2, tileOffsetY + gridHeight + 2, level)
|
||||||
// Tiles
|
// Tiles
|
||||||
for (cellX in 0 .. gridWidth + 2) {
|
for (cellX in 0 .. gridWidth + 2) {
|
||||||
val tileX = tileOffsetX + cellX
|
val tileX = tileOffsetX + cellX
|
||||||
|
|
@ -170,7 +141,7 @@ fun MapCanvas(
|
||||||
|
|
||||||
// Tile
|
// Tile
|
||||||
bitmap?.let {
|
bitmap?.let {
|
||||||
val imageBitmap = bitmap.asImageBitmap()
|
val imageBitmap = it.asImageBitmap()
|
||||||
drawImage(
|
drawImage(
|
||||||
image = imageBitmap,
|
image = imageBitmap,
|
||||||
topLeft = totalOffset
|
topLeft = totalOffset
|
||||||
|
|
@ -179,174 +150,230 @@ fun MapCanvas(
|
||||||
|
|
||||||
// Debug grid
|
// Debug grid
|
||||||
if (debug) {
|
if (debug) {
|
||||||
drawRect(
|
drawTileDebugInfo(gridColor, totalOffset, textMeasurer, tile)
|
||||||
color = gridColor,
|
|
||||||
size = Size(TILE_SIZE, TILE_SIZE),
|
|
||||||
topLeft = totalOffset,
|
|
||||||
style = Stroke(width = 4F)
|
|
||||||
)
|
|
||||||
|
|
||||||
drawText(
|
|
||||||
textMeasurer = textMeasurer,
|
|
||||||
text = buildAnnotatedString {
|
|
||||||
withStyle(ParagraphStyle(textAlign = TextAlign.Center)) {
|
|
||||||
withStyle(SpanStyle(color = gridColor)) {
|
|
||||||
val mercX = tile.mercateX()
|
|
||||||
val mercY = tile.mercateY()
|
|
||||||
append("%.6f, %.6f,\n%d, %d".format(mercX, mercY, tileX, tileY))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
topLeft = totalOffset,
|
|
||||||
size = Size(TILE_SIZE, TILE_SIZE)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Placed point and line to center
|
// Placed point and line to center
|
||||||
if (pointLat != 0F) {
|
if (pointLat != 0F) {
|
||||||
val pointOffsetX = SphereMercator.mercateLon(pointLon.toDouble(), level, -TILE_SIZE.toDouble()).toFloat() - offsetX
|
drawPointInfo(lat, lon, pointLat, pointLon, pointHeight, offsetX, offsetY, halvedX, halvedY, level, gridColor, ctx, textMeasurer)
|
||||||
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)
|
val crossRadius = 24F
|
||||||
if (pointOffsetX >= 0 && pointOffsetY >= 0 && pointOffsetX < size.width && pointOffsetY < size.height)
|
drawCursor(halvedX, halvedY, crossRadius, KhmParser.getHeight(lon, lat, ctx), textMeasurer)
|
||||||
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 additionalSize = if (debug) 96F else 0F
|
||||||
val stepLat = coords[step].second
|
val infoBoxSize = Size(216F, 96F + additionalSize)
|
||||||
val stepLon = coords[step].first
|
val infoBoxOffset = Offset(16F, 16F)
|
||||||
val stepOffsetX = SphereMercator.mercateLon(stepLon.toDouble(), level, -TILE_SIZE.toDouble()).toFloat() - offsetX
|
val infoBoxText = buildInfoBoxString(gridColor, lat, lon, offsetX, offsetY, debug)
|
||||||
val stepOffsetY = SphereMercator.mercateLat(stepLat.toDouble(), level, -TILE_SIZE.toDouble()).toFloat() - offsetY
|
drawInfoBox(backColor, infoBoxSize, infoBoxOffset, infoBoxText, textMeasurer)
|
||||||
if (stepOffsetX >= 0 && stepOffsetY >= 0 && stepOffsetX < size.width && stepOffsetY < size.height && heights[step] > 0u)
|
|
||||||
drawRect(
|
drawDistanceMeasurer(level, halvedX, textMeasurer)
|
||||||
color = if (heights[step] > startHeight) Color.Red else Color.Green,
|
}
|
||||||
size = Size(8F, 8F),
|
}
|
||||||
topLeft = Offset(stepOffsetX - 4, stepOffsetY - 4)
|
|
||||||
)
|
fun DrawScope.drawTileDebugInfo(color: Color, topLeft: Offset, textMeasurer: TextMeasurer, tile: Tile) {
|
||||||
|
val size = Size(TILE_SIZE, TILE_SIZE)
|
||||||
|
drawRect(
|
||||||
|
color,
|
||||||
|
topLeft,
|
||||||
|
size,
|
||||||
|
style = Stroke(width = 4F)
|
||||||
|
)
|
||||||
|
|
||||||
|
drawText(
|
||||||
|
textMeasurer = textMeasurer,
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
withStyle(ParagraphStyle(textAlign = TextAlign.Center)) {
|
||||||
|
withStyle(SpanStyle(color)) {
|
||||||
|
val mercX = tile.mercateX()
|
||||||
|
val mercY = tile.mercateY()
|
||||||
|
append("%.6f, %.6f,\n%d, %d".format(mercX, mercY, tile.x, tile.y))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val lineLength = sqrt((pointOffsetX - halvedX).pow(2) + (pointOffsetY - halvedY).pow(2))
|
},
|
||||||
val lineMeters = (lineLength * SphereMercator.scaledMetersPerPixel(level + 1)).toInt()
|
topLeft,
|
||||||
drawText(
|
size = size
|
||||||
textMeasurer = textMeasurer,
|
)
|
||||||
text = distanceString(lineMeters),
|
}
|
||||||
size = Size(160F, 48F),
|
|
||||||
topLeft = Offset(
|
|
||||||
halvedX - 80F,
|
|
||||||
halvedY - 80F
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cursor path
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
val cursorPath = Path()
|
fun DrawScope.drawPointInfo(
|
||||||
with(cursorPath) {
|
lat: Float,
|
||||||
moveTo(halvedX - crossRadius, halvedY)
|
lon: Float,
|
||||||
lineTo(halvedX + crossRadius, halvedY)
|
pointLat: Float,
|
||||||
moveTo(halvedX, halvedY - crossRadius)
|
pointLon: Float,
|
||||||
lineTo(halvedX, halvedY + crossRadius)
|
pointHeight: Int,
|
||||||
close()
|
offsetX: Float,
|
||||||
}
|
offsetY: Float,
|
||||||
|
drawOffsetX: Float,
|
||||||
|
drawOffsetY: Float,
|
||||||
|
level: Int,
|
||||||
|
color: Color,
|
||||||
|
ctx: Context,
|
||||||
|
textMeasurer: TextMeasurer
|
||||||
|
) {
|
||||||
|
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,
|
||||||
|
size = Size(8F, 8F),
|
||||||
|
topLeft = Offset(pointOffsetX - 4, pointOffsetY - 4)
|
||||||
|
)
|
||||||
|
drawLine(
|
||||||
|
color,
|
||||||
|
start = Offset(pointOffsetX, pointOffsetY),
|
||||||
|
end = Offset(drawOffsetX, drawOffsetY)
|
||||||
|
)
|
||||||
|
|
||||||
// Cursor
|
if (pointOffsetX >= 0 && pointOffsetY >= 0 && pointOffsetX < size.width && pointOffsetY < size.height)
|
||||||
drawPath(
|
drawText(
|
||||||
cursorPath,
|
textMeasurer,
|
||||||
Color.White,
|
text = buildAnnotatedString { withStyle(SpanStyle(color = Color.White)) {
|
||||||
style = Stroke(width = 6F)
|
append("↑${pointHeight}m")
|
||||||
|
} },
|
||||||
|
topLeft = Offset(pointOffsetX, pointOffsetY - 32)
|
||||||
)
|
)
|
||||||
// Height under cursor
|
val latPV = KhmParser.getLatPerValue()
|
||||||
KhmParser.getHeight(lon, lat, ctx).let {
|
val lonPV = KhmParser.getLonPerValue()
|
||||||
if (it < 1u) return@let
|
val valueDistance = sqrt(latPV.pow(2) + lonPV.pow(2))
|
||||||
drawText(
|
val latDiff = lat - pointLat
|
||||||
textMeasurer = textMeasurer,
|
val lonDiff = lon - pointLon
|
||||||
text = buildAnnotatedString {
|
val distance = sqrt((latDiff).pow(2) + (lonDiff).pow(2))
|
||||||
withStyle(SpanStyle(color = Color.White)) {
|
val valuesCount = floor(distance / valueDistance).toInt()
|
||||||
append("↑${it}m")
|
val array: Array<Pair<Float, Float>> = Array(valuesCount) { step ->
|
||||||
}
|
val interCoef = 1F - step.toFloat() / valuesCount
|
||||||
},
|
Pair(lon - lonDiff * interCoef, lat - latDiff * interCoef)
|
||||||
topLeft = Offset(halvedX, halvedY + crossRadius)
|
}
|
||||||
|
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 && heights[step] > 0u)
|
||||||
|
drawRect(
|
||||||
|
color = if (heights[step] > pointHeight.toUShort()) Color.Red else Color.Green,
|
||||||
|
size = Size(8F, 8F),
|
||||||
|
topLeft = Offset(stepOffsetX - 4, stepOffsetY - 4)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
val lineLength = sqrt((pointOffsetX - drawOffsetX).pow(2) + (pointOffsetY - drawOffsetY).pow(2))
|
||||||
|
val lineMeters = (lineLength * SphereMercator.scaledMetersPerPixel(level + 1)).toInt()
|
||||||
// Info box
|
drawText(
|
||||||
drawRect(
|
textMeasurer,
|
||||||
color = backColor,
|
text = buildDistanceString(lineMeters),
|
||||||
size = latLonSize,
|
size = Size(160F, 48F),
|
||||||
topLeft = latLonOffset
|
topLeft = Offset(
|
||||||
|
drawOffsetX - 80F,
|
||||||
|
drawOffsetY - 80F
|
||||||
)
|
)
|
||||||
// Info box content
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DrawScope.drawCursor(xOffset: Float, yOffset: Float, radius: Float, height: UShort, textMeasurer: TextMeasurer) {
|
||||||
|
// Cursor path
|
||||||
|
val cursorPath = Path()
|
||||||
|
with(cursorPath) {
|
||||||
|
moveTo(xOffset - radius, yOffset)
|
||||||
|
lineTo(xOffset + radius, yOffset)
|
||||||
|
moveTo(xOffset, yOffset - radius)
|
||||||
|
lineTo(xOffset, yOffset + radius)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor
|
||||||
|
drawPath(
|
||||||
|
cursorPath,
|
||||||
|
Color.White,
|
||||||
|
style = Stroke(width = 6F)
|
||||||
|
)
|
||||||
|
// Height under cursor
|
||||||
|
if (height > 0u)
|
||||||
drawText(
|
drawText(
|
||||||
textMeasurer = textMeasurer,
|
textMeasurer = textMeasurer,
|
||||||
text = buildAnnotatedString {
|
text = buildAnnotatedString {
|
||||||
withStyle(ParagraphStyle(textAlign = TextAlign.Center)) {
|
withStyle(SpanStyle(color = Color.White)) {
|
||||||
withStyle(SpanStyle(color = gridColor)) {
|
append("↑${height}m")
|
||||||
append("%.6f\n%.6f".format(lat, lon))
|
} },
|
||||||
if (debug) {
|
topLeft = Offset(xOffset, yOffset + radius)
|
||||||
append("\n%.0f\n%.0f".format(offsetX, offsetY))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
size = latLonSize,
|
|
||||||
topLeft = latLonOffset
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Distance measurer path
|
fun DrawScope.drawInfoBox(color: Color, size: Size, topLeft: Offset, text: AnnotatedString, textMeasurer: TextMeasurer) {
|
||||||
val measurerPath = Path()
|
// Info box
|
||||||
with(measurerPath) {
|
drawRect(
|
||||||
moveTo((halvedX - targetPixels).toFloat(), 8F)
|
color,
|
||||||
relativeLineTo(0F, measurerHeight)
|
topLeft,
|
||||||
relativeMoveTo(targetPixels.toFloat() * 2F, 0F)
|
size
|
||||||
relativeLineTo(0F, -measurerHeight)
|
)
|
||||||
relativeMoveTo(0F, measurerHeight / 2F)
|
// Info box content
|
||||||
relativeLineTo(-2F * targetPixels.toFloat(), 0F)
|
drawText(
|
||||||
close()
|
textMeasurer = textMeasurer,
|
||||||
}
|
text,
|
||||||
// Distance measurer
|
topLeft,
|
||||||
drawPath(
|
size = size
|
||||||
measurerPath,
|
)
|
||||||
Color.White,
|
}
|
||||||
style = Stroke(width = 6F)
|
|
||||||
)
|
fun DrawScope.drawDistanceMeasurer(level: Int, xOffset: Float, textMeasurer: TextMeasurer) {
|
||||||
// Distance measurer text
|
val startTargetMeters = 64 * 1000 * 1000
|
||||||
drawText(
|
val targetMeters = startTargetMeters shr level
|
||||||
textMeasurer = textMeasurer,
|
val targetPixels = (targetMeters).toFloat() / SphereMercator.scaledMetersPerPixel(level)
|
||||||
text = distanceString(targetMeters),
|
val measurerHeight = 24F
|
||||||
topLeft = Offset(targetPixels.toFloat() * 2F, measurerHeight),
|
// Distance measurer path
|
||||||
size = Size(targetPixels.toFloat() * 2F, 48F)
|
val measurerPath = Path()
|
||||||
)
|
with(measurerPath) {
|
||||||
|
moveTo((xOffset - 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 = buildDistanceString(targetMeters),
|
||||||
|
topLeft = Offset(targetPixels.toFloat() * 2F, measurerHeight),
|
||||||
|
size = Size(targetPixels.toFloat() * 2F, 48F)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildDistanceString(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildInfoBoxString(color: Color, lat: Float, lon: Float, offsetX: Float, offsetY: Float, debug: Boolean): AnnotatedString {
|
||||||
|
return buildAnnotatedString {
|
||||||
|
withStyle(ParagraphStyle(textAlign = TextAlign.Center)) {
|
||||||
|
withStyle(SpanStyle(color = color)) {
|
||||||
|
append("%.6f\n%.6f".format(lat, lon))
|
||||||
|
if (debug) {
|
||||||
|
append("\n%.0f\n%.0f".format(offsetX, offsetY))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ class TileViewModel(application: Application): ViewModel() {
|
||||||
|
|
||||||
var rememberedPointLon = mutableFloatStateOf(0F)
|
var rememberedPointLon = mutableFloatStateOf(0F)
|
||||||
var rememberedPointLat = mutableFloatStateOf(0F)
|
var rememberedPointLat = mutableFloatStateOf(0F)
|
||||||
|
var rememberedPointHeight = mutableIntStateOf(0)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val tileDb = TileDB.getInstance(application)
|
val tileDb = TileDB.getInstance(application)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue