Getting lon/lat from pixel coords in Google Static Map
Asked Answered
P

4

9

I have a JAVA project to do using Google Static Maps and after hours and hours working, I can't get a thing working, I will explain everything and I hope someone will be able to help me.

I am using a static map (480pixels x 480pixels), the map's center is lat=47, lon=1.5 and the zoom level is 5.

Now what I need is being able to get lat and lon when I click a pixel on this static map. After some searches, I found that I should use Mercator Projection (right ?), I also found that each zoom level doubles the precision in both horizontal and vertical dimensions but I can't find the right formula to link pixel, zoom level and lat/lon...

My problem is only about getting lat/lon from pixel, knowing the center's coords and pixel and the zoom level...

Thank you in advance !

Pharmacy answered 4/5, 2012 at 2:54 Comment(1)
Do you have a link to the page where you learned about the Mercator Projection?Varnado
D
2

Use the Mercator projection.

If you project into a space of [0, 256) by [0,256]:

LatLng(47,=1.5) is Point(129.06666666666666, 90.04191318303863)

At zoom level 5, these equate to pixel coordinates:

x = 129.06666666666666 * 2^5 = 4130
y = 90.04191318303863 * 2^5 = 2881

Therefore, the top left of your map is at:

x = 4130 - 480/2 = 4070
y = 2881 - 480/2 = 2641

4070 / 2^5 = 127.1875
2641 / 2^5 = 82.53125

Finally:

Point(127.1875, 82.53125) is LatLng(53.72271667491848, -1.142578125)
Distort answered 4/5, 2012 at 20:45 Comment(2)
Thanks for your reply but I think something is wrong. Indeed you say my top left is at LatLng(53.72271667491848, -1.142578125) which is in UK but in fact, my map's top left corner is more in Ireland, take a look : i50.tinypic.com/9vb2b7.jpg You can see my window, - the map is centered on LatLng(47,1.5) - the map's size is 480x480 - the map's zoom level is 5 --> So I guess LatLng(47,1.5) == Point(240,240) no ? ( it seems true when I put my mouse over the Point(240,240) I'm really close to the LatLng(47,1.5) ) Thanks in advancePharmacy
@user1374021: old thread, i know, but i'm working on this problem and this answer was a big help. the problem was with the math to compute x; it should be x = 4130 - 480/2 = 3890. the y=2641 is correct. when you push those numbers through, you'll get the a lon of -9.00 which matches with your image.Rollerskate
P
1

Google-maps uses tiles for the map to efficient divide the world into a grid of 256^21 pixel tiles. Basically the world is made of 4 tiles in the lowest zoom. When you start to zoom you get 16 tiles and then 64 tiles and then 256 tiles. It basically a quadtree. Because such a 1d structure can only flatten a 2d you also need a mercantor projection or a conversion to WGS 84. Here is a good resource Convert long/lat to pixel x/y on a given picture. There is function in Google Maps that convert from lat-long pair to pixel. Here is a link but it says the tiles are 128x128 only: http://michal.guerquin.com/googlemaps.html.

  1. Google Maps V3 - How to calculate the zoom level for a given bounds
  2. http://www.physicsforums.com/showthread.php?t=455491
Preventer answered 4/5, 2012 at 5:47 Comment(1)
In fact, I don't "care" about the tiles, I'm doing this project in Java so all I have is a picture which I know its size (480pixels x 480pixels) and I know its center's coordinates (lat=47,lon=1.5 so center's pixel is 240x240 ) and its zoom level (5). All I need is the formula to get any pixel coordinates... Thanks in advancePharmacy
R
1

Based on the math in Chris Broadfoot's answer above and some other code on Stack Overflow for the Mercator Projection, I got this

public class MercatorProjection implements Projection {

    private static final double DEFAULT_PROJECTION_WIDTH = 256;
    private static final double DEFAULT_PROJECTION_HEIGHT = 256;

    private double centerLatitude;
    private double centerLongitude;
    private int areaWidthPx;
    private int areaHeightPx;
    // the scale that we would need for the a projection to fit the given area into a world view (1 = global, expect it to be > 1)
    private double areaScale;

    private double projectionWidth;
    private double projectionHeight;
    private double pixelsPerLonDegree;
    private double pixelsPerLonRadian;

    private double projectionCenterPx;
    private double projectionCenterPy;

    public MercatorProjection(
            double centerLatitude,
            double centerLongitude,
            int areaWidthPx,
            int areaHeightPx,
            double areaScale
    ) {
        this.centerLatitude = centerLatitude;
        this.centerLongitude = centerLongitude;
        this.areaWidthPx = areaWidthPx;
        this.areaHeightPx = areaHeightPx;
        this.areaScale = areaScale;

        // TODO stretch the projection to match to deformity at the center lat/lon?
        this.projectionWidth = DEFAULT_PROJECTION_WIDTH;
        this.projectionHeight = DEFAULT_PROJECTION_HEIGHT;
        this.pixelsPerLonDegree = this.projectionWidth / 360;
        this.pixelsPerLonRadian = this.projectionWidth / (2 * Math.PI);

        Point centerPoint = projectLocation(this.centerLatitude, this.centerLongitude);
        this.projectionCenterPx = centerPoint.x * this.areaScale;
        this.projectionCenterPy = centerPoint.y * this.areaScale;
    }

    @Override
    public Location getLocation(int px, int py) {
        double x = this.projectionCenterPx + (px - this.areaWidthPx / 2);
        double y = this.projectionCenterPy + (py - this.areaHeightPx / 2);

        return projectPx(x / this.areaScale, y / this.areaScale);
    }

    @Override
    public Point getPoint(double latitude, double longitude) {
        Point point = projectLocation(latitude, longitude);

        double x = (point.x * this.areaScale - this.projectionCenterPx) + this.areaWidthPx / 2;
        double y = (point.y * this.areaScale - this.projectionCenterPy) + this.areaHeightPx / 2;

        return new Point(x, y);
    }

    // from https://mcmap.net/q/628942/-how-to-get-bounds-of-a-google-static-map

    Location projectPx(double px, double py) {
        final double longitude = (px - this.projectionWidth/2) / this.pixelsPerLonDegree;
        final double latitudeRadians = (py - this.projectionHeight/2) / -this.pixelsPerLonRadian;
        final double latitude = rad2deg(2 * Math.atan(Math.exp(latitudeRadians)) - Math.PI / 2);
        return new Location() {
            @Override
            public double getLatitude() {
                return latitude;
            }

            @Override
            public double getLongitude() {
                return longitude;
            }
        };
    }

    Point projectLocation(double latitude, double longitude) {
        double px = this.projectionWidth / 2 + longitude * this.pixelsPerLonDegree;
        double siny = Math.sin(deg2rad(latitude));
        double py = this.projectionHeight / 2 + 0.5 * Math.log((1 + siny) / (1 - siny) ) * -this.pixelsPerLonRadian;
        Point result = new org.opencv.core.Point(px, py);
        return result;
    }

    private double rad2deg(double rad) {
        return (rad * 180) / Math.PI;
    }

    private double deg2rad(double deg) {
        return (deg * Math.PI) / 180;
    }
}

Here's a unit test for the original answer

public class MercatorProjectionTest {

    @Test
    public void testExample() {

        // tests against values in https://mcmap.net/q/1223301/-getting-lon-lat-from-pixel-coords-in-google-static-map

        double centerLatitude = 47;
        double centerLongitude = 1.5;

        int areaWidth = 480;
        int areaHeight = 480;

        // google (static) maps zoom level
        int zoom = 5;

        MercatorProjection projection = new MercatorProjection(
                centerLatitude,
                centerLongitude,
                areaWidth,
                areaHeight,
                Math.pow(2, zoom)
        );

        Point centerPoint = projection.projectLocation(centerLatitude, centerLongitude);
        Assert.assertEquals(129.06666666666666, centerPoint.x, 0.001);
        Assert.assertEquals(90.04191318303863, centerPoint.y, 0.001);

        Location topLeftByProjection = projection.projectPx(127.1875, 82.53125);
        Assert.assertEquals(53.72271667491848, topLeftByProjection.getLatitude(), 0.001);
        Assert.assertEquals(-1.142578125, topLeftByProjection.getLongitude(), 0.001);

        // NOTE sample has some pretty serious rounding errors
        Location topLeftByPixel = projection.getLocation(0, 0);
        Assert.assertEquals(53.72271667491848, topLeftByPixel.getLatitude(), 0.05);
        // the math for this is wrong in the sample (see comments)
        Assert.assertEquals(-9, topLeftByPixel.getLongitude(), 0.05);

        Point reverseTopLeftBase = projection.projectLocation(topLeftByPixel.getLatitude(), topLeftByPixel.getLongitude());
        Assert.assertEquals(121.5625, reverseTopLeftBase.x, 0.1);
        Assert.assertEquals(82.53125, reverseTopLeftBase.y, 0.1);

        Point reverseTopLeft = projection.getPoint(topLeftByPixel.getLatitude(), topLeftByPixel.getLongitude());
        Assert.assertEquals(0, reverseTopLeft.x, 0.001);
        Assert.assertEquals(0, reverseTopLeft.y, 0.001);

        Location bottomRightLocation = projection.getLocation(areaWidth, areaHeight);
        Point bottomRight = projection.getPoint(bottomRightLocation.getLatitude(), bottomRightLocation.getLongitude());
        Assert.assertEquals(areaWidth, bottomRight.x, 0.001);
        Assert.assertEquals(areaHeight, bottomRight.y, 0.001);
    }

}

If you're (say) working with aerial photography, I feel like the algorithm doesn't take into account the stretching effect of the mercator projection, so it might lose accuracy if your region of interest isn't relatively close to the equator. I guess you could approximate it by multiplying your x coordinates by cos(latitude) of the center?

Recently answered 7/1, 2017 at 23:58 Comment(0)
I
0

It seems worth mentioning that you can actually have the google maps API give you the latitudinal & longitudinal coordinates from pixel coordinates.

While it's a little convoluted in V3 here's an example of how to do it.
(NOTE: This is assuming you already have a map and the pixel vertices to be converted to a lat&lng coordinate):

let overlay  = new google.maps.OverlayView();
overlay.draw = function() {};
overlay.onAdd = function() {};
overlay.onRemove = function() {};
overlay.setMap(map);

let latlngObj = overlay.fromContainerPixelToLatLng(new google.maps.Point(pixelVertex.x, pixelVertex.y);

overlay.setMap(null); //removes the overlay

Hope that helps someone.

UPDATE: I realized that I did this two ways, both still utilizing the same way of creating the overlay (so I won't duplicate that code).

let point = new google.maps.Point(628.4160703464878, 244.02779437950872);
console.log(point);
let overlayProj = overlay.getProjection();
console.log(overlayProj);
let latLngVar = overlayProj.fromContainerPixelToLatLng(point);
console.log('the latitude is: '+latLngVar.lat()+' the longitude is: '+latLngVar.lng());
Indamine answered 17/8, 2017 at 5:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.