问题
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;
}
回答1:
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.
回答2:
I use a simple formula below:
public int getZoomLevel(Circle circle) {
if (circle != null){
double radius = circle.getRadius();
double scale = radius / 500;
zoomLevel =(int) (16 - Math.log(scale) / Math.log(2));
}
return zoomLevel;
}
You can also replace circle with its specific radius.
回答3:
This page is extremely helpful for explaining all this stuff (distance between two lat-lng pairs, etc).
6371 is the approximate radius of the earth in kilometers.
57.2958 is 180/pi
also, check out these Mercator projection calculations for converting between latitude-longitude and X-Y: http://wiki.openstreetmap.org/wiki/Mercator
回答4:
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).
回答5:
I needed the opposite: given a particular radius at a certain zoom level (i.e., 40 meters at zoom level 15), I needed the radii at other zoom levels that showed the same circle size (graphically) in the map. To do this:
// after retrieving the googleMap from either getMap() or getMapAsync()...
// we want a circle with r=40 meters at zoom level 15
double base = 40 / zoomToDistFactor(15);
final Circle circle = googleMap.addCircle(new CircleOptions()
.center(center)
.radius(40)
.fillColor(Color.LTGRAY)
);
googleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
@Override
public void onCameraMove() {
CameraPosition cameraPosition = googleMap.getCameraPosition();
LatLng center = cameraPosition.target;
float z2 = cameraPosition.zoom;
double newR = base * zoomToDistFactor(z2);
circle.setRadius(newR);
}
});
// ...
private double zoomToDistFactor(double z) {
return Math.pow(2,8-z) / 1.6446;
}
I figure I'd put it here to save anyone else the effort to get this conversion.
As a side note, moving the circle around like this in a cameramovelistener made the circle motion very choppy. I ended up putting a view in the center of the enclosing MapView that simply drew a small circle.
回答6:
I think he gets this function:
function calculateZoom(WidthPixel,Ratio,Lat,Length){
// from a segment Length (km),
// with size ratio of the of the segment expected on a map (70%),
// with a map widthpixel size (100px), and a latitude (45°) we can ge the best Zoom
// earth radius : 6,378,137m, earth is a perfect ball; perimeter at the equator = 40,075,016.7 m
// the full world on googlemap is available in a box of 256 px; It has a ratio of 156543.03392 (px/m)
// for Z = 0;
// pixel scale at the Lat_level is ( 156543,03392 * cos ( PI * (Lat/180) ))
// map scale increase at the rate of square root of Z
//
Length = Length *1000; //Length is in Km
var k = WidthPixel * 156543.03392 * Math.cos(Lat * Math.PI / 180); //k = perimeter of the world at the Lat_level, for Z=0
var myZoom = Math.round( Math.log( (Ratio * k)/(Length*100) )/Math.LN2 );
myZoom = myZoom -1; // z start from 0 instead of 1
//console.log("calculateZoom: width "+WidthPixel+" Ratio "+Ratio+" Lat "+Lat+" length "+Length+" (m) calculated zoom "+ myZoom);
// not used but it could be usefull for some: Part of the world size a the Lat
MapDim = k /Math.pow(2,myZoom);
//console.log("calculateZoom: size of the map at the Lat: "+MapDim + " meters.");
//console.log("calculateZoom: world perimeter at the Lat: " +k+ " meters.");
return(myZoom);
}
来源:https://stackoverflow.com/questions/5939983/how-does-this-google-maps-zoom-level-calculation-work