如何在地图抓图上根据GPS坐标标记图标?
How to mark the icons according to the GPS coordinates on the map-captured image?
- 捕获地图、创建图像并将其显示在屏幕上。
- 图像有起点和终点。
- 旋转图像,使起点始终在左侧,无论实际方向如何。
- 图片已固定,用户无法zoom/rotate图片。
- 从服务器获取四个顶点(a,b,c,d)的坐标。
- 我在问题中使用了捕获的图像,但是 Google 无法使用地图,因为应用程序使用了自定义图像。
当我收到地图区域内的特定坐标时,如何在图像上显示图标?
实图上的图片(top is north
)
屏幕上的图像(top is east
)
来自 https://thecodinglog.github.io/math/javascript/2019/04/25/convert-gps-to-screenxy.html
的帮助
我的android代码https://github.com/susemi99/GpsToScreen
实际地图
稍微顺时针旋转(map1)
旋转map1
180度(map2)
MainActivity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
GpsToScreenTheme {
val viewModel = viewModel<MainActivityViewModel>()
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Column(verticalArrangement = Arrangement.Center) {
Row() {
TextButton(onClick = { viewModel.setupMap1() }) {
Text(text = "map1")
}
TextButton(onClick = { viewModel.setupMap2() }) {
Text(text = "map2")
}
}
Box(modifier = Modifier.fillMaxWidth()) {
Image(
painter = painterResource(id = viewModel.mapImage.value),
contentDescription = "",
modifier = Modifier
.fillMaxWidth()
.onSizeChanged { viewModel.updateMapViewSize(it) },
contentScale = ContentScale.FillBounds
)
viewModel.pins.forEach { PinIcon(it) }
}
}
}
}
}
}
@Composable
private fun PinIcon(offset: IntOffset) {
var iconSizeOffset by remember { mutableStateOf(IntOffset.Zero) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.offset { iconSizeOffset }
.onSizeChanged {
iconSizeOffset = IntOffset(offset.x - it.width / 2, offset.y - it.height)
},
) {
Icon(Icons.Default.LocationOn, contentDescription = "", tint = Color.Yellow)
}
}
}
MainActivityViewModel
class MainActivityViewModel : ViewModel() {
private val _mapImage = mutableStateOf(R.drawable.map_1)
val mapImage: State<Int> = _mapImage
private val _mapViewSize = mutableStateOf(IntSize.Zero)
val mapViewSize: State<IntSize> = _mapViewSize
private var leftTopLocation = Location("")
private var rightTopLocation = Location("")
private var leftBottomLocation = Location("")
private var theta = 0.0
private var longitudeEquation = LinearEquation(0.0, 0.0)
private var latitudeEquation = LinearEquation(0.0, 0.0)
private val _pins = mutableStateListOf<IntOffset>()
val pins: SnapshotStateList<IntOffset> = _pins
fun updateMapViewSize(size: IntSize) {
if (size == mapViewSize.value) {
return
}
_mapViewSize.value = size
setupMap1()
}
fun setupMap1() {
_mapImage.value = R.drawable.map_1
leftTopLocation = Location("").apply {
latitude = 37.583744
longitude = 126.502398
}
rightTopLocation = Location("").apply {
latitude = 37.901339
longitude = 127.765617
}
leftBottomLocation = Location("").apply {
latitude = 37.027601
longitude = 126.723336
}
calculateLocation()
}
fun setupMap2() {
_mapImage.value = R.drawable.map_2
leftTopLocation = Location("").apply {
latitude = 37.345171
longitude = 127.988737
}
rightTopLocation = Location("").apply {
latitude = 37.027601
longitude = 126.723336
}
leftBottomLocation = Location("").apply {
latitude = 37.901339
longitude = 127.765617
}
calculateLocation()
}
private fun calculateLocation() {
val leftTopGpsPosition = GpsScreenPosition(0, 0, leftTopLocation.latitude, leftTopLocation.longitude)
val rightTopGpsPosition = GpsScreenPosition(mapViewSize.value.width, 0, rightTopLocation.latitude, rightTopLocation.longitude)
val leftBottomGpsPosition = GpsScreenPosition(0, mapViewSize.value.height, leftBottomLocation.latitude, leftBottomLocation.longitude)
theta = calcTheta(
convertUnitToLat(leftTopGpsPosition.longitude),
leftTopGpsPosition.latitude,
convertUnitToLat(rightTopGpsPosition.longitude),
rightTopGpsPosition.latitude
) * -1
val horizontalCoordinatesAfterRotation = calcCoordinatesAfterRotation(
convertUnitToLat(leftTopGpsPosition.longitude), leftTopGpsPosition.latitude,
convertUnitToLat(rightTopGpsPosition.longitude), rightTopGpsPosition.latitude, theta
)
horizontalCoordinatesAfterRotation.x = convertUnitToLon(horizontalCoordinatesAfterRotation.x.toDouble()).toFloat()
rightTopGpsPosition.rotatedLongitude = horizontalCoordinatesAfterRotation.x.toDouble()
rightTopGpsPosition.rotatedLatitude = horizontalCoordinatesAfterRotation.y.toDouble()
val verticalCoordinatesAfterRotation = calcCoordinatesAfterRotation(
convertUnitToLat(leftTopGpsPosition.longitude), leftTopGpsPosition.latitude,
convertUnitToLat(leftBottomGpsPosition.longitude), leftBottomGpsPosition.latitude, theta
)
verticalCoordinatesAfterRotation.x = convertUnitToLon(verticalCoordinatesAfterRotation.x.toDouble()).toFloat()
leftBottomGpsPosition.rotatedLongitude = verticalCoordinatesAfterRotation.x.toDouble()
leftBottomGpsPosition.rotatedLatitude = verticalCoordinatesAfterRotation.y.toDouble()
longitudeEquation = makeLinearEquation(leftTopGpsPosition.longitude, leftTopGpsPosition.x.toDouble(), rightTopGpsPosition.rotatedLongitude, rightTopGpsPosition.x.toDouble())
latitudeEquation = makeLinearEquation(leftTopGpsPosition.latitude, leftTopGpsPosition.y.toDouble(), leftBottomGpsPosition.rotatedLatitude, leftBottomGpsPosition.y.toDouble())
showLocationPin()
}
private fun showLocationPin() {
_pins.clear()
_pins.add(locationToIntOffset(37.386341, 127.893554))
_pins.add(locationToIntOffset(37.515625, 127.297432))
_pins.add(locationToIntOffset(37.827975, 127.674172))
_pins.add(locationToIntOffset(37.162741, 126.776208))
_pins.add(locationToIntOffset(37.555538, 126.623294))
_pins.add(locationToIntOffset(37.237769, 127.426801))
}
private fun locationToIntOffset(lat: Double, lng: Double): IntOffset {
val rotation = calcCoordinatesAfterRotation(
convertUnitToLat(leftTopLocation.longitude), leftTopLocation.latitude,
convertUnitToLat(lng), lat, theta
)
rotation.x = convertUnitToLon(rotation.x.toDouble()).toFloat()
val x = longitudeEquation.slope * rotation.x + longitudeEquation.intercept
val y = latitudeEquation.slope * rotation.y + latitudeEquation.intercept
return IntOffset(x.toInt(), y.toInt())
}
private fun calcDistancePer(): PointF {
val distancePerLat = leftTopLocation.distanceTo(Location("temp").apply {
latitude = leftTopLocation.latitude + 1
longitude = leftTopLocation.longitude
}) / 1000 // 1경도당 거리(km)
val distancePerLon = leftTopLocation.distanceTo(Location("temp").apply {
latitude = leftTopLocation.latitude
longitude = leftTopLocation.longitude + 1
}) / 1000 // 1위도당 거리(km)
return PointF(distancePerLat, distancePerLon)
}
private fun convertUnitToLat(lonValue: Double): Double {
val distancePer = calcDistancePer()
return lonValue * distancePer.y / distancePer.x
}
private fun convertUnitToLon(latValue: Double): Double {
val distancePer = calcDistancePer()
return latValue * distancePer.x / distancePer.y
}
private fun calcTheta(origin_x: Double, origin_y: Double, x: Double, y: Double): Double {
val a = y - origin_y
val b = x - origin_x
return atan(a / b)
}
private fun calcCoordinatesAfterRotation(x1: Double, y1: Double, x2: Double, y2: Double, theta: Double): PointF {
val rebasedX = x2 - x1
val rebasedY = y2 - y1
val rotatedX = rebasedX * cos(theta) - rebasedY * sin(theta)
val rotatedY = rebasedX * sin(theta) + rebasedY * cos(theta)
val xx = rotatedX + x1
val yy = rotatedY + y1
return PointF(xx.toFloat(), yy.toFloat())
}
private fun makeLinearEquation(x1: Double, y1: Double, x2: Double, y2: Double): LinearEquation {
val x = x2 - x1
val y = y2 - y1
val slope = y / x
val intercept = y1 - (slope * x1)
return LinearEquation(slope, intercept)
}
}
线性方程
data class LinearEquation(
val slope: Double,
val intercept: Double
)
GpsScreenPosition
data class GpsScreenPosition(
val x: Int,
val y: Int,
val latitude: Double,
val longitude: Double,
var rotatedLongitude: Double = 0.0,
var rotatedLatitude: Double = 0.0
)
- 捕获地图、创建图像并将其显示在屏幕上。
- 图像有起点和终点。
- 旋转图像,使起点始终在左侧,无论实际方向如何。
- 图片已固定,用户无法zoom/rotate图片。
- 从服务器获取四个顶点(a,b,c,d)的坐标。
- 我在问题中使用了捕获的图像,但是 Google 无法使用地图,因为应用程序使用了自定义图像。
当我收到地图区域内的特定坐标时,如何在图像上显示图标?
实图上的图片(top is north
)
屏幕上的图像(top is east
)
来自 https://thecodinglog.github.io/math/javascript/2019/04/25/convert-gps-to-screenxy.html
的帮助我的android代码https://github.com/susemi99/GpsToScreen
实际地图
稍微顺时针旋转(map1)
旋转map1
180度(map2)
MainActivity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
GpsToScreenTheme {
val viewModel = viewModel<MainActivityViewModel>()
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Column(verticalArrangement = Arrangement.Center) {
Row() {
TextButton(onClick = { viewModel.setupMap1() }) {
Text(text = "map1")
}
TextButton(onClick = { viewModel.setupMap2() }) {
Text(text = "map2")
}
}
Box(modifier = Modifier.fillMaxWidth()) {
Image(
painter = painterResource(id = viewModel.mapImage.value),
contentDescription = "",
modifier = Modifier
.fillMaxWidth()
.onSizeChanged { viewModel.updateMapViewSize(it) },
contentScale = ContentScale.FillBounds
)
viewModel.pins.forEach { PinIcon(it) }
}
}
}
}
}
}
@Composable
private fun PinIcon(offset: IntOffset) {
var iconSizeOffset by remember { mutableStateOf(IntOffset.Zero) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.offset { iconSizeOffset }
.onSizeChanged {
iconSizeOffset = IntOffset(offset.x - it.width / 2, offset.y - it.height)
},
) {
Icon(Icons.Default.LocationOn, contentDescription = "", tint = Color.Yellow)
}
}
}
MainActivityViewModel
class MainActivityViewModel : ViewModel() {
private val _mapImage = mutableStateOf(R.drawable.map_1)
val mapImage: State<Int> = _mapImage
private val _mapViewSize = mutableStateOf(IntSize.Zero)
val mapViewSize: State<IntSize> = _mapViewSize
private var leftTopLocation = Location("")
private var rightTopLocation = Location("")
private var leftBottomLocation = Location("")
private var theta = 0.0
private var longitudeEquation = LinearEquation(0.0, 0.0)
private var latitudeEquation = LinearEquation(0.0, 0.0)
private val _pins = mutableStateListOf<IntOffset>()
val pins: SnapshotStateList<IntOffset> = _pins
fun updateMapViewSize(size: IntSize) {
if (size == mapViewSize.value) {
return
}
_mapViewSize.value = size
setupMap1()
}
fun setupMap1() {
_mapImage.value = R.drawable.map_1
leftTopLocation = Location("").apply {
latitude = 37.583744
longitude = 126.502398
}
rightTopLocation = Location("").apply {
latitude = 37.901339
longitude = 127.765617
}
leftBottomLocation = Location("").apply {
latitude = 37.027601
longitude = 126.723336
}
calculateLocation()
}
fun setupMap2() {
_mapImage.value = R.drawable.map_2
leftTopLocation = Location("").apply {
latitude = 37.345171
longitude = 127.988737
}
rightTopLocation = Location("").apply {
latitude = 37.027601
longitude = 126.723336
}
leftBottomLocation = Location("").apply {
latitude = 37.901339
longitude = 127.765617
}
calculateLocation()
}
private fun calculateLocation() {
val leftTopGpsPosition = GpsScreenPosition(0, 0, leftTopLocation.latitude, leftTopLocation.longitude)
val rightTopGpsPosition = GpsScreenPosition(mapViewSize.value.width, 0, rightTopLocation.latitude, rightTopLocation.longitude)
val leftBottomGpsPosition = GpsScreenPosition(0, mapViewSize.value.height, leftBottomLocation.latitude, leftBottomLocation.longitude)
theta = calcTheta(
convertUnitToLat(leftTopGpsPosition.longitude),
leftTopGpsPosition.latitude,
convertUnitToLat(rightTopGpsPosition.longitude),
rightTopGpsPosition.latitude
) * -1
val horizontalCoordinatesAfterRotation = calcCoordinatesAfterRotation(
convertUnitToLat(leftTopGpsPosition.longitude), leftTopGpsPosition.latitude,
convertUnitToLat(rightTopGpsPosition.longitude), rightTopGpsPosition.latitude, theta
)
horizontalCoordinatesAfterRotation.x = convertUnitToLon(horizontalCoordinatesAfterRotation.x.toDouble()).toFloat()
rightTopGpsPosition.rotatedLongitude = horizontalCoordinatesAfterRotation.x.toDouble()
rightTopGpsPosition.rotatedLatitude = horizontalCoordinatesAfterRotation.y.toDouble()
val verticalCoordinatesAfterRotation = calcCoordinatesAfterRotation(
convertUnitToLat(leftTopGpsPosition.longitude), leftTopGpsPosition.latitude,
convertUnitToLat(leftBottomGpsPosition.longitude), leftBottomGpsPosition.latitude, theta
)
verticalCoordinatesAfterRotation.x = convertUnitToLon(verticalCoordinatesAfterRotation.x.toDouble()).toFloat()
leftBottomGpsPosition.rotatedLongitude = verticalCoordinatesAfterRotation.x.toDouble()
leftBottomGpsPosition.rotatedLatitude = verticalCoordinatesAfterRotation.y.toDouble()
longitudeEquation = makeLinearEquation(leftTopGpsPosition.longitude, leftTopGpsPosition.x.toDouble(), rightTopGpsPosition.rotatedLongitude, rightTopGpsPosition.x.toDouble())
latitudeEquation = makeLinearEquation(leftTopGpsPosition.latitude, leftTopGpsPosition.y.toDouble(), leftBottomGpsPosition.rotatedLatitude, leftBottomGpsPosition.y.toDouble())
showLocationPin()
}
private fun showLocationPin() {
_pins.clear()
_pins.add(locationToIntOffset(37.386341, 127.893554))
_pins.add(locationToIntOffset(37.515625, 127.297432))
_pins.add(locationToIntOffset(37.827975, 127.674172))
_pins.add(locationToIntOffset(37.162741, 126.776208))
_pins.add(locationToIntOffset(37.555538, 126.623294))
_pins.add(locationToIntOffset(37.237769, 127.426801))
}
private fun locationToIntOffset(lat: Double, lng: Double): IntOffset {
val rotation = calcCoordinatesAfterRotation(
convertUnitToLat(leftTopLocation.longitude), leftTopLocation.latitude,
convertUnitToLat(lng), lat, theta
)
rotation.x = convertUnitToLon(rotation.x.toDouble()).toFloat()
val x = longitudeEquation.slope * rotation.x + longitudeEquation.intercept
val y = latitudeEquation.slope * rotation.y + latitudeEquation.intercept
return IntOffset(x.toInt(), y.toInt())
}
private fun calcDistancePer(): PointF {
val distancePerLat = leftTopLocation.distanceTo(Location("temp").apply {
latitude = leftTopLocation.latitude + 1
longitude = leftTopLocation.longitude
}) / 1000 // 1경도당 거리(km)
val distancePerLon = leftTopLocation.distanceTo(Location("temp").apply {
latitude = leftTopLocation.latitude
longitude = leftTopLocation.longitude + 1
}) / 1000 // 1위도당 거리(km)
return PointF(distancePerLat, distancePerLon)
}
private fun convertUnitToLat(lonValue: Double): Double {
val distancePer = calcDistancePer()
return lonValue * distancePer.y / distancePer.x
}
private fun convertUnitToLon(latValue: Double): Double {
val distancePer = calcDistancePer()
return latValue * distancePer.x / distancePer.y
}
private fun calcTheta(origin_x: Double, origin_y: Double, x: Double, y: Double): Double {
val a = y - origin_y
val b = x - origin_x
return atan(a / b)
}
private fun calcCoordinatesAfterRotation(x1: Double, y1: Double, x2: Double, y2: Double, theta: Double): PointF {
val rebasedX = x2 - x1
val rebasedY = y2 - y1
val rotatedX = rebasedX * cos(theta) - rebasedY * sin(theta)
val rotatedY = rebasedX * sin(theta) + rebasedY * cos(theta)
val xx = rotatedX + x1
val yy = rotatedY + y1
return PointF(xx.toFloat(), yy.toFloat())
}
private fun makeLinearEquation(x1: Double, y1: Double, x2: Double, y2: Double): LinearEquation {
val x = x2 - x1
val y = y2 - y1
val slope = y / x
val intercept = y1 - (slope * x1)
return LinearEquation(slope, intercept)
}
}
线性方程
data class LinearEquation(
val slope: Double,
val intercept: Double
)
GpsScreenPosition
data class GpsScreenPosition(
val x: Int,
val y: Int,
val latitude: Double,
val longitude: Double,
var rotatedLongitude: Double = 0.0,
var rotatedLatitude: Double = 0.0
)