如何在地图抓图上根据GPS坐标标记图标?

How to mark the icons according to the GPS coordinates on the map-captured image?

  1. 捕获地图、创建图像并将其显示在屏幕上。
  2. 图像有起点和终点。
  3. 旋转图像,使起点始终在左侧,无论实际方向如何。
  4. 图片已固定,用户无法zoom/rotate图片。
  5. 从服务器获取四个顶点(a,b,c,d)的坐标。
  6. 我在问题中使用了捕获的图像,但是 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)

旋转map1180度(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
)