Android: Как установить уровень масштабирования вида карты в радиусе 1 км вокруг моего текущего местоположения?

Я хочу, чтобы изображение было увеличено до радиуса 1 км, но не могу понять, как?

Документ говорит, что уровень масштабирования 1 будет отображать экваториальные экваторы на 256 пикселей. Итак, как мне рассчитать, какой уровень масштабирования мне нужно установить, чтобы отображение карты отображало область в радиусе 1KM?

ОБНОВИТЬ:
Прочитав несколько сообщений в блоге, я написал следующий код:

private int calculateZoomLevel() { double equatorLength = 6378140; // in meters double widthInPixels = screenWidth; double metersPerPixel = equatorLength / 256; int zoomLevel = 1; while ((metersPerPixel * widthInPixels) > 2000) { metersPerPixel /= 2; ++zoomLevel; } Log.i("ADNAN", "zoom level = "+zoomLevel); return zoomLevel; } 

Идея состоит в том, что сначала я вычисляю Meters на пиксель на уровне масштабирования 1, который согласно google показывает экватор земли с использованием 256 пикселей. Теперь каждый последующий уровень увеличения увеличивается на уровень 2, поэтому я половину метров на пиксель для каждого уровня масштабирования. Я делаю это, пока у меня не будет уровня масштабирования, где метры на пиксель, умноженные на ширину экрана, дают мне меньше 2000, т.е. 2 км в поперечнике.

Но я не думаю, что уровень масштабирования, который я получаю, показывает карту радиуса 2Km. Может кто-нибудь сказать мне, что я делаю неправильно здесь?

Хотя этот ответ логичен, и я нахожу его работающим, но результаты не точны. Я не знаю, почему, но я устал от этого подхода, и этот метод намного точнее.

1) Сделайте круг на объекте с нулевым радиусом

 Circle circle = mGoogleMap.addCircle(new CircleOptions().center(new LatLng(latitude, longitude)).radius(getRadiusInMeters()).strokeColor(Color.RED)); circle.setVisible(true); getZoomLevel(circle); 

2) Передайте этот объект этой функции и установите уровень масштабирования. Вот ссылка.

 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; } 

Следующий код – это то, что в конечном итоге используется. Учитывая ширину экрана и тот факт, что при уровне масштабирования 1 экватор Земли имеет длину 256 пикселей, а каждый последующий уровень увеличения удваивает количество пикселей, необходимых для представления экватора Земли, следующая функция возвращает уровень масштабирования, где на экране будет отображаться область Ширины 2Km.

 private int calculateZoomLevel(int screenWidth) { double equatorLength = 40075004; // in meters double widthInPixels = screenWidth; double metersPerPixel = equatorLength / 256; int zoomLevel = 1; while ((metersPerPixel * widthInPixels) > 2000) { metersPerPixel /= 2; ++zoomLevel; } Log.i("ADNAN", "zoom level = "+zoomLevel); return zoomLevel; } 

Я закончил использование utils из:

https://github.com/googlemaps/android-maps-utils

Я извлек класс из lib, поэтому вам не нужна вся библиотека. Вместо установки уровня масштабирования вы используете границы. Результат тот же.

Код, чтобы показать ровно 1 километр:

 animateToMeters(1000); private void animateToMeters(int meters){ int mapHeightInDP = 200; Resources r = getResources(); int mapSideInPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mapHeightInDP, r.getDisplayMetrics()); LatLng point = new LatLng(0, 0); LatLngBounds latLngBounds = calculateBounds(point, meters); if(latLngBounds != null){ cameraUpdate = CameraUpdateFactory.newLatLngBounds(latLngBounds, mapSideInPixels, mapSideInPixels, MARKER_BOUNDS); if(mMap != null) mMap.animateCamera(cameraUpdate); } } private LatLngBounds calculateBounds(LatLng center, double radius) { return new LatLngBounds.Builder(). include(SphericalUtil.computeOffset(center, radius, 0)). include(SphericalUtil.computeOffset(center, radius, 90)). include(SphericalUtil.computeOffset(center, radius, 180)). include(SphericalUtil.computeOffset(center, radius, 270)).build(); } 

Класс, извлеченный (слегка измененный) из lib:

 public class SphericalUtil { static final double EARTH_RADIUS = 6371009; /** * Returns hav() of distance from (lat1, lng1) to (lat2, lng2) on the unit sphere. */ static double havDistance(double lat1, double lat2, double dLng) { return hav(lat1 - lat2) + hav(dLng) * cos(lat1) * cos(lat2); } /** * Returns haversine(angle-in-radians). * hav(x) == (1 - cos(x)) / 2 == sin(x / 2)^2. */ static double hav(double x) { double sinHalf = sin(x * 0.5); return sinHalf * sinHalf; } /** * Computes inverse haversine. Has good numerical stability around 0. * arcHav(x) == acos(1 - 2 * x) == 2 * asin(sqrt(x)). * The argument must be in [0, 1], and the result is positive. */ static double arcHav(double x) { return 2 * asin(sqrt(x)); } private SphericalUtil() {} /** * Returns the heading from one LatLng to another LatLng. Headings are * expressed in degrees clockwise from North within the range [-180,180). * @return The heading in degrees clockwise from north. */ public static double computeHeading(LatLng from, LatLng to) { // http://williams.best.vwh.net/avform.htm#Crs double fromLat = toRadians(from.latitude); double fromLng = toRadians(from.longitude); double toLat = toRadians(to.latitude); double toLng = toRadians(to.longitude); double dLng = toLng - fromLng; double heading = atan2( sin(dLng) * cos(toLat), cos(fromLat) * sin(toLat) - sin(fromLat) * cos(toLat) * cos(dLng)); return wrap(toDegrees(heading), -180, 180); } /** * Returns the LatLng resulting from moving a distance from an origin * in the specified heading (expressed in degrees clockwise from north). * @param from The LatLng from which to start. * @param distance The distance to travel. * @param heading The heading in degrees clockwise from north. */ public static LatLng computeOffset(LatLng from, double distance, double heading) { distance /= EARTH_RADIUS; heading = toRadians(heading); // http://williams.best.vwh.net/avform.htm#LL double fromLat = toRadians(from.latitude); double fromLng = toRadians(from.longitude); double cosDistance = cos(distance); double sinDistance = sin(distance); double sinFromLat = sin(fromLat); double cosFromLat = cos(fromLat); double sinLat = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading); double dLng = atan2( sinDistance * cosFromLat * sin(heading), cosDistance - sinFromLat * sinLat); return new LatLng(toDegrees(asin(sinLat)), toDegrees(fromLng + dLng)); } /** * Returns the location of origin when provided with a LatLng destination, * meters travelled and original heading. Headings are expressed in degrees * clockwise from North. This function returns null when no solution is * available. * @param to The destination LatLng. * @param distance The distance travelled, in meters. * @param heading The heading in degrees clockwise from north. */ public static LatLng computeOffsetOrigin(LatLng to, double distance, double heading) { heading = toRadians(heading); distance /= EARTH_RADIUS; // http://lists.maptools.org/pipermail/proj/2008-October/003939.html double n1 = cos(distance); double n2 = sin(distance) * cos(heading); double n3 = sin(distance) * sin(heading); double n4 = sin(toRadians(to.latitude)); // There are two solutions for b. b = n2 * n4 +/- sqrt(), one solution results // in the latitude outside the [-90, 90] range. We first try one solution and // back off to the other if we are outside that range. double n12 = n1 * n1; double discriminant = n2 * n2 * n12 + n12 * n12 - n12 * n4 * n4; if (discriminant < 0) { // No real solution which would make sense in LatLng-space. return null; } double b = n2 * n4 + sqrt(discriminant); b /= n1 * n1 + n2 * n2; double a = (n4 - n2 * b) / n1; double fromLatRadians = atan2(a, b); if (fromLatRadians < -PI / 2 || fromLatRadians > PI / 2) { b = n2 * n4 - sqrt(discriminant); b /= n1 * n1 + n2 * n2; fromLatRadians = atan2(a, b); } if (fromLatRadians < -PI / 2 || fromLatRadians > PI / 2) { // No solution which would make sense in LatLng-space. return null; } double fromLngRadians = toRadians(to.longitude) - atan2(n3, n1 * cos(fromLatRadians) - n2 * sin(fromLatRadians)); return new LatLng(toDegrees(fromLatRadians), toDegrees(fromLngRadians)); } /** * Returns the LatLng which lies the given fraction of the way between the * origin LatLng and the destination LatLng. * @param from The LatLng from which to start. * @param to The LatLng toward which to travel. * @param fraction A fraction of the distance to travel. * @return The interpolated LatLng. */ public static LatLng interpolate(LatLng from, LatLng to, double fraction) { // http://en.wikipedia.org/wiki/Slerp double fromLat = toRadians(from.latitude); double fromLng = toRadians(from.longitude); double toLat = toRadians(to.latitude); double toLng = toRadians(to.longitude); double cosFromLat = cos(fromLat); double cosToLat = cos(toLat); // Computes Spherical interpolation coefficients. double angle = computeAngleBetween(from, to); double sinAngle = sin(angle); if (sinAngle < 1E-6) { return from; } double a = sin((1 - fraction) * angle) / sinAngle; double b = sin(fraction * angle) / sinAngle; // Converts from polar to vector and interpolate. double x = a * cosFromLat * cos(fromLng) + b * cosToLat * cos(toLng); double y = a * cosFromLat * sin(fromLng) + b * cosToLat * sin(toLng); double z = a * sin(fromLat) + b * sin(toLat); // Converts interpolated vector back to polar. double lat = atan2(z, sqrt(x * x + y * y)); double lng = atan2(y, x); return new LatLng(toDegrees(lat), toDegrees(lng)); } /** * Returns distance on the unit sphere; the arguments are in radians. */ private static double distanceRadians(double lat1, double lng1, double lat2, double lng2) { return arcHav(havDistance(lat1, lat2, lng1 - lng2)); } /** * Returns the angle between two LatLngs, in radians. This is the same as the distance * on the unit sphere. */ static double computeAngleBetween(LatLng from, LatLng to) { return distanceRadians(toRadians(from.latitude), toRadians(from.longitude), toRadians(to.latitude), toRadians(to.longitude)); } /** * Returns the distance between two LatLngs, in meters. */ public static double computeDistanceBetween(LatLng from, LatLng to) { return computeAngleBetween(from, to) * EARTH_RADIUS; } /** * Returns the length of the given path, in meters, on Earth. */ public static double computeLength(List<LatLng> path) { if (path.size() < 2) { return 0; } double length = 0; LatLng prev = path.get(0); double prevLat = toRadians(prev.latitude); double prevLng = toRadians(prev.longitude); for (LatLng point : path) { double lat = toRadians(point.latitude); double lng = toRadians(point.longitude); length += distanceRadians(prevLat, prevLng, lat, lng); prevLat = lat; prevLng = lng; } return length * EARTH_RADIUS; } /** * Returns the area of a closed path on Earth. * @param path A closed path. * @return The path's area in square meters. */ public static double computeArea(List<LatLng> path) { return abs(computeSignedArea(path)); } /** * Returns the signed area of a closed path on Earth. The sign of the area may be used to * determine the orientation of the path. * "inside" is the surface that does not contain the South Pole. * @param path A closed path. * @return The loop's area in square meters. */ public static double computeSignedArea(List<LatLng> path) { return computeSignedArea(path, EARTH_RADIUS); } /** * Returns the signed area of a closed path on a sphere of given radius. * The computed area uses the same units as the radius squared. * Used by SphericalUtilTest. */ static double computeSignedArea(List<LatLng> path, double radius) { int size = path.size(); if (size < 3) { return 0; } double total = 0; LatLng prev = path.get(size - 1); double prevTanLat = tan((PI / 2 - toRadians(prev.latitude)) / 2); double prevLng = toRadians(prev.longitude); // For each edge, accumulate the signed area of the triangle formed by the North Pole // and that edge ("polar triangle"). for (LatLng point : path) { double tanLat = tan((PI / 2 - toRadians(point.latitude)) / 2); double lng = toRadians(point.longitude); total += polarTriangleArea(tanLat, lng, prevTanLat, prevLng); prevTanLat = tanLat; prevLng = lng; } return total * (radius * radius); } /** * Returns the signed area of a triangle which has North Pole as a vertex. * Formula derived from "Area of a spherical triangle given two edges and the included angle" * as per "Spherical Trigonometry" by Todhunter, page 71, section 103, point 2. * See http://books.google.com/books?id=3uBHAAAAIAAJ&pg=PA71 * The arguments named "tan" are tan((pi/2 - latitude)/2). */ private static double polarTriangleArea(double tan1, double lng1, double tan2, double lng2) { double deltaLng = lng1 - lng2; double t = tan1 * tan2; return 2 * atan2(t * sin(deltaLng), 1 + t * cos(deltaLng)); } /** * Wraps the given value into the inclusive-exclusive interval between min and max. * @param n The value to wrap. * @param min The minimum. * @param max The maximum. */ static double wrap(double n, double min, double max) { return (n >= min && n < max) ? n : (mod(n - min, max - min) + min); } /** * Returns the non-negative remainder of x / m. * @param x The operand. * @param m The modulus. */ static double mod(double x, double m) { return ((x % m) + m) % m; } } 

Карты Google, похоже, тесно работают с милями / пикселями. При масштабировании = 13, 1 миля = 100 пикселей. 2 мили = 200 пикселей. Каждый зум-рендер увеличивается или уменьшается в 2 раза. Поэтому при Zoom 14, 1 миля = 200 пикселей и при масштабировании 12, 1 миля = 50 пикселей.

Я преобразовал принятый ответ, чтобы вернуть двойное значение, поскольку в библиотеке Google Maps Google используются уровни масштабирования с плавающей запятой, а также учитываются широты от экватора.

 public static double getZoomForMetersWide ( final double desiredMeters, final double mapWidth, final double latitude ) { final double latitudinalAdjustment = Math.cos( Math.PI * latitude / 180.0 ); final double arg = EQUATOR_LENGTH * mapWidth * latitudinalAdjustment / ( desiredMeters * 256.0 ); return Math.log( arg ) / Math.log( 2.0 ); } 

В целом, для достижения наилучших результатов на Android не проходит реальный подсчет пикселей, а размер масштабируется для плотности пикселей устройства.

 DisplayMetrics metrics = getResources().getDisplayMetrics(); float mapWidth = mapView.getWidth() / metrics.scaledDensity; 

Надеюсь, это поможет кому-то.