0

I'm prototyping some visual overlays of a GoogleMap @Composable (so Android, Compose, Kotlin). I use a Box to place a Canvas over the GoogleMaps composable, so they're the same size.

The remembered CameraPosition gives me a Projection. Projection's toScreenLocation() can be used to convert my LatLng's to pixels that Canvas draws in the right place.

What is less clear, is when I need to draw a delta distance provided by meters. For example, an arc centered at LatLng with a radius in meters (yes, I now about the Circle composable, this is just an example AND the Circle composable doesn't give me enough drawing fidelity for what I'm doing).

I've found that I can use the following to compute a pixelsPerMeter to multiply by my distances to get pixel deltas for meters:

val Projection.visibleMetricHeight: Double get() = visibleRegion.latLngBounds.metricHeight
val LatLngBounds.latitudinalHeight: Double get() = northeast.latitude - southwest.latitude
val LatLngBounds.metricHeight: Double get() = latitudinalHeight * 111320

In my Canvas code then, I can compute pixelPerMeter as

val pixelsPerMeter = size.height / positionState.projection.visibleMetricHeight

This seems to produce results that look OK. My understanding is that doing the computation along the Y/latitude axis is better because latitude stays (mostly) consistent is it goes around the (mostly) spherical globe.

I don't like this solution because I have to do this computation at the top of each Canvas DrawScope invocation. The Projection seems to have the information it needs to compute LatLngs directly to pixels without having to know the draw target extents. Why can't it do distances?

I've considered two other approaches:

1

I can measure the distance in meters between two LatLngs with something like the following:

fun LatLng.distance(to: LatLng): Distance {
    val result = floatArrayOf(0.0f)
    Location.distanceBetween(this.latitude, this.longitude, to.latitude, to.longitude, result)
    return result[0].double
}

With that I could just compute the distance in pixels between the northeast/southwest coords by using the projection to map those to screen points, and then measuring the pixel distance between them, and then use the above to get the denominator of the ratio by dividing by the distance in meters between the same two coordinates.

This avoids having to get involved with the Canvas DrawScope size.height, in fact I could just make it a computed property of the CameraPosition objects. But boy, what a lot of computation to repeatedly invoke. Seems round about.

2

I gather that the zoom value can somehow be used to compute a pixels per meter density. I have found various posts that seem to indicate this value can be used to compute pixels per meter directly. Using one of those, I came up with the following:

val pixels = 256 * 2.0.pow(positionState.position.zoom.toDouble())
val meters = PI * 2 * 40_075_017 * cos(Math.toRadians(positionState.position.target.latitude))
val pixelsPerMeter = pixels / meters

If this would work, I would like this better. Seems more direct. Oddly, if I log the value, it is consistently off by a factor of ~18.5 compared to the original. There seems to be a factor I'm missing that I don't understand correctly.

Obviously, I've tried things (3 of them). Which is the best way to about it? It frustrates me that the coord to pixel point was so easy, but the distance part has been unclear how to do this in this environment.

Travis Griggs
  • 21,522
  • 19
  • 91
  • 167
  • It's quite old. But will this somehow help? https://stackoverflow.com/questions/3385117/how-to-change-1-meter-to-pixel-distance – Yrll May 18 '23 at 03:53

0 Answers0