Метод tileProvider getTile - нужно перевести x и y в lat/long

Портирование приложения iOS на Android и использование Google Maps Android API v2. Приложению необходимо нанести на карту наложение тепловой карты.

Пока, похоже, лучший вариант - использовать TileOverlay и реализовать пользовательский TileProvider. В методе getTile мой метод задан x, y и zoom, и ему нужно вернуть растровое изображение в форме Tile. Все идет нормально.

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

  • Как определить, содержит ли плитка, представленную x, y и зумом, значение lat/long элемента heatmap?
  • Как перевести lat/long элемента heatmap в координаты x/y растрового изображения.

Благодарим за помощь!

UPDATE

Благодаря ответу MaciejGórski ниже и выполнению marcin я смог получить ответы на 1-й половине моего вопроса, но мне все еще нужна помощь со второй частью. Чтобы уточнить, мне нужна функция, чтобы вернуть x/y координаты плитки для заданного lat/long. Я пробовал обратить вспять вычисления MaciejGórski и marcin ответ без везения.

public static Point fromLatLng(LatLng latlng, int zoom){
    int noTiles = (1 << zoom);
    double longitudeSpan = 360.0 / noTiles;
    double mercator = fromLatitude(latlng.latitude);
    int y = ((int)(mercator / 360 * noTiles)) + 180;
    int x = (int)(latlng.longitude / longitudeSpan) + 180;
    return new Point(x, y);
}

Любая помощь приветствуется!

Ответы

Ответ 1

Это сработало для меня:

double n = Math.pow(2, zoom);
double longitudeMin = x/n * 360 -180;
double lat_rad = Math.atan(Math.sinh(Math.PI * (1 - 2 * y/n)));
double latitudeMin = lat_rad * 180/Math.PI;

double longitudeMax = (x + 1)/n * 360 -180;
lat_rad = Math.atan(Math.sinh(Math.PI * (1 - 2 * (y + 1)/n)));
double latitudeMax = lat_rad * 180/Math.PI;

Литература: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames

Ответ 2

При уровне масштабирования 0 существует только одна плитка (x = 0, y = 0). На следующем уровне масштабирования количество плиток увеличивается в четыре раза (удваивается по x и y).

Это означает, что уровень масштабирования W, x может быть значением в диапазоне < 0, 1 < Вт).

Из документации:

Координаты плиток измеряются в верхнем левом (северо-западном) углу карты. При уровне масштабирования N значения x координат плитки варьируются от 0 до 2N - 1 и увеличиваются с запада на восток, а значения y варьируются от 0 до 2N - 1 и увеличиваются с севера на юг.

Вы можете достичь этого, используя простые вычисления.

Для долготы это просто:

double longitudeMin = (((double) x) / (1 << zoom)) * 360 - 180;
double longitudeMax = (((double) x + 1) / (1 << zoom)) * 360 - 180;
longitudeMax = Double.longBitsToDouble(Double.doubleToLongBits(longitudeMax) - 1); // adjust

Здесь x сначала масштабируется в < 0,1), затем в < -180, 180).

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

Для широты это будет немного сложнее, потому что Google Maps использует прогноз Меркатора.

Сначала вы масштабируете y так же, как в диапазоне < -180,180). Обратите внимание, что значения должны быть отменены.

double mercatorMax = 180 - (((double) y) / (1 << zoom)) * 360;
double mercatorMin = 180 - (((double) y + 1) / (1 << zoom)) * 360;

Теперь вы используете магическую функцию, которая выполняет проекцию Меркатора (от SphericalMercator.java):

public static double toLatitude(double mercator) {
    double radians = Math.atan(Math.exp(Math.toRadians(mercator)));
    return Math.toDegrees(2 * radians) - 90;
}

latitudeMax = SphericalMercator.toLatitude(mercatorMax);
latitudeMin = SphericalMercator.toLatitude(mercatorMin);
latitudeMin = Double.longBitsToDouble(Double.doubleToLongBits(latitudeMin) + 1);

Это было напечатано из памяти и не было протестировано каким-либо образом, поэтому, если там есть ошибка, поставьте комментарий, и я исправлю это.

Ответ 3

MaciejGórski, если вы не возражаете (если вы это сделаете, я удалю это сообщение) Я скомпилировал ваш код в готовый к использованию метод:

private LatLngBounds boundsOfTile(int x, int y, int zoom) {
    int noTiles = (1 << zoom);
    double longitudeSpan = 360.0 / noTiles;
    double longitudeMin = -180.0 + x * longitudeSpan;

    double mercatorMax = 180 - (((double) y) / noTiles) * 360;
    double mercatorMin = 180 - (((double) y + 1) / noTiles) * 360;
    double latitudeMax = toLatitude(mercatorMax);
    double latitudeMin = toLatitude(mercatorMin);

    LatLngBounds bounds = new LatLngBounds(new LatLng(latitudeMin, longitudeMin), new LatLng(latitudeMax, longitudeMin + longitudeSpan));
    return bounds;
}