Метод TileProvider getTile - необходимо перевести x и y в широту/долготу

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

На данный момент кажется, что лучший вариант — использовать TileOverlay и реализовать собственный TileProvider. В методе getTile моему методу задаются x, y и масштаб, и он должен возвращать растровое изображение в виде Tile. Все идет нормально.

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

  1. Как определить, содержит ли плитка, представленная x, y и масштабирование, широту/долготу элемента тепловой карты?
  2. Как преобразовать широту/долготу элемента тепловой карты в координаты x/y растрового изображения.

Спасибо за помощь!

ОБНОВЛЕНИЕ

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

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

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


person azcoastal    schedule 02.06.2013    source источник
comment
Вы нашли способ преобразовать LatLng Z в X Y? я нашел функцию, которая частично работает, но она дает мне неправильный y.   -  person Hugo Alves    schedule 08.11.2013


Ответы (3)


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

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

person Swati Pardeshi    schedule 01.10.2013
comment
Спасибо за ссылку, которая предоставляет фрагменты кода для выполнения обеих операций на наиболее распространенных языках! - person Tim Autin; 13.11.2013

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

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

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

Координаты тайлов отсчитываются от верхнего левого (северо-западного) угла карты. На уровне масштабирования 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 используют проекцию Меркатора.

Сначала вы масштабируете 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);

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

person MaciejGórski    schedule 04.06.2013
comment
Хо ли дерьмо! Я как бы ожидал встроенной функции, о которой я не знал. Если требуется так много кода, возможно, TileOverlay — неправильный способ рисования на карте. Это встроено в Google Maps Javascript v3, как и в iOS MKOverlayView. - person azcoastal; 04.06.2013
comment
Просматривая ваш ответ, я заметил, что вы рассчитываете долготу от x, а также широту. Это опечатка? Я предполагаю, что вы имели в виду: double mercatorMax = 180 - (((double) y) / (1 ‹‹ zoom)) * 360; Кроме того, есть ли аналогичный способ преобразования широты/долготы в x/y? - person azcoastal; 04.06.2013
comment
@azcoastal Да. Это была ошибка копирования-вставки. Я исправил это. Для широты/долготы вам нужно будет просто назвать все в обратном порядке, начиная с SphericalMercator.fromLatitude по ссылке, которую я указал в ответе. На самом деле не так много кода. - person MaciejGórski; 04.06.2013
comment
Ненавижу спрашивать, но не могли бы вы показать, что вы подразумеваете под обратным порядком? С вашей помощью я смогу быстро все исправить и принять ответ. - person azcoastal; 06.06.2013
comment
@azcoastal Это означает то же самое, что если вы хотите вычислить x из y = x + 1, вы берете x слева, а все остальное справа: -x = -y + 1; x = y - 1 и сделайте это от последней строки исходного кода к первой. - person MaciejGórski; 06.06.2013
comment
это выглядит правильно для вас? public static Point fromLatLng(LatLng latlng, int zoom){ int noTiles = (1 ‹‹ zoom); двойная долготаSpan = 360,0/noTiles; двойной меркатор = fromLatitude (latlng.latitude); int y = ((int)(mercator * noTiles/360)) + 180; int x = (int)(latlng.longitude / longitudeSpan) + 180; вернуть новую точку (x, y); } - person azcoastal; 06.06.2013
comment
@azcoastal Извините, я не могу читать код в комментариях. Лучше всего создать для него модульные тесты, а затем написать код и посмотреть, не сработают ли они. - person MaciejGórski; 06.06.2013

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;
}
person marcin    schedule 05.06.2013
comment
Хорошо. Возвращает ли код правильные значения? У меня пока нет возможности протестировать. - person MaciejGórski; 05.06.2013
comment
Насколько я знаю: да, это так :) - person marcin; 05.06.2013
comment
@marcin, у меня может быть это задом наперед, так что поправьте меня, если я ошибаюсь ... Поскольку конструктор для LatLngBounds ищет юго-западный угол в качестве первого параметра, должен ли он быть «новым LatLng (latitudeMax, longitudeMin)» как 1-й параметр, затем «новый LatLng (широтаMin, longitudeMin + longitudeSpan)» в качестве 2-го параметра? Я думал, что x & y представляют собой северо-западный угол плитки. - person azcoastal; 06.06.2013
comment
широта (в градусах) увеличивается с запада на восток и с юга на север, поэтому точка юго-запада будет new LatLng(latitudeMin, longitudeMin), а северо-восток будет new LatLng(latitudeMax, longitudeMax), где longitudeMax=longitudeMin+longitudeSpan - person marcin; 06.06.2013
comment
@azcoastal x & y represent the NW corner of the tile Нет. x, y и масштабирование представляют плитку в целом. Это как идентификатор. - person MaciejGórski; 06.06.2013
comment
Что следует остерегаться: LatLng преобразует значение долготы 180 в -180, поэтому, если вы попытаетесь представить тайл [0,0,0] с помощью LatLngBounds, его диапазон долготы будет от -180 до -180. Такой LatLngBounds имеет нулевую ширину; contains(LatLng) возвращает false для любого местоположения. - person Kevin Krumwiede; 03.01.2017