­

#zoom

Google Maps Calculate Zoom

/**
* Calculates the zoom level for the StreetWatchMap using the formula given by a Google employee (maps team)
* in https://groups.google.com/d/msg/google-maps-js-api-v3/hDRO4oHVSeM/osOYQYXg2oUJ. For simplification
* the formula has been duplicated and simplified below
*
* R = RequestedMeterRadius
* S = MinScreenSizeInDp
* B = BaseMetersPerPixel (for the type of map)
* L = Latitude (Center of focus)
* Z = ZoomLevel
*
* (2R)/S = (B * Cos(L * PI / 180)) / (2^Z)
*
* which solving for Z becomes
* Z = log2((B * Cos(L * PI / 180) * S) / (2R))
*
* @param viewSize The size of the [MapView] in DP
* @param geoLocation The location at the center of the view
* @param requestedRadius The radius around the [geoLocation] in Meters
*/
fun calculateZoom(viewSize: Point, geoLocation: GeoLocation, requestedRadius: Int): Float {
val latitudeRadian = geoLocation.latitude * Math.PI / 180
val top = BASE_METER_PER_DP * Math.cos(latitudeRadian) * Math.min(viewSize.x, viewSize.y)
val total = top / (requestedRadius * 2).toFloat()
return total.log(2.0).toFloat()
}
val zoomLevel = calculateZoom(Point(mapView.width.pxToDp(), mapView.height.pxToDp()), geoLocation, radius)
val cameraUpdate = CameraUpdateFactory.newCameraPosition(
CameraPosition.Builder().apply {
target(LatLng(geoLocation.latitude, geoLocation.longitude))
zoom(zoomLevel)
}.build()
)
map.moveCamera(cameraUpdate);

GoogleMap workaround to keep bearing

/**
* map.animateCamera(CameraUpdateFactory.newLatLngBounds()) will lose ignore current bearing and rotate the camera
*/
fun GoogleMap.animateToBounds(bounds: LatLngBounds, width: Int, height: Int) = animateCamera(boundsToCameraUpdate(bounds, width, height))
/**
* map.moveCamera(CameraUpdateFactory.newLatLngBounds()) will lose ignore current bearing and rotate the camera
*/
fun GoogleMap.moveCameraToBounds(bounds: LatLngBounds, width: Int, height: Int) = moveCamera(boundsToCameraUpdate(bounds, width, height))
fun GoogleMap.boundsToCameraUpdate(bounds: LatLngBounds, width: Int, height: Int): CameraUpdate {
val zoom = getBoundsZoomLevel(cameraPosition.zoom, bounds, width, height, GeneralMapStrategy.MAX_ZOOM)
return CameraUpdateFactory.newCameraPosition(
CameraPosition.Builder()
.target(bounds.center)
.zoom(if (zoom >= GeneralMapStrategy.MIN_ZOOM) zoom else cameraPosition.zoom)
.bearing(cameraPosition.bearing)
.tilt(cameraPosition.tilt)
.build()
)
}
/**
* north west ----------- north east
* | |
* | |
* | |
* south west ---------- south east
*
* note: only tested for north west hemisphere
*/
fun GoogleMap.getBoundsZoomLevel(currentZoom: Float, bounds: LatLngBounds, mapWidthPx: Int, mapHeightPx: Int, maxZoom: Float, padding: Float? = null): Float {
val nePoint = projection.toScreenLocation(bounds.northeast)
val swPoint = projection.toScreenLocation(bounds.southwest)
val width = (swPoint.x - nePoint.x) + (padding ?: 128f.px)
val height = (swPoint.y - nePoint.y) + (padding ?: 128f.px)
val latZoom = currentZoom + log2(mapWidthPx / width)
val lngZoom = currentZoom + log2(mapHeightPx / height)
val result = min(latZoom, lngZoom)
return min(result, maxZoom)
}