Calculating pixel-offset for location on OpenStreetMap

Ok, so I have a bit of a problem, which I'm pretty sure is more math and geography related than java related.

I am using OpenStreetMap to render a map. The map contains a handful of specified geographical coordinates. After I retrieve the static map from OSM, I am just painting the specified coordinates over top of the map image. The problem I am having is locating the correct latitudinal pixel location for each geographical coordinate. The longitudinal locations appear to be working correctly. Or, in other words, my "x" coordinate is correct, but my "y" coordinate is not (too small).

Here is the code which calculates the x and y (pixel) coordinates from the lat and lon (geographical) coordinates.

Code java:

// The number of KMs per pixel, for the specified latitutde
double distancePerPixel = map.getDistancePerPixel(airport.getLatitude());
// A coordinate on the same latitudinal line as "airport", but has its longitude as the map's origin
Coordinate latOrigin = new Coordinate(origin.getX(), airport.getLatitude());
// The horizontal distance between the two geographical coordinates
double xDistance = Coordinate.getDistanceBetween(airport.getCoordinate(), latOrigin);
// The x coordinate for the airport
int xCoordinate = (int)(xDistance/distancePerPixel);
// A coordinate on the same longitudinal line as "airport", but has its latitude as the map's origin
Coordinate lonOrigin = new Coordinate(airport.getLongitude(), origin.getY());
// The vertical distance between the two geographical coordinates
double yDistance = Coordinate.getDistanceBetween(lonOrigin, airport.getCoordinate());
// The y coordinate for the airport -- THIS IS WRONG
int yCoordinate = (int)(yDistance/distancePerPixel);

Notes about code:

1. **map** is just an object which contains information about the retrieved map

2. **origin** is the most north-west geographical coordinate visible on the map (the map's origin coordinate)

3. **airport** is the location I am trying to mark on the map

4. **Coordinate** is just a class which holds x/y coordinates, and also servers to hold lat/lon coordinates

I believe the yCoordinate values becomes "more wrong" the further you go down the map, which means I obviously need to calculate the vertical distance-per-pixels differently than the horizontal distance-per-pixels. The problem, is that I don't know the correct equation to use for this, and I can't find anything helpful on OSM's wikipages.

Does anyone have any suggestions?

Re: Calculating pixel-offset for location on OpenStreetMap

Interesting that the problem is with the latitude, not the longitude. The inter line distances are constant for latitude from the equator to the pole. The inter-longitude distances decrease going from the equator to the pole.

Re: Calculating pixel-offset for location on OpenStreetMap

Short answer is your distancePerPixel isn't the same for latitude as it is for longitude. Long answer ...

Google uses ESPG 900913 global web Mercator for map projection. The nuts and bolts of this projection system is that it treats the planet as a perfect sphere, unlike typical projections which correctly treat it as a spheroid. The practical implication is that you will get latitudinal drift the closer you get to the poles when trying to convert between the two.

I've been neck deep in GIS and satellite imagery development for a couple of months now and I have to warn you this is a lot more involved than you might imagine. For one thing when you say 'pixel coordinates' my mind immediately jumps to the tile (level of detail / x / y) for an individual 256*256 pixel image in the mosaic. I might be way off what you think when you say pixel coordinate but if not:

Code Java:

double sinLatitude = Math.Sin(latitude * Math.PI/180);
double pixelY = (0.5 – Math.Log10((1 + sinLatitude) / (1 – sinLatitude)) / (4 * Math.PI)) * 256 * (2 << level);

Also, a few links which I have found immensely helpful recently:

Tiles à la Google Maps: Coordinates, Tile Bounds and Projection

Bing Maps Tile System (Ignore the Bing specific quad key stuff)

Re: Calculating pixel-offset for location on OpenStreetMap

Quote:

Originally Posted by

**Norm**
Interesting that the problem is with the latitude, not the longitude. The inter line distances are constant for latitude from the equator to the pole. The inter-longitude distances decrease going from the equator to the pole.

Well, you will notice that the calculation for the distance per pixel in the image is dependent on the latitude. So it works for the inter-longitude distance, but I should not be using the same distance per pixel calculation for the inter-latitude distance. This would explain why the "incorrectness" of the y value changes along with the latitude. I know the distance between the lines for the inter-latitude are constant, but I am unsure how to calculate that distance, based on the zoom level. OSM's wikipage only mentions how to calculate the inter-longitude distances...

Quote:

For one thing when you say 'pixel coordinates' my mind immediately jumps to the tile (level of detail / x / y) for an individual 256*256 pixel image in the mosaic. I might be way off what you think when you say pixel coordinate but if not

No, you are correct. My map is the combination of several tiles, but the basic principle of calculating it should multiply across. I'll read up on some of the information you provided tomorrow.

The current way I am calculating the inter-longitude distance is this:

Code java:

public double getDistancePerPixel(double latitude) {
return (GeneralConstants.LENGTH_OF_EQUATOR*Math.cos(Math.toRadians(latitude))/Math.pow(2, this.zoom+8));
}

As far as I can tell, it works perfectly. GeneralConstants.LENGTH_OF_EQUATOR is the circumference of Earth at the equator (40075.016686 KM).

The only trouble I'm having is with the calculation which is "supposed" to be the easy one, lol.

Re: Calculating pixel-offset for location on OpenStreetMap

Quote:

my "y" coordinate is not (too small).

Can you give a few more details?

Are the positions and maps in the southern hemisphere?

What is the range of latitudes you are working with?

Are the computed positions on the correct longitude line but above/below the latitudes of known positions?

How much further off are the computed latitudes for positions at low latitudes vs those at high latitudes?

Reason for my interest: I wrote a very simple plotting program (without maps) for a waypoint program 10 years ago. It showed the relative positions of the waypoints together with the course and distances between them. It was for waypoints I used when sailing in various parts of the world. I never got it to work properly and haven't tried rewriting it with the currently available maps.

I'm currently working on an Android app for my tablet that uses Google Maps to display waypoints. Also there is an Android app: navigator from MapFactor that uses OSM maps that I have found useful when navigating in a city without having a wifi connection on my tablet. navigator gets my current location from GPS and plots my position on the OSM map.

1 Attachment(s)

Re: Calculating pixel-offset for location on OpenStreetMap

It will be easier to explain with an image. I provided a map with labeled airport POIs. To see the difference I am talking about, notice how the airport: SEA (Seattle) is not too far from where it should be, compared to the airport MCO (Orlando) which is being put into somewhere in South Carolina (instead of the middle of Florida). It would appear that all of the "x" values are correct, but the "y" values are wrong.

I believe the coordinates are restricted to the US, but that includes the US's insular areas (like the Virgin Islands, Guam, ect.), so I'm not 100% sure if the coordinates are ONLY in the Northwestern hemisphere (technically not a hemisphere, but I don't know off the top of my head what a quarter of the Earth is called).

I believe all the locations are too far North right now. SEA *might* be in the correct location. I think it *should* be a bit further south, but int-coordinate painting might have screwed with the accuracy a bit. The POIs further south are very obviously way too far North.

Attachment 2638

Re: Calculating pixel-offset for location on OpenStreetMap

Are the calculated positions at higher latitudes closer to correct than those at lower lats, but still too far north?

Is there a latitude where where the calculated lat is correctly positioned?

I'd pick two positions on the map (one at the top and the other at the bottom) and work on the math to calculate and plot them correctly.

Re: Calculating pixel-offset for location on OpenStreetMap

Quote:

Are the calculated positions at higher latitudes closer to correct than those at lower lats, but still too far north?

Yes.

Quote:

I'd pick two positions on the map (one at the top and the other at the bottom) and work on the math to calculate and plot them correctly.

I've thought about this, but I have one problem: verifying that they are plotted correctly. If my math to calculate from geographical coordinates to pixels is wrong, then converting back from pixels to geographical coordinate (to verify their placement) will also be wrong, lol.

I'm going to attempt your idea of just two positions and see if I can figure out an equation which works, but even a small mathematical mistake in my "solution" could be huge when provided with different input.

Re: Calculating pixel-offset for location on OpenStreetMap

Quote:

verifying that they are plotted correctly

Pick a physical feature and use Google Earth or Maps to get its latitude.

Re: Calculating pixel-offset for location on OpenStreetMap

Quote:

Originally Posted by

**Norm**
I'm currently working on an Android app for my tablet that uses Google Maps to display waypoints. Also there is an Android app: navigator from MapFactor that uses OSM maps that I have found useful when navigating in a city without having a wifi connection on my tablet. navigator gets my current location from GPS and plots my position on the OSM map.

Can I pitch a shameless plug for the software I am working on?

Mappt is a GIS suite for Android tablet that has KML and shapefile support, in app editing of waypoints & polys, background GPS tracking, offline caching, calculators, attributes, geofencing and custom imagery. I think it's pretty cool and everything is free except exporting.

Anyways back on topic.

Can you confirm your own calculations match mine?

Quote:

Seattle

WGS84: 47.443764, -122.302567

Zoom level: 3

Ground Resolution: 13234.021332778428 meters per pixel at this latitude

Map Size: 2048

Pixel: 328, 717

Tile: 1, 2

Pixel offset from this tile: 72, 205

PixelXY to Lat/Long: 47.39834920035925, -122.34375

Orlando

WGS84: 28.4158, -81.2989

Zoom level: 3

Ground Resolution: 17210.289888632455 meters per pixel at this latitude

Map Size: 2048

Pixel: 561, 855

Tile: 2, 3

Pixel offset from this tile: 49, 87

PixelXY to Lat/Long: 28.45903301972804, -81.38671875

Perth

WGS84: -31.9522, 115.8589

Zoom level: 3

Ground Resolution: 16603.147809246348 meters per pixel at this latitude

Map Size: 2048

Pixel: 1683, 1216

Tile: 6, 4

Pixel offset from this tile: 147, 192

PixelXY to Lat/Long: -31.952162238024968, 115.83984375

Code Java:

/**
*
* Global Web Mercator to pixel coordinates.
*
* Coverted to Java from
* [url=http://msdn.microsoft.com/en-us/library/bb259689.aspx]Bing Maps Tile System[/url]
*
* @author ChristopherLowe
*
*/
public class MapPoint {
public static final double EARTH_RADIUS = 6378137;
public static final int PIXELS_PER_TILE = 256;
/**
* Calculates the width and height in pixels at a given zoom level
*
* @param zoom
* @return
*/
public static int mapSize(int zoom) {
return 256 << zoom;
}
/**
* The ground resolution in Meters per pixel at a given zoom level
*
* @param latitude in decimal degrees
* @param zoom from 1 to 24 (highest)
* @return groundResolution in Meters per pixel
*/
public static double groundResolution(double latitude, int zoom) {
return Math.cos(latitude * Math.PI / 180) * 2 * Math.PI * EARTH_RADIUS / mapSize(zoom);
}
/**
* The X pixel location of a given WGS-84 longitude and zoom level
*
* @param longitude
* @param zoom
* @return
*/
public static int pixelX(double longitude, int zoom) {
double x = ((longitude + 180) / 360) * mapSize(zoom);
return (int) x;
}
/**
* The Y pixel location of a given WGS-84 latitude and zoom level
*
* @param latitude
* @param zoom
* @return
*/
public static int pixelY(double latitude, int zoom) {
int mapSize = PIXELS_PER_TILE * (1 << zoom); // 256 * 2^zoom
double sinLatitude = Math.sin(latitude * Math.PI / 180);
double y = 0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI);
int pixelY = (int) (y * mapSize + 0.5);
return pixelY;
}
/**
* The longitude of a given x pixel and zoom level
*
* @param pixelX
* @param zoom
* @return
*/
public static double pixelXToLongitude(int pixelX, int zoom) {
double mapSize = mapSize(zoom);
double x = pixelX / mapSize - 0.5;
return 360 * x;
}
/**
* The latitude of a given y pixel and zoom level
*
* @param pixelY
* @param zoom
* @return
*/
public static double pixelYToLatitude(int pixelY, int zoom) {
double mapSize = mapSize(zoom);
double y = 0.5 - pixelY / mapSize;
double latitude = 90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI)) / Math.PI;
return latitude;
}
/**
* The tile position of a pixel.
*
* @param pixel
* @return
*/
public static int pixelToTile(int pixel) {
return pixel / 256;
}
/**
* The pixel offset from a tile
*
* @param pixel
* @return
*/
public static int pixelOffsetForTile(int pixel) {
return pixel % PIXELS_PER_TILE;
}
//////////////////////////////////////////////////////////
//
// Test
//
public static void printTest(double latitude, double longitude, int zoom) {
int pixelX = pixelX(longitude, zoom);
int pixelY = pixelY(latitude, zoom);
double groundResolution = groundResolution(latitude, zoom);
//Convert the pixel x/y back to lat/lng to verify the calculations
double recalculatedLat = pixelYToLatitude(pixelY, zoom);
double recalculateLng = pixelXToLongitude(pixelX, zoom);
System.out.println("WGS84: " + latitude + ", " + longitude);
System.out.println("Zoom level: " + zoom);
System.out.println("Ground Resolution: " + groundResolution + " meters per pixel at this latitude");
System.out.println("Map Size: " + mapSize(zoom));
System.out.println("Pixel: " + pixelX + ", " + pixelY);
System.out.println("Tile: " + pixelToTile(pixelX) + ", " + pixelToTile(pixelY));
System.out.println("Pixel offset from this tile: " + pixelOffsetForTile(pixelX) + ", " + pixelOffsetForTile(pixelY));
System.out.println("PixelXY to Lat/Long: " + recalculatedLat + ", " + recalculateLng);
System.out.println();
}
public static void test() {
// Seattle -122.302567,47.443764 47.6097° N, 122.3331
// Zoom level 3
System.out.println("Seattle");
printTest(47.443764, -122.302567, 3);
// Orlando 28.4158° N, 81.2989° W
// Zoom level 3
System.out.println("Orlando");
printTest(28.4158, -81.2989, 3);
// Perth 31.9522° S, 115.8589° E
// Zoom level 3
System.out.println("Perth");
printTest(-31.9522, 115.8589, 3);
}
public static void main(String[] args) {
test();
}
}

Re: Calculating pixel-offset for location on OpenStreetMap

Code :

// The number of KMs per pixel, for the specified latitutde
double distancePerPixel = map.getDistancePerPixel(airport.getLatitude());

distance per pixel is different for lat and long

try using an arg of 0 for a new variable: distancePerPixelForLat

@ChristopherLowe Thanks for the link. I'll take a look at Mappt.

Re: Calculating pixel-offset for location on OpenStreetMap

@ChristopherLowe, I think you have the x and y coordinates swapped. Have you tried plotting on a map?

Quote:

try using an arg of 0 for a new variable: distancePerPixelForLat

Hmm, this is interesting. As far as I can tell, I believe doing that just shifted every single point about 40 pixels further north.

I'm still playing around with some ideas. I figure the first step should be to try to accurately draw the latitude lines on the map. If I can do that, I should be able to draw all the points at the correct y locations.

EDIT: Ok, interesting development here. By adding the zoom by an extra 0.5 when calculating the distance per pixel for y, I was able to get the latitude grid lines mostly correct. The point for SJU is now too far south, and a few of the others a little bit off too, but this is really close. I suppose this means that something needs to change in the denominator.

Here are the new methods for calculating the latitude and longitude distance per pixels:

Code java:

/**
* @param latitude the latitude to calculate the distance with
* @return the distance (west to east) per pixel for the map
*/
public double getDistancePerPixel(double latitude) {
// LENGTH_OF_EQUATOR = 40075.016686
return (GeneralConstants.LENGTH_OF_EQUATOR*Math.cos(Math.toRadians(latitude))/Math.pow(2, this.zoom+8));
}
/**
* @return the latitude distance (north to south) per pixel for the map
*/
public double getLatitudeDistancePerPixel() {
// LENGTH_OF_POLES = 40008
return (GeneralConstants.LENGTH_OF_POLES)/Math.pow(2, this.zoom+8.5);
}

**NOTE: Math.cos(0) is not needed since it equals 1**

Any ideas on what should change in the denominator?

1 Attachment(s)

Re: Calculating pixel-offset for location on OpenStreetMap

Ok, so I took a very different approach, and I got some very interesting results.

First off, I decided to not calculate the location from the entire map, but instead calculate the location from the individual coordinate's tile, and then add on the x and y offset for the tile. I figured this would reduce the amount of mathematical error, since the formulas would be calculated with much smaller values.

This has positioned SJU in the correct place, as well as a few others, but most of them are still a bit off.

First off, my "OSMap" class (which contains a bunch of info about the retrieved map) now looks like this (I have omitted all the code which is not needed for this problem):

Code java:

public class OSMap {
private int zoom;
private List<List<Tile>> tileList;
/**
* @param latitude the latitude to calculate the distance with
* @return the distance (west to east) per pixel for the map
*/
public double getDistancePerPixel(double latitude) {
// LENGTH_OF_EQUATOR = 40075.016686
return (GeneralConstants.LENGTH_OF_EQUATOR*Math.cos(Math.toRadians(latitude))/Math.pow(2, this.zoom+8));
}
/**
* @return the latitude distance (north to south) per pixel for the map
*/
public double getLatitudeDistancePerPixel() {
// LENGTH_OF_POLES = 40008
return (GeneralConstants.LENGTH_OF_POLES)/(Math.pow(2, this.zoom+8)); //*1.4
}
/**
* Finds the pixel coordinates for the given geographical coordines.
*
* @param toFind the geographical coordinates to find the pixel coordinates for
* @return the pixel coordinates for the geographical coordinates
*/
public Coordinate getPixelLocationForCoordinate(Coordinate toFind) {
// Get the tile for the coordinate
Tile tile = getTileFor(toFind);
if(tile==null)
return null;
// Set the x and y pixel coordinate variables
int xPixels = 0;
int yPixels = 0;
// Offset the x and y pixel coordinates for the given tile
OUTER:
for(List<Tile> row : this.tileList) {
for(Tile rowTile : row) {
if(tile==rowTile) {
break OUTER;
}
xPixels+=GeneralConstants.OS_MAP_IMAGE_WIDTH;
}
xPixels = 0;
yPixels+=GeneralConstants.OS_MAP_IMAGE_HEIGHT;
}
// Origin Geographical Coordinate for the tile
Coordinate tileOrigin = getOriginForCoordinate(tile);
// The horizontal distance per pixel (west to east)
double horizontalDistancePerPixel = getDistancePerPixel(toFind.getY());
// The vertical distance per pixel (north to south)
double verticalDistancePerPixel = getLatitudeDistancePerPixel();
// The horizontal origin to calculate the distance against
Coordinate horizontalOrigin = new Coordinate(tileOrigin.getX(), toFind.getY());
// The horizontal geographical distance
double xDistance = Coordinate.getDistanceBetween(toFind, horizontalOrigin);
// The pixel x coordinate for the tile and the coordinate
int xCoordinate = (int)(xDistance/horizontalDistancePerPixel);
xPixels += xCoordinate;
// The vertical origin to calculate the distance against
Coordinate verticalOrigin = new Coordinate(toFind.getX(), tileOrigin.getY());
// The vertical geographical distance
double yDistance = Coordinate.getDistanceBetween(verticalOrigin, toFind);
// The pixel y coordinate for the tile and the coordinate
int yCoordinate = (int)(yDistance/verticalDistancePerPixel);
yPixels += yCoordinate;
return new Coordinate(xPixels,yPixels);
}
private Coordinate getOriginForCoordinate(Tile originFor) {
return OSMapHelper.getMapOrigin(originFor.getX(),originFor.getY(),this.zoom);
}
private Tile getTileFor(Coordinate tileFor) {
int xTile = OSMapHelper.getXTile(tileFor.getX(),this.zoom);
int yTile = OSMapHelper.getYTile(tileFor.getY(), this.zoom);
for(List<Tile> row : this.tileList) {
for(Tile tile : row) {
if(tile.getX()==xTile && tile.getY()==yTile) {
return tile;
}
}
}
return null;
}
}

The "Tile" object is just a container for the X and Y tile numbers used in the web query to OSM's url. When I load a new map tile, I create a new "Tile" object with the queried X and Y tile numbers and add it to the tile list. I do this so I can calculate the tile offset.

Now, in the class where I draw the map and paint over it, I have this code:

Code java:

g.setColor(Color.GRAY);
int x = 0;
int y = 0;
while(x<ApplicationConstants.SUB_PANEL_WIDTH) {
g.drawLine(x, 0, x, ApplicationConstants.SUB_PANEL_HEIGHT);
x+=256;
}
while(y<ApplicationConstants.SUB_PANEL_HEIGHT) {
g.drawLine(0, y, ApplicationConstants.SUB_PANEL_WIDTH, y);
y+=256;
}
g.setColor(Color.RED);
double startLon = Database.getAirport("SEA").getLongitude();
double startLat = Database.getAirport("SEA").getLatitude();
double offset = 1;
for(int i=0;i<30;i++) {
Coordinate start = map.getPixelLocationForCoordinate(new Coordinate(startLon,startLat));
if(start==null) {
logger.info("Start Null: "+startLon+", "+startLat);
break;
}
Coordinate end = map.getPixelLocationForCoordinate(new Coordinate(startLon+offset,startLat-offset));
if(end==null) {
logger.info("End Null: "+(startLon+offset)+", "+(startLat-offset));
break;
}
g.drawOval((int)end.getX(), (int)end.getY(), 5,5);
g.drawLine((int)start.getX(), (int)start.getY(), (int)end.getX(), (int)end.getY());
startLon+=offset;
startLat-=offset;
}
g.setColor(Color.BLACK);
for(Airport airport : Database.getUserCompany().getAirports()) {
Coordinate mapLocation = map.getPixelLocationForCoordinate(airport.getCoordinate());
AirportPOI poi = new AirportPOI(airport,mapLocation,POI_RADIUS);
airportMapping.put(airport, poi);
int xCoordinate = (int)mapLocation.getX();
int yCoordinate = (int)mapLocation.getY();
logger.info("Airport ("+airport.getCode()+") Location: ("+mapLocation.getX()+","+mapLocation.getY()+") Coord: ("+airport.getLatitude()+","+airport.getLongitude()+")");
g.fillOval(xCoordinate, yCoordinate, POI_RADIUS, POI_RADIUS);
g.drawString(airport.getCode(), xCoordinate+POI_RADIUS, yCoordinate);
}

This code is doing a few things. First it is painting gray borders around the individual tiles, second it is painting a red line (which should be straight) for debugging, and third it is painting the airport locations and their names.

Now, if the calculations were correct, that red line **should** be straight. But, it is not. What appears to happen is the red line is straight until it reaches some certain inconsistent location in a tile, and then suddenly leaps down. I have attached a picture of the generated map to show what I mean.

Any thoughts on what is happening?

Attachment 2654