I know what the input and outputs are, but I'm just not sure how or why it works.
This code is being used to, given a min and max longitude/latitude (a square) that contains a set of points, determine the maximum zoom level on Google Maps that will still display all of those points. The original author is gone, so I'm not sure what some of these numbers are even for (i.e. 6371 and 8). Consider it a puzzle =D
int mapdisplay = 322; //min of height and width of element which contains the map
double dist = (6371 * Math.acos(Math.sin(min_lat / 57.2958) * Math.sin(max_lat / 57.2958) +
(Math.cos(min_lat / 57.2958) * Math.cos(max_lat / 57.2958) * Math.cos((max_lon / 57.2958) - (min_lon / 57.2958)))));
double zoom = Math.floor(8 - Math.log(1.6446 * dist / Math.sqrt(2 * (mapdisplay * mapdisplay))) / Math.log (2));
if(numPoints == 1 || ((min_lat == max_lat)&&(min_lon == max_lon))){
zoom = 11;
}
Distance per pixel math As tiles are 256-pixels wide, the horizontal distance represented by one pixel is: Spixel = Stile / 256 = C ∙ cos(latitude) / 2. For example on the equator and at zoom level 0, we get 40 075 016.686 / 256 ≈ 156 543.03 (in meters per pixel).
The Google Maps API provides map tiles at various zoom levels for map type imagery. Most roadmap imagery is available from zoom levels 0 to 18, for example. Satellite imagery varies more widely as this imagery is not generated, but directly photographed.
Some numbers can be explained easily
MeanRadiusEarthInKm = 6371 (according to IUGG)
DegToRadDivisor = 180/PI = 57.2958
And again the zoom level doubles the size with each step, i.e. increase the zoomlevel by one halfs the size on the screen.
zoom = 8 - log(factor * dist) / log(2) = 8 - log_2(factor * dist)
=> dist = 2^(8-zoom) / factor
From the numbers we find that zoom level eight corresponds to a distance of 276.89km.
After many attempts I made a solution. I assume that you have a padding outside a radius (for instance, if you have radius = 10000 m, then it will be 2500 m left and right). Also you should have an accuracy in meters. You can set suitable zoom with recoursion (binary search). If you change moveCamera
to animateCamera
, you will get an interesting animation of search. The larger radius, the more accurate zoom value you will receive. This is a usual binary search.
private fun getCircleZoomValue(latitude: Double, longitude: Double, radius: Double,
minZoom: Float, maxZoom: Float): Float {
val position = LatLng(latitude, longitude)
val currZoom = (minZoom + maxZoom) / 2
val camera = CameraUpdateFactory.newLatLngZoom(position, currZoom)
googleMap!!.moveCamera(camera)
val results = FloatArray(1)
val topLeft = googleMap!!.projection.visibleRegion.farLeft
val topRight = googleMap!!.projection.visibleRegion.farRight
Location.distanceBetween(topLeft.latitude, topLeft.longitude, topRight.latitude,
topRight.longitude, results)
// Difference between visible width in meters and 2.5 * radius.
val delta = results[0] - 2.5 * radius
val accuracy = 10 // 10 meters.
return when {
delta < -accuracy -> getCircleZoomValue(latitude, longitude, radius, minZoom,
currZoom)
delta > accuracy -> getCircleZoomValue(latitude, longitude, radius, currZoom,
maxZoom)
else -> currZoom
}
}
Usage:
if (googleMap != null) {
val zoom = getCircleZoomValue(latitude, longitude, radius, googleMap!!.minZoomLevel,
googleMap!!.maxZoomLevel)
}
You should call this method not earlier than inside first event of googleMap?.setOnCameraIdleListener
, see animateCamera works and moveCamera doesn't for GoogleMap - Android. If you call it right after onMapReady
, you will have a wrong distance, because the map will not draw itself that time.
Warning! Zoom level depends on location (latitude). So that the circle will have different sizes with the same zoom level depending on distance from equator (see Determine a reasonable zoom level for Google Maps given location accuracy).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With