Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TileProvider method getTile - need to translate x and y to lat/long

I’m porting an iOS app to Android, and using Google Maps Android API v2. The application needs to draw a heatmap overlay onto the map.

So far, it looks like the best option is to use a TileOverlay and implement a custom TileProvider. In the method getTile, my method is given x, y, and zoom, and needs to return a bitmap in the form of a Tile. So far, so good.

I have an array of heatmap items that I will use to draw radial gradients onto the bitmap, each with a lat/long. I am having trouble with the following two tasks:

  1. How do I determine if the tile represented by x, y, and zoom contains the lat/long of the heatmap item?
  2. How do I translate the lat/long of the heatmap item to x/y coordinates of the bitmap.

Thank you for your help!

UPDATE

Thanks to MaciejGórski's answer below, and marcin's implementation I was able to get the 1st half of my question answered, but I still need help with the 2nd part. To clarify, I need a function to return the x/y coordinates of the tile for a specified lat/long. I've tried reversing the calculations of MaciejGórski's and marcin's answer with no luck.

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

Any help is appreciated!

like image 849
azcoastal Avatar asked Jun 02 '13 15:06

azcoastal


3 Answers

This worked for me:

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;

References: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames

like image 51
Swati Pardeshi Avatar answered Nov 19 '22 19:11

Swati Pardeshi


On zoom level 0, there is only one tile (x=0,y=0). On next zoom level number of tiles are quadrupled (doubled on x and y).

This means on zoom level W, x may be a value in range <0, 1 << W).

From documentation:

The coordinates of the tiles are measured from the top left (northwest) corner of the map. At zoom level N, the x values of the tile coordinates range from 0 to 2N - 1 and increase from west to east and the y values range from 0 to 2N - 1 and increase from north to south.

You can achieve this using simple calculations.

For longitude this is straightforward :

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

Here x is first scaled into <0,1), then into <-180,180).

The max value is adjusted, so it doesn't overlap with the next area. You may skip this.

For latitude this will be a bit harder, because Google Maps use Mercator projection.

First you scale y just like it was in range <-180,180). Note that the values need to be reversed.

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

Now you use a magical function that does Mercator projection (from 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);

This was was typed from memory and was not tested in any way, so if there is an error there, please put a comment and I will fix it.

like image 28
MaciejGórski Avatar answered Nov 19 '22 19:11

MaciejGórski


MaciejGórski if you don't mind (if you do, I will remove this post) I compiled your code into ready to use method:

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;
}
like image 8
marcin Avatar answered Nov 19 '22 19:11

marcin